我可以使用 Grand Central Dispatch 将任务分派到 OSX DisplayLink 线程吗?

Bil*_*ngs 5 opengl macos multithreading grand-central-dispatch

在 OSX 下的 OpenGL 应用程序中,渲染代码通常在 DisplayLink 线程上运行,该线程与主线程分开。

在后台执行任务(例如加载 GL 资源)时,同步线程非常重要,因此渲染线程不会尝试从后台线程正在主动更改的模型中进行绘制。

当渲染发生在主线程上时,我一直在使用 GCD 将后台任务的关键部分分派到主分派队列,如下所示:

dispatch_async(dispatch_get_main_queue(), ^{ [self doCriticalThing]; });
Run Code Online (Sandbox Code Playgroud)

但是,当渲染发生在 DisplayLink 线程上时,这不起作用,因为毫不奇怪,当 DisplayLink 尝试渲染时,我会在尝试运行关键任务的主线程之间遇到线程冲突。

是否可以使用 GCD 将任务分派到 DisplayLink 线程,而不是主线程?

或者我是否需要恢复使用类似的东西:

performSelector:onThread:withObject:waitUntilDone:
Run Code Online (Sandbox Code Playgroud)

直接将任务分配给DisplayLink线程?

ipm*_*mcc 3

似乎没有任何内置 API 可以实现此目的。不过做饭还是相当简单的。它可能看起来像这样:

static CVReturn DispatchDisplayLinkOneshotCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext);

void dispatch_async_displaylink(CVDisplayLinkRef dl, dispatch_block_t block)
{
    if (!block || !dl)
        return;

    CVDisplayLinkRef privateDisplayLink = nil;
    if (kCVReturnSuccess != CVDisplayLinkCreateWithCGDisplay(CVDisplayLinkGetCurrentCGDisplay(dl), &privateDisplayLink) || !privateDisplayLink)
    {
        NSLog(@"Couldn't create display link for dispatch");
        return;
    }

    CVDisplayLinkSetOutputCallback(privateDisplayLink, DispatchDisplayLinkOneshotCallback, (void*)CFBridgingRetain([block copy]));
    CVDisplayLinkStart(privateDisplayLink);
}

static CVReturn DispatchDisplayLinkOneshotCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext)
{
    dispatch_block_t block = (dispatch_block_t)CFBridgingRelease(displayLinkContext);
    block();
    CVDisplayLinkStop(displayLink);
    CVDisplayLinkSetOutputCallback(displayLink, NULL, NULL);
    CVDisplayLinkRelease(displayLink);
    return kCVReturnSuccess;
}
Run Code Online (Sandbox Code Playgroud)

根据经验,我确实注意到,使用这种方法排队的块的最终执行顺序不会得到维护,因此,如果这对您很重要,您可能会想要采取不同的方法,但如果您要寻找的是“在显示链接回调上调用此块”这应该可以做到。

另外,FWIW,从技术上讲,这种方法并不是在您传递给它的确切 回调上执行CVDisplayLink,而是查询该显示的回调,然后创建一个新的“一次性”,CVDisplayLink因此如果您需要强有力的保证CVDisplayLink再次使用精确的回调,您需要为此添加一些额外的机制。

-performSelector:onThread:withObject:waitUntilDone:是基于运行循环的,并且CVDisplayLink线程似乎没有运行循环(它们有一些东西,但看起来不像CFRunLoop)编辑:显然这毕竟有效。不确定那是什么,但这是一个选项。

编辑:好吧,我无法抗拒。这是一个替代示例,它维护顺序并使用CVDisplayLink您在初始化时传递给它的特定内容。如果你有一个也需要被调用的“主要”回调,你必须在初始化时传递它(及其上下文)(因为一旦设置了现有回调,就没有 API 可以从中获取它CVDisplayLink,我们需要设置回调以便为我们的队列提供服务。)

typedef CVReturn (^CVDisplayLinkBlock)(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut);

@interface MYDisplayLinkDispatcher : NSObject

- (instancetype)initWithDisplayLink: (CVDisplayLinkRef)dl primaryCallback: (CVDisplayLinkOutputCallback)primaryCallback primaryCallbackContext: (void*)pcc;
- (instancetype)initWithDisplayLink: (CVDisplayLinkRef)dl; // No primary callback

- (void)performDisplayLinkBlock: (CVDisplayLinkBlock)block;
- (void)performBlock: (dispatch_block_t)block;

@end

