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线程?
似乎没有任何内置 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)