feat: use CAMetalDisplayLink for metal renderer to fix refresh rate issues
- update imgui to use stream fps instead of hardcoded 60 - hide renderer setting for iOS < 17 as CAMetalDisplayLink only supports later versions
This commit is contained in:
1 parent
8d05e8e670
commit
628ce0f12e
9 files changed
+147
-46
No files matched your search
@@ -443,21 +443,30 @@ CFStringRef __currentColorSpace;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)renderFrame:(Frame *)frame toLayer:(CAMetalLayer *)layer {
|
||||
|
||||
// Enhanced method that receives timing information from CAMetalDisplayLink
|
||||
- (void)renderFrame:(Frame *)frame withDrawable:(id<CAMetalDrawable>)drawable targetPresentationTimestamp:(CFTimeInterval)targetPresentationTimestamp API_AVAILABLE(ios(17.0)) {
|
||||
@autoreleasepool {
|
||||
if (self.isStopping) {
|
||||
Log(LOG_I, @"[MetalVideoRenderer] isStopping");
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate accurate frametime using CAMetalDisplayLink timing
|
||||
if (self.lastPresented > 0.0f) {
|
||||
CFTimeInterval frametime = targetPresentationTimestamp - self.lastPresented;
|
||||
[[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:(frametime * 1000.0)];
|
||||
}
|
||||
self.lastPresented = targetPresentationTimestamp;
|
||||
|
||||
// Handle changes to the frame's colorspace from last time we rendered
|
||||
BOOL layerDidChange = NO;
|
||||
if (![self updateColorSpaceForFrame:frame toLayer:layer layerDidChange:&layerDidChange]) {
|
||||
if (![self updateColorSpaceForFrame:frame toLayer:(CAMetalLayer *)drawable.layer layerDidChange:&layerDidChange]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle changes to the video size or drawable size
|
||||
if (![self updateVideoRegionSizeForFrame:frame toLayer:layer]) {
|
||||
if (![self updateVideoRegionSizeForFrame:frame toLayer:(CAMetalLayer *)drawable.layer]) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -483,7 +492,7 @@ CFStringRef __currentColorSpace;
|
||||
id<MTLFunction> yuvToLinear = [defaultLibrary newFunctionWithName:@"yuvToLinear"];
|
||||
|
||||
// Determine if this is 10-bit based on the layer's pixel format (after colorspace update)
|
||||
BOOL is10Bit = (layer.pixelFormat == MTLPixelFormatBGR10A2Unorm);
|
||||
BOOL is10Bit = (((CAMetalLayer *)drawable.layer).pixelFormat == MTLPixelFormatBGR10A2Unorm);
|
||||
|
||||
NSString *fragmentShaderName;
|
||||
if (planes == 2) {
|
||||
@@ -492,13 +501,13 @@ CFStringRef __currentColorSpace;
|
||||
fragmentShaderName = is10Bit ? @"ps_draw_triplanar_10bit" : @"ps_draw_triplanar_8bit";
|
||||
}
|
||||
|
||||
pipelineDesc.colorAttachments[0].pixelFormat = layer.pixelFormat;
|
||||
pipelineDesc.colorAttachments[0].pixelFormat = ((CAMetalLayer *)drawable.layer).pixelFormat;
|
||||
pipelineDesc.vertexBuffers[0].mutability = MTLMutabilityImmutable;
|
||||
|
||||
Log(LOG_I, @"Metal pipeline state for %zu planes with pixel format %@",
|
||||
planes, layer.pixelFormat == MTLPixelFormatRGBA16Float ? @"MTLPixelFormatRGBA16Float" : @"MTLPixelFormatBGR10A2Unorm");
|
||||
planes, ((CAMetalLayer *)drawable.layer).pixelFormat == MTLPixelFormatRGBA16Float ? @"MTLPixelFormatRGBA16Float" : @"MTLPixelFormatBGR10A2Unorm");
|
||||
|
||||
if (layer.pixelFormat == MTLPixelFormatRGBA16Float) {
|
||||
if (((CAMetalLayer *)drawable.layer).pixelFormat == MTLPixelFormatRGBA16Float) {
|
||||
// 4:2:0 or 4:4:4 YUV -> BT.2020 RGB -> linear float
|
||||
pipelineDesc.vertexFunction = vertexVsDraw;
|
||||
pipelineDesc.fragmentFunction = yuvToLinear;
|
||||
@@ -554,11 +563,7 @@ CFStringRef __currentColorSpace;
|
||||
}
|
||||
}
|
||||
|
||||
id<CAMetalDrawable> drawable = [layer nextDrawable];
|
||||
if (!drawable) {
|
||||
Log(LOG_E, @"Failed to get nextDrawable");
|
||||
return;
|
||||
}
|
||||
// Use the provided drawable instead of getting nextDrawable from layer
|
||||
_renderPassDescriptor.colorAttachments[0].texture = drawable.texture;
|
||||
|
||||
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
|
||||
@@ -572,7 +577,7 @@ CFStringRef __currentColorSpace;
|
||||
[renderEncoder setVertexBuffer:_VideoVertexBuffer offset:0 atIndex:0];
|
||||
[renderEncoder setFragmentBuffer:_CscParamsBuffer offset:0 atIndex:0];
|
||||
#if !TARGET_OS_TV
|
||||
if (layer.pixelFormat == MTLPixelFormatRGBA16Float) {
|
||||
if (((CAMetalLayer *)drawable.layer).pixelFormat == MTLPixelFormatRGBA16Float) {
|
||||
[self pollCurrentEDRHeadroom];
|
||||
[renderEncoder setFragmentBytes:&_currentEDRHeadroom length:sizeof(float) atIndex:1];
|
||||
}
|
||||
@@ -580,15 +585,6 @@ CFStringRef __currentColorSpace;
|
||||
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
|
||||
[renderEncoder endEncoding];
|
||||
|
||||
__block MetalVideoRenderer *strongSelf = self;
|
||||
[drawable addPresentedHandler:^(id<MTLDrawable> d) {
|
||||
if (strongSelf.lastPresented > 0.0f) {
|
||||
CFTimeInterval frametime = d.presentedTime - strongSelf.lastPresented;
|
||||
[[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:(frametime * 1000.0)];
|
||||
}
|
||||
strongSelf.lastPresented = d.presentedTime;
|
||||
}];
|
||||
|
||||
// signal semaphore, compute GPU time average, and clear textures
|
||||
__block dispatch_semaphore_t block_semaphore = _inFlightSemaphore;
|
||||
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
|
||||
@@ -609,15 +605,10 @@ CFStringRef __currentColorSpace;
|
||||
CVMetalTextureCacheFlush(self->_textureCache, 0);
|
||||
}];
|
||||
|
||||
#if TARGET_OS_SIMULATOR
|
||||
// CAMetalDisplayLink handles timing automatically, so we don't use afterMinimumDuration
|
||||
[commandBuffer presentDrawable:drawable];
|
||||
#else
|
||||
// present for a minimum duration for best frame pacing
|
||||
[commandBuffer presentDrawable:drawable afterMinimumDuration:1.0f / _framerate];
|
||||
#endif
|
||||
|
||||
[commandBuffer commit];
|
||||
[commandBuffer waitUntilCompleted];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user