diff --git a/VoidLink/Metal/MetalViewController.m b/VoidLink/Metal/MetalViewController.m index 6aaf6e3e..852e99fb 100644 --- a/VoidLink/Metal/MetalViewController.m +++ b/VoidLink/Metal/MetalViewController.m @@ -18,6 +18,7 @@ MetalView *_metalView; MetalVideoRenderer *_renderer; MetricsHandler _metricsHandler; + CADisplayLink *_displayLink; } - (nonnull instancetype)initWithFrame:(CGRect)bounds framerate:(float)framerate enableHdr:(BOOL)enableHdr metricsHandler:(MetricsHandler)metricsHandler { @@ -72,6 +73,20 @@ // Initialize the renderer-dependent view properties. view.metalLayer.pixelFormat = renderer.colorPixelFormat; view.metalLayer.maximumDrawableCount = 3; + + // 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 15.0, tvOS 15.0, *)) { + _displayLink.preferredFrameRateRange = CAFrameRateRangeMake(_framerate, _framerate, _framerate); + } else { + _displayLink.preferredFramesPerSecond = _framerate; + } + [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; +} + +- (void)displayLinkHandler:(CADisplayLink *)link { + // Rendering does not use DisplayLink, this exists to fool iOS into keeping us running at the desired framerate } - (void)waitToRenderTo:(nonnull CAMetalLayer *)layer { @@ -102,6 +117,10 @@ Log(LOG_I, @"[MetalViewController] viewDidDisappear"); + if (_displayLink) { + [_displayLink invalidate]; + } + [_renderer shutdown]; _renderer = nil; }