From 0b9454896cc4081bbe6bedbbb1d7afb9610261a9 Mon Sep 17 00:00:00 2001 From: Acaki Date: Tue, 19 Aug 2025 00:07:23 +0800 Subject: [PATCH] Revert "feat: add proper CADisplayLink handling for iOS15 where commandBuffer presentDrawable:drawable afterMinimumDuration doesn't seem to work well" This reverts commit ee67ff56c78d3ba72d9a2d80824080bbfe3492ec. --- VoidLink/Metal/MetalVideoRenderer.h | 9 --- VoidLink/Metal/MetalVideoRenderer.m | 65 ++----------------- VoidLink/Metal/MetalViewController.h | 3 - VoidLink/Metal/MetalViewController.m | 52 ++------------- .../StreamFrameViewController.m | 6 -- 5 files changed, 9 insertions(+), 126 deletions(-) diff --git a/VoidLink/Metal/MetalVideoRenderer.h b/VoidLink/Metal/MetalVideoRenderer.h index 19e6f4d1..1c35c786 100644 --- a/VoidLink/Metal/MetalVideoRenderer.h +++ b/VoidLink/Metal/MetalVideoRenderer.h @@ -22,21 +22,12 @@ @property (nonatomic) id _Nullable nextDrawable; @property (atomic) BOOL isStopping; -// iOS 15.0 and below frame timing properties -@property (nonatomic) BOOL useLegacyFrameTiming; -@property (nonatomic) CFTimeInterval lastDisplayLinkTime; -@property (nonatomic) CFTimeInterval displayLinkFrametime; - - (nonnull instancetype)initWithMetalDevice:(nonnull id)device drawablePixelFormat:(MTLPixelFormat)drawablePixelFormat framerate:(float)framerate; - (void)renderFrame:(nonnull Frame *)frame toLayer:(nonnull CAMetalLayer *)layer API_AVAILABLE(ios(13.0)); - (void)waitToRenderTo:(nonnull CAMetalLayer *)layer API_AVAILABLE(ios(13.0)); - (void)drawableResize:(CGSize)drawableSize; - (void)shutdown; -// iOS 15.0 and below legacy timing support -- (void)updateLegacyFrameTiming:(CFTimeInterval)displayLinkTimestamp; -- (void)resetFrameTiming; - + (NSString *_Nullable)currentColorSpace; @end diff --git a/VoidLink/Metal/MetalVideoRenderer.m b/VoidLink/Metal/MetalVideoRenderer.m index 4787c50a..d2347014 100644 --- a/VoidLink/Metal/MetalVideoRenderer.m +++ b/VoidLink/Metal/MetalVideoRenderer.m @@ -157,17 +157,6 @@ CFStringRef __currentColorSpace; _lastPresented = 0.0f; _inFlightSemaphore = dispatch_semaphore_create(MaxFramesInFlight); _isStopping = NO; - - // Enable legacy frame timing for iOS 15.0 and below to fix timing issues - if (@available(iOS 16.0, *)) { - _useLegacyFrameTiming = NO; - Log(LOG_I, @"MetalVideoRenderer: Using iOS 16+ timing (Metal presentedTime)"); - } else { - _useLegacyFrameTiming = YES; - _lastDisplayLinkTime = 0.0f; - _displayLinkFrametime = 1.0f / framerate * 1000.0f; // Expected frametime in ms - Log(LOG_I, @"MetalVideoRenderer: Using legacy timing for iOS 15.0 and below (CADisplayLink)"); - } CFStringRef keys[1] = {kCVMetalTextureUsage}; NSUInteger values[1] = {MTLTextureUsageShaderRead}; @@ -724,21 +713,14 @@ CFStringRef __currentColorSpace; [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; [renderEncoder endEncoding]; -#if !TARGET_OS_SIMULATOR __block MetalVideoRenderer *strongSelf = self; +#if !TARGET_OS_SIMULATOR [drawable addPresentedHandler:^(id d) { - // Only use Metal's presentedTime on iOS 16+ due to timing bugs on iOS 15.0 - if (!strongSelf.useLegacyFrameTiming) { - if (strongSelf.lastPresented > 0.0f) { - CFTimeInterval frametime = d.presentedTime - strongSelf.lastPresented; - // Sanity check: reject negative or impossibly large frametimes - if (frametime > 0 && frametime < 0.1) { // Less than 100ms - [[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:(frametime * 1000.0)]; - } - } - strongSelf.lastPresented = d.presentedTime; + if (strongSelf.lastPresented > 0.0f) { + CFTimeInterval frametime = d.presentedTime - strongSelf.lastPresented; + [[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:(frametime * 1000.0)]; } - // For iOS 15.0 and below, frametime is calculated in updateLegacyFrameTiming + strongSelf.lastPresented = d.presentedTime; }]; #endif @@ -795,43 +777,6 @@ CFStringRef __currentColorSpace; } } -#pragma mark - iOS 15.0 and below legacy timing support - -- (void)updateLegacyFrameTiming:(CFTimeInterval)displayLinkTimestamp { - if (!_useLegacyFrameTiming) { - return; // Only for iOS 15.0 and below - } - - if (_lastDisplayLinkTime > 0.0f) { - CFTimeInterval actualFrametime = (displayLinkTimestamp - _lastDisplayLinkTime) * 1000.0; // Convert to ms - - // Sanity check: reject negative or impossibly large frametimes - if (actualFrametime > 0 && actualFrametime < 100.0) { // Less than 100ms - [[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:actualFrametime]; - _displayLinkFrametime = actualFrametime; - } else { - // Use expected frametime if actual is unreliable - Log(LOG_W, @"MetalVideoRenderer: Rejecting invalid frametime %.2f ms, using expected %.2f ms", actualFrametime, _displayLinkFrametime); - [[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:_displayLinkFrametime]; - } - } - - _lastDisplayLinkTime = displayLinkTimestamp; -} - -- (void)resetFrameTiming { - if (_useLegacyFrameTiming) { - // Reset legacy timing state for iOS 15.0 and below - _lastDisplayLinkTime = 0.0f; - _displayLinkFrametime = 1.0f / _framerate * 1000.0f; - Log(LOG_I, @"MetalVideoRenderer: Reset legacy frame timing for iOS 15.0 and below"); - } else { - // Reset Metal timing state for iOS 16+ - _lastPresented = 0.0f; - Log(LOG_I, @"MetalVideoRenderer: Reset Metal frame timing for iOS 16+"); - } -} - /// Responds to the drawable's size or orientation changes. - (void)drawableResize:(CGSize)drawableSize { [self resize:drawableSize]; diff --git a/VoidLink/Metal/MetalViewController.h b/VoidLink/Metal/MetalViewController.h index f06ebea3..29e51cdc 100644 --- a/VoidLink/Metal/MetalViewController.h +++ b/VoidLink/Metal/MetalViewController.h @@ -22,7 +22,4 @@ enableHdr:(BOOL)enableHdr metricsHandler:(MetricsHandler _Nonnull)metricsHandler; -// Reset frame timing for iOS 15.0 and below compatibility -- (void)resetFrameTiming; - @end diff --git a/VoidLink/Metal/MetalViewController.m b/VoidLink/Metal/MetalViewController.m index 73ce2983..e5e6ffb4 100644 --- a/VoidLink/Metal/MetalViewController.m +++ b/VoidLink/Metal/MetalViewController.m @@ -92,35 +92,19 @@ view.metalLayer.pixelFormat = renderer.colorPixelFormat; view.metalLayer.maximumDrawableCount = 3; - // We need a displaylink timer for iOS 15.0 and below to provide accurate frame timing, - // or just as a no-op for iOS 16+ to prevent iOS from dropping to 60fps + // We need a no-op displaylink timer or iOS can decide to run at 60fps + // The overhead from this should be minimal. _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkHandler:)]; - if (@available(iOS 16.0, tvOS 16.0, *)) { - // iOS 16+: Use CAFrameRateRange, displaylink is just a no-op + if (@available(iOS 15.0, tvOS 15.0, *)) { _displayLink.preferredFrameRateRange = CAFrameRateRangeMake(_framerate, _framerate, _framerate); - Log(LOG_I, @"MetalViewController: DisplayLink using iOS 16+ CAFrameRateRange"); - } else if (@available(iOS 15.0, tvOS 15.0, *)) { - // iOS 15.0: Use legacy preferredFramesPerSecond to avoid CAFrameRateRange bugs - _displayLink.preferredFramesPerSecond = _framerate; - Log(LOG_I, @"MetalViewController: DisplayLink using iOS 15.0 legacy preferredFramesPerSecond"); } else { - // iOS 14 and below _displayLink.preferredFramesPerSecond = _framerate; } [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; } - (void)displayLinkHandler:(CADisplayLink *)link { - // For iOS 16+: This is just a no-op to fool iOS into keeping us running at the desired framerate - // For iOS 15.0 and below: Update renderer with accurate frame timing to fix negative framerates - if (@available(iOS 16.0, *)) { - // No-op for iOS 16+, Metal's presentedTime is reliable - } else { - // iOS 15.0 and below: Use DisplayLink timing since Metal's presentedTime is unreliable - if (_renderer) { - [_renderer updateLegacyFrameTiming:link.timestamp]; - } - } + // Rendering does not use DisplayLink, this exists to fool iOS into keeping us running at the desired framerate } - (void)waitToRenderTo:(nonnull CAMetalLayer *)layer { @@ -170,32 +154,4 @@ } #endif -- (void)resetFrameTiming { - // Reset the renderer's frame timing state for iOS 15.0 and below compatibility - if (_renderer) { - [_renderer resetFrameTiming]; - Log(LOG_I, @"MetalViewController: Reset frame timing called"); - } - - // Reset DisplayLink timing for iOS 15.0 and below - if (@available(iOS 16.0, *)) { - // No additional DisplayLink reset needed for iOS 16+ - } else { - // For iOS 15.0 and below, pause and resume DisplayLink to re-engage timing - if (_displayLink) { - _displayLink.paused = YES; - - // Re-apply frame rate (avoid CAFrameRateRange on iOS 15.0) - if (@available(iOS 15.0, *)) { - _displayLink.preferredFramesPerSecond = _framerate; - } else { - _displayLink.preferredFramesPerSecond = _framerate; - } - - _displayLink.paused = NO; - Log(LOG_I, @"MetalViewController: Reset DisplayLink timing for iOS 15.0 and below"); - } - } -} - @end diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index cf9138f4..3d46e3f0 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -1043,12 +1043,6 @@ } [self->_streamMan.videoRenderer resetFramePacing]; - - // Also reset Metal renderer timing for iOS 15.0 and below compatibility - if (self.metalViewController) { - [self.metalViewController resetFrameTiming]; - } - _isRestoringFromPiP = NO; }