Skip to content

Problem with the g_max_inflight_buffers design? #2

@bhaller

Description

@bhaller

Hi Nick. Your code has been useful for me to understand how to make a screensaver that uses Metal, thanks. I think there might be an issue with it that needs fixing, though. I'm not sure whether this has user-visible consequences or not.

Here's the problem. The CVDisplayLink timer set up in -startAnimation ticks along, firing DisplayLinkCallback periodically, which calls -animateOneFrame. That keeps going unless/until -stopAnimation is called, or the screensaver wakes up. So far, so good.

On the other hand, -animateOneFrame basically makes an autorelease pool and calls -re_render, and -rc_render does the following:

dispatch_semaphore_wait(_inflight_semaphore, DISPATCH_TIME_FOREVER);

This blocks the thread until _inflight_semaphore says we're clear to proceed with rendering the next frame. And that makes sense; we don't want to get ahead of the GPU too much, and we have a limited number of vector buffers to work with. So far, so good.

The issue, it seems to me – if there is an issue! – lies in the way these two pieces of code interact. Suppose the GPU is chronically behind in handling the rendering; it can't keep up with the frame rate we'd like to feed it. Once it gets sufficiently far behind, the dispatch_semaphore_wait() call will block waiting for the GPU to catch up; but the CVDisplayLink will keep firing, and so further calls will be made to -animateOneFrame, and onward to -rc_render, which will also block. So if the GPU is slow enough, and just can't keep up, you'll start burning through threads, all blocked in dispatch_semaphore_wait(), adding a new one every 60th of a second or whatever, and releasing one whenever the GPU finishes rendering a frame and _inflight_semaphore allows one thread to unblock.

Is this not a problem for some reason? I guess I'm assuming that CVDisplayLink fires on whatever free thread it is given by GCD, and that if that thread blocks, it will then fire next time on whatever different thread it is given by GCD the next time around, or something. But maybe if the thread that it fires on blocks, it just doesn't fire any more until that thread returns?

I'm wondering about all this in the context of my own screensaver, Satori, which I just recently ported to Metal (http://www.sticksoftware.com/software/Satori.html). Based on some Apple sample code, I set up my CVDisplayLink timer a bit differently:

- (BOOL)setupCVDisplayLinkForScreen:(NSScreen*)screen
{
	CVReturn cvReturn;
	
	// Run our own timer, synchronized to the display's refresh rate
	if (!displaySource_)
	{
		// The CVDisplayLink callback, DispatchRenderLoop, never executes on the main thread. To execute rendering on the main thread, create a dispatch source
		// using the main queue (the main thread).  DispatchRenderLoop merges this dispatch source in each call to execute rendering on the main thread.
		displaySource_ = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
		__weak ComStickSatoriView *weakSelf = self;
		dispatch_source_set_event_handler(displaySource_, ^(){
			[weakSelf animationTick];
		});
		dispatch_resume(displaySource_);
	}
	
	if (displayLink_)
		CVDisplayLinkRelease(displayLink_);
	
	// Create a display link capable of being used with all active displays
	cvReturn = CVDisplayLinkCreateWithActiveCGDisplays(&displayLink_);
	
	if (cvReturn != kCVReturnSuccess)
		return NO;
	
	// Set DispatchRenderLoop as the callback function and supply displaySource_ as the argument to the callback.
	cvReturn = CVDisplayLinkSetOutputCallback(displayLink_, &DispatchRenderLoop, (__bridge void *)displaySource_);
	
	if (cvReturn != kCVReturnSuccess)
		return NO;
	
	// Associate the display link with the display on which the view resides
	CGDirectDisplayID viewDisplayID = [self.window.screen.deviceDescription[@"NSScreenNumber"] unsignedIntValue];
	
	cvReturn = CVDisplayLinkSetCurrentCGDisplay(displayLink_, viewDisplayID);
	
	if (cvReturn != kCVReturnSuccess)
		return NO;
	
	CVDisplayLinkStart(displayLink_);
	
    return YES;
}

Even if your code does not suffer from the problem I described above, I'm wondering whether maybe mine does, since I shift the rendering work onto the main thread. The main thread is then the one that blocks in dispatch_semaphore_wait(), in my code; so then perhaps further firings of CVDisplayLink can occur, and clog up background threads, while the main thread is blocked? I'm still trying to get used to how GCD and blocks and such manage threads; this is a new world for me, I've used NSThread for multithreading until this recent work moving Satori to Metal.

If you're willing to take a few minutes to clarify whether this is an issue, for either your code or mine, and if not, why not, I'd greatly appreciate the guidance. Again, thanks for the very useful example code!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions