CVDisplayLink instead of NSTimer - objective-c

I have started to implement cvDisplayLink to drive the render loop instead of nstimer, as detailed in this technical note https://developer.apple.com/library/archive/qa/qa1385/_index.html
Is it better to do the actual rendering within the displaylink callback itself, or should I instead call setNeedsDisplay on the view and let the main thread do the rendering?
In order to render directly in the callback, I need to lock the opengl context, which I was hoping to avoid.
Is it possible to use cvDisplayLink for timing and still render on the main thread?
Thanks in advance for any feedback....

There’s no intrinsic problem with locking the context; it should be uncontended and unlock quickly. Redrawing on the main thread, via -setNeedsDisplay:, has a chance of missing the VBL window just because of the runloop round-trip.

Related

Multiple threads to draw in NSView

In my code, I subclassed NSView and in its drawRect method, I am spawning three threads to perform the drawing.
-(void)drawRect:(NSRect)dirtyRect
{
[[self window] setAllowsConcurrentViewDrawing:YES];
[self setCanDrawConcurrently:YES];
[NSThread detachNewThreadSelector:#selector(DrawText) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:#selector(DrawRectangle) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:#selector(DrawGradient) toTarget:self withObject:nil];
//Wherease these functions DrawText, DrawRectangle and DrawGradient performs their task as suggested by name.
//In DrawText, DrawRectangle, and DrawGradient lockFocus and unlockFocus is being
// used for multithreaded drawing.
}
When I run the same program from Xcode, it is running fine. Output is shown below.
But when I run it from the outside, there is problem and output is shown below.
First, I would like to know is it right way to draw from a secondary thread? Or what is another way to draw from a secondary thread?
What is the reason behind this problem?
Ken Aspeslagh is somewhat incorrect about drawing from secondary threads (he is correct it is generally a bad idea). From what I can see of your code you don't have a good use case for drawing on a secondary thread. Can you explain why you want to do this?
you yourself have already discovered setCanDrawConcurrently: which explicitly talks of invoking drawRect: from a background thread. Note that the views window must have allowsConcurrentViewDrawing set to YES for this to work (it is the default).
Apples own Cocoa Drawing Guide has a section on drawing from secondary threads. I have highlighted some portions I think are relevant to you.
The Application Kit maintains a unique graphics context for each window and thread combination. Because each thread has its own graphics context object for a given window, it is possible to use secondary threads to draw to that window. There are some caveats, however.
During the normal update cycle for windows, all drawing requests are sent to your application’s main thread for processing. The normal update cycle happens when a user event triggers a change in your user interface. In this situation, you would call the setNeedsDisplay: or setNeedsDisplayInRect: method (or the display family of methods) from your application’s main thread to invalidate the portions of your view that require redrawing. You should not call these methods from any secondary threads.
If you want to update a window or view from a secondary thread, you must manually lock focus on the window or view and initiate drawing yourself. Locking focus configures the drawing environment for that window's graphics context. Once locked, you can configure the drawing environment, issue your drawing commands as usual, and then flush the contents of the graphics context to the window buffer.
In order to draw regularly on a secondary thread, you must notify the thread yourself. The simplest way to send regular notifications is using an NSTimer or NSAnimation object. For more information on how to animate content, see “Advanced Drawing Techniques.”
The Cocoa Threading Programming Guide also says this:
If you want to use a thread to draw to a view, bracket all drawing code between the lockFocusIfCanDraw and unlockFocus methods of NSView
Just an aside, GCD block invocation is probably a much nicer method for performing small sets of operations in the background than NSThread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// you can put each of these calls in their own queue if you want
[self DrawText];
[self DrawRectangle];
[self DrawGradient];
});
However, that likely has nothing to do with your problem; I mention it only because I think it will serve you better to use GCD queues.
You should only be drawing to the screen from the main thread.
Edit: It is apparently really complicated so you're better off drawing to the screen from the main thread. ;)
If you need to render something that takes too much time to avoid blocking the main thread, consider using a thread to do the drawing to an offscreen context, and then copy that context to the screen on the main thread.
I read about NSGraphicsContext Restriction at Thread guide.
Here, I found the following line:
If you do any drawing from a secondary thread, you must flush your drawing calls manually. Cocoa does not automatically update views with content drawn from secondary threads, so you need to call the flushGraphics method of NSGraphicsContext when you finish your drawing. If your application draws content from the main thread only, you do not need to flush your drawing calls.
After calling flushGraphics, it works fine.

How can I allow a user adjust an NSSlider without pausing the application update loop?

NOTE: Updated below...
I have a cocoa desktop application which consists of a series of controls around a custom NSView. I am using displayLink to drive the updates.
When a user clicks on an NSControl (a slider, a button, a checkbox, a radio button) the application appears to freeze until the mouse is released. I can confirm in fact that the displayLink callback (getFrameForTime) is NOT firing during the time. If I create a timer, that also does not fire, both remain paused until the user releases the mouse, at which point the application resumes updating.
The control is bound, and if I update that value from another thread (for example, via a callback from a MIDI interface) the slider behaves as expected: it moves, the value updates and the application does not pause.
I feel like this should be a fairly obvious fix, but I'm stumped.
Checking "continuous" in IB does as advertised: sends the values continuously, but still exhibits this behavior (preventing the UI update) until the mouse is released.
This seems to be related specifically to mouseDown on NSControl? Why would this block, and do I really need to subclass all my UI elements to change this behavior (seems extreme)
DisplayLink is in its own thread, so why mouseDown on the main thread block it? If this is the case, given the injunction on updating the Cocoa UI from other than the main thread, how do I deal with it?
Any help much appreciated.
Update
Per #Nikolai's comments below, I can confirm that using an NSTimer and adding it to NSEventTrackingRunLoopMode does NOT block. However, I would really like to use CVDisplayLink which (according to the documentation) runs in it's own thread and should not be blocked in this way. Unlike CADisplayLink, I cannot find a way to explicitly assign a runloop to CVDisplayLink (it seems it doesn't work that way), so perhaps the new question should be:
Why does CVDisplayLink block on NSEventTrackingRunLoopMode?
When clicking on an NSControl the runloop mode goes from NSDefaultRunLoopMode to NSEventTrackingRunLoopMode, as long as the mouse is down. That means that only run loop sources (display link) and timers fire that have been added to this mode.
You can add timers to any mode by using -[NSRunLoop addTimer:forMode:]. For a display link the equivalent method is -[CADisplayLink addToRunLoop:forMode:].
To make your animation continue during event tracking you would do something like:
[myDisplayLink addToRunLoop:[NSRunLoop currentRunLoop]
forMode:NSEventTrackingRunLoopMode];
Your test project shows that you are calling a view's display method from within the display link's callback.
When commenting the display message out, the display link is called continuously even while moving the slider.
So what goes wrong is that when the runloop goes into event tracking mode, the call to display on the display link's thread blocks until the mouse is released and the run loop goes back to default mode. You can easily confirm this by putting a log statement before the call and one after it.
Why exactly that happens is not clear to me. What is clear is that it's illegal to call a view's methods from a background thread. You have to trigger the view's display by dispatching a setNeedsDisplay: on the main thread:
static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
dispatch_async(dispatch_get_main_queue(), ^{
[(__bridge MyCustomView*)displayLinkContext setNeedsDisplay:YES];
});
return kCVReturnSuccess;
}

Window blocks on execute Cocoa

My problem is that my application's window blocks when I raise a method that represents values in a NSTableView. I want to do an animation with a NSProgressIndicator (spinner), but my window is blocked and spinner doesn't show animation.
I want if anybody can give me any hint? I thought in threads or something like this, but I'm not pretty sure how to solve this issue.
You're right, you need some kind of multithreading. Fortunately, it's rather easy to do simple operations on threads in Cocoa.
You should look into NSOperation and NSOperationQueue.
You may find this blogpost useful: turbo charging your apps with nsoperation

How can I update the contents of a CALayer while the main thread is blocked?

An instance of AVCaptureVideoPreviewLayer continues to update its contents from the video capture stream even while the main thread is blocked. Is it possible to generally replicate this behaviour with a custom subclass of CALayer? In other words, given raw image data, can we update what is displayed on-screen while the main thread is blocked?
You can't update anything in the view when the main thread is blocked. The whole of UIKit is single-threaded and runs on the main event loop. Video capture is a special case because it draws directly to the screen buffer, but you won't be able to replicate it yourself.
Furthermore, if you do a long-running task on the main thread, iOS will assume your app has crashed and kill it after a few seconds anyway.
Why not perform your other task on a background thread instead? That's the standard practice.
I've found a way to update the UI on non-UI-Thread.
We can excute the code in any thread, and it actually changes the layer's transform even when the main thread is sleeping.
self.labelLayer.transform = CATransform3DMakeScale(1.2, 1.2, 1.0);
So if anyone can explain this, please feel free to concat me!

Cancelable loading in background thread

I have a window that displays some data in an NSTableView. This data is loaded in the background. The data-loading-thread is started in the windowDidLoad: method. If the window is closed before loading has finished, the background thread should be cancelled. I do this by signalling the thread in the windowWillClose: delegate method and waiting for the background thread to finish.
Now this all works perfectly. But I have one problem: How can I update the data in the table view? I have tried calling reloadData via performSelectorOnMainThread: but this leads to a race condition: The reloadData call is sometimes queued on the main thread after the window close command, and will execute after the window has closed, and everything goes up in flames.
What's the best way to control and communicate with a background thread?
Well, you know, this is exactly what makes the use of threading complex: you always face synchronization issues.
What I suggest is, instead of calling [tableView reloadData] from your thread, simply signal your controller (by calling a method controllerShouldReloadTable) and let your controller do the check if windowWillClose has been called or not. There might be a chance that your controller has been also released by the time controllerShouldReloadTable, and to fix this you will definitely need to retain the controller from the secondary thread.
On a side note, I would cancel the thread in viewDidUnload (for symmetry).
Most important: I would use asynchronous calls and a delegate class so that the whole multithreading issue is solved at its root.
EDIT: Sending asynchronously a request will not block the sending thread waiting for the response. Instead, asynchronous send (for NSURLConnection is called start) immediately returns (so, no blocking) and when the response is received, a delegate method will be called (i.e., connectionDidFinishLoading:) so that you can updated the model and the UI. Take a look at NSURLConnection docs, but as usual, I strongly suggest using [ASIHTTPRequest][2], which has many advantages.