diff --git a/VoidLink/Metal/MetalVideoRenderer.h b/VoidLink/Metal/MetalVideoRenderer.h index f9eaf430..33006bdb 100644 --- a/VoidLink/Metal/MetalVideoRenderer.h +++ b/VoidLink/Metal/MetalVideoRenderer.h @@ -23,12 +23,13 @@ @property (nonatomic) id _Nullable nextDrawable; @property (atomic) BOOL isStopping; @property (nonatomic) BOOL hdrEnabled; +@property (nonatomic, readonly, nonnull) dispatch_semaphore_t inFlightSemaphore; - (instancetype _Nonnull )initWithMetalDevice:(id_Nonnull)device drawablePixelFormat:(MTLPixelFormat)drawablePixelFormat settings:(TemporarySettings* _Nonnull )currentSettings; - (void)renderFrame:(nonnull Frame *)frame toLayer:(nonnull CAMetalLayer *)layer; -- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer API_AVAILABLE(ios(13.0)); +- (BOOL)waitToRenderTo:(nonnull CAMetalLayer *)layer API_AVAILABLE(ios(13.0)); - (void)drawableResize:(CGSize)drawableSize; - (void)shutdown; diff --git a/VoidLink/Metal/MetalVideoRenderer.m b/VoidLink/Metal/MetalVideoRenderer.m index 54b8ad36..82c96a4e 100644 --- a/VoidLink/Metal/MetalVideoRenderer.m +++ b/VoidLink/Metal/MetalVideoRenderer.m @@ -138,6 +138,8 @@ CFStringRef __currentColorSpace; dispatch_semaphore_t _inFlightSemaphore; } +@synthesize inFlightSemaphore = _inFlightSemaphore; + - (instancetype)initWithMetalDevice:(id)device drawablePixelFormat:(MTLPixelFormat)drawablePixelFormat settings:(TemporarySettings* )currentSettings { self = [super init]; if (self) { @@ -842,13 +844,20 @@ CFStringRef __currentColorSpace; } } -- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer { +- (BOOL)waitToRenderTo:(nonnull CAMetalLayer *)layer { // Wait to ensure only `MaxFramesInFlight` number of frames are getting processed // by any stage in the Metal pipeline (CPU, GPU, Metal, Drivers, etc.). - if (!self.isStopping) { - dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)); // 100ms - dispatch_semaphore_wait(_inFlightSemaphore, timeout); + if (self.isStopping) { + return NO; } + + dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)); // 100ms + if (dispatch_semaphore_wait(_inFlightSemaphore, timeout) != 0) { + Log(LOG_W, @"Timed out waiting for in-flight frame buffer."); + return NO; + } + + return YES; } - (void)shutdown { diff --git a/VoidLink/Metal/MetalViewController.m b/VoidLink/Metal/MetalViewController.m index 2fce5da0..2c8f0a9d 100644 --- a/VoidLink/Metal/MetalViewController.m +++ b/VoidLink/Metal/MetalViewController.m @@ -19,6 +19,7 @@ MetalVideoRenderer *_renderer; MetricsHandler _metricsHandler; CADisplayLink *_displayLink; + BOOL _frameSlotAcquired; } - (nonnull instancetype)initWithFrame:(CGRect)bounds framerate:(float)framerate settings:(TemporarySettings* )settings metricsHandler:(MetricsHandler)metricsHandler { @@ -113,17 +114,20 @@ return; } - // Renderer obtains a nextDrawable, waiting if necessary if (@available(iOS 13.0, *)) { - [_renderer waitToRenderTo:layer]; + _frameSlotAcquired = [_renderer waitToRenderTo:layer]; } - // If we don't have a frame yet, wait on that too - [_frameQueue waitForEnqueue]; + if (_frameSlotAcquired) { + [_frameQueue waitForEnqueue]; + } } /// Draw frame (used by manual loop) - (void)renderTo:(nonnull CAMetalLayer *)layer { + if (!_frameSlotAcquired) { + return; + } CFTimeInterval timeout = (1.0f / _framerate) - _renderer.averageGPUTime; Frame *frame = [_frameQueue dequeueWithTimeout:timeout]; @@ -133,10 +137,13 @@ //if (@available(iOS 13.0, *)) { [_renderer renderFrame:frame toLayer:layer]; //} + } else { + dispatch_semaphore_signal([_renderer inFlightSemaphore]); } } else { // When paused, we still dequeue frames to prevent accumulation // but don't render them. Also sleep a bit to reduce CPU usage + dispatch_semaphore_signal([_renderer inFlightSemaphore]); usleep(100000); } }