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:
Acaki committed 2025-08-10 20:58:56 +08:00
1 parent 8d05e8e670
commit 628ce0f12e
9 files changed
+147 -46

No files matched your search

+19 -28
View File
@@ -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];
}
}