@implementation MYDisplayLinkDispatcher
{
    CVDisplayLinkRef _displayLink;
    NSMutableArray* _performQueue;
    dispatch_queue_t _guardQueue;
    CVDisplayLinkOutputCallback _primaryCallback;
    void* _primaryCallbackContext;
    BOOL _didStartLink;
}

static CVReturn MYDisplayLinkDispatcherOutputCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext);

- (instancetype)initWithDisplayLink: (CVDisplayLinkRef)dl primaryCallback: (CVDisplayLinkOutputCallback)primaryCallback primaryCallbackContext: (void*)pcc
{
    if (self = [super init])
    {
        if (!dl)
        {
            return nil;
        }

        _displayLink = CVDisplayLinkRetain(dl);
        _performQueue = [NSMutableArray array];
        _guardQueue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
        _primaryCallback = primaryCallback;
        _primaryCallbackContext = pcc;

        if (kCVReturnSuccess != CVDisplayLinkSetOutputCallback(_displayLink, MYDisplayLinkDispatcherOutputCallback, (__bridge void *)(self)))
        {
            NSLog(@"Couldn't set output callback on displayLink");
            return nil;
        }
    }
    return self;
}

- (instancetype)initWithDisplayLink: (CVDisplayLinkRef)dl
{
    return [self initWithDisplayLink: dl primaryCallback: NULL primaryCallbackContext: NULL];
}

- (void)dealloc
{
    // If we started it, we should stop it too...
    if (_didStartLink) CVDisplayLinkStop(_displayLink);
    CVDisplayLinkSetOutputCallback(_displayLink, NULL, NULL);
    CVDisplayLinkRelease(_displayLink);
}

- (void)performDisplayLinkBlock: (CVDisplayLinkBlock)block
{
    if (block)
    {

        block = ^CVReturn(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut)
        {
            // Capture self so self is assured to stick around through the execution of the block.
            id localSelf __attribute__ ((objc_precise_lifetime)) = self;
            return block(displayLink, inNow, inOutputTime, flagsIn, flagsOut);
#pragma unused(localSelf)
        };

        // If it's not running we need to start it, otherwise this block will never run.
        const BOOL isRunning = CVDisplayLinkIsRunning(_displayLink);
        dispatch_sync(self->_guardQueue, ^{
            [_performQueue insertObject: [block copy] atIndex: 0];
            _didStartLink = _didStartLink || !isRunning;
        });

        if (!isRunning)
        {
            CVDisplayLinkStart(_displayLink);
        }
    }
}

- (void)performBlock: (dispatch_block_t)block
{
    if (block)
    {
        [self performDisplayLinkBlock:^CVReturn(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut) {
            block();
            return kCVReturnSuccess;
        }];
    }
}

static CVReturn MYDisplayLinkDispatcherOutputCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext)
{
    MYDisplayLinkDispatcher* self = (__bridge MYDisplayLinkDispatcher *)(displayLinkContext);

    // Always do the primary callback first.
    if (self->_primaryCallback)
    {
        CVReturn result = self->_primaryCallback(displayLink, inNow, inOutputTime, flagsIn, flagsOut, self->_primaryCallbackContext);
        if (kCVReturnSuccess != result)
        {
            return result;
        }
    }

    // Then service the queue until its empty or until we get an error condition.
    __block CVDisplayLinkBlock block = nil;
    do
    {
        dispatch_sync(self->_guardQueue, ^{
            block = [self->_performQueue lastObject];
            [self->_performQueue removeLastObject];
        });

        if (block)
        {
            CVReturn result = block(displayLink, inNow, inOutputTime, flagsIn, flagsOut);
            if (kCVReturnSuccess != result)
            {
                return result;
            }
        }
    } while (block);

    return kCVReturnSuccess;
}

@end
Run Code Online (Sandbox Code Playgroud)

然后要使用它,您可以执行以下操作:

CVDisplayLinkRef dl = nil;
if (kCVReturnSuccess != CVDisplayLinkCreateWithActiveCGDisplays(&dl))
{
    NSLog(@"Problem");
}

MYDisplayLinkDispatcher* dld = [[MYDisplayLinkDispatcher alloc] initWithDisplayLink: dl];

for (NSUInteger i = 0; i < 100; i++)
{
    [dld performBlock:^{
        NSLog(@"Whee! Hello from the display link thread! i: %@", @(i));
    }];
}
Run Code Online (Sandbox Code Playgroud)