fix: metal renderer teardown and resume
This commit is contained in:
1 parent
8126173128
commit
686418997b
9 files changed
+216
-58
No files matched your search
@@ -19,5 +19,7 @@
|
|||||||
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer;
|
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer;
|
||||||
- (void)drawableResize:(CGSize)drawableSize;
|
- (void)drawableResize:(CGSize)drawableSize;
|
||||||
- (void)plotFrametime:(CFTimeInterval)presentedTime withPresentTime:(CFTimeInterval)presentTime;
|
- (void)plotFrametime:(CFTimeInterval)presentedTime withPresentTime:(CFTimeInterval)presentTime;
|
||||||
|
- (void)stop;
|
||||||
|
- (void)discardNextDrawable;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -95,8 +95,6 @@ static const NSUInteger MaxFramesInFlight = 3;
|
|||||||
id<MTLBuffer> _VideoVertexBuffer;
|
id<MTLBuffer> _VideoVertexBuffer;
|
||||||
CFTimeInterval _lastPresented;
|
CFTimeInterval _lastPresented;
|
||||||
|
|
||||||
// https://developer.apple.com/documentation/metal/synchronizing-cpu-and-gpu-work?language=objc
|
|
||||||
dispatch_semaphore_t _inFlightSemaphore;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithMetalDevice:(id<MTLDevice>)device drawablePixelFormat:(MTLPixelFormat)drawablePixelFormat framerate:(float)framerate {
|
- (instancetype)initWithMetalDevice:(id<MTLDevice>)device drawablePixelFormat:(MTLPixelFormat)drawablePixelFormat framerate:(float)framerate {
|
||||||
@@ -114,7 +112,6 @@ static const NSUInteger MaxFramesInFlight = 3;
|
|||||||
_lastColorSpace = -1;
|
_lastColorSpace = -1;
|
||||||
_lastFullRange = NO;
|
_lastFullRange = NO;
|
||||||
_lastPresented = 0;
|
_lastPresented = 0;
|
||||||
_inFlightSemaphore = dispatch_semaphore_create(MaxFramesInFlight);
|
|
||||||
|
|
||||||
CFStringRef keys[1] = {kCVMetalTextureUsage};
|
CFStringRef keys[1] = {kCVMetalTextureUsage};
|
||||||
NSUInteger values[1] = {MTLTextureUsageShaderRead};
|
NSUInteger values[1] = {MTLTextureUsageShaderRead};
|
||||||
@@ -131,10 +128,21 @@ static const NSUInteger MaxFramesInFlight = 3;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
if (_CscParamsBuffer) {
|
// Release the Core Foundation texture cache, which is not managed by ARC.
|
||||||
_CscParamsBuffer = nil;
|
if (_textureCache) {
|
||||||
|
CFRelease(_textureCache);
|
||||||
|
_textureCache = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Safely release any textures that might still be referenced.
|
||||||
|
for (int i = 0; i < MAX_VIDEO_PLANES; i++) {
|
||||||
|
if (_cvMetalTextures[i]) {
|
||||||
|
CFRelease(_cvMetalTextures[i]);
|
||||||
|
_cvMetalTextures[i] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARC will handle the rest of the Objective-C objects like _CscParamsBuffer.
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !TARGET_OS_TV
|
#if !TARGET_OS_TV
|
||||||
@@ -453,38 +461,21 @@ static const NSUInteger MaxFramesInFlight = 3;
|
|||||||
id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:_renderPassDescriptor];
|
id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:_renderPassDescriptor];
|
||||||
|
|
||||||
[renderEncoder setRenderPipelineState:_videoPipelineState];
|
[renderEncoder setRenderPipelineState:_videoPipelineState];
|
||||||
|
[renderEncoder setVertexBuffer:_VideoVertexBuffer offset:0 atIndex:0];
|
||||||
|
[renderEncoder setFragmentBuffer:_CscParamsBuffer offset:0 atIndex:0];
|
||||||
|
|
||||||
for (size_t i = 0; i < planes; i++) {
|
for (size_t i = 0; i < planes; i++) {
|
||||||
[renderEncoder setFragmentTexture:CVMetalTextureGetTexture(_cvMetalTextures[i]) atIndex:i];
|
[renderEncoder setFragmentTexture:CVMetalTextureGetTexture(_cvMetalTextures[i]) atIndex:i];
|
||||||
}
|
}
|
||||||
|
|
||||||
__block dispatch_semaphore_t block_semaphore = _inFlightSemaphore;
|
|
||||||
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
|
|
||||||
dispatch_semaphore_signal(block_semaphore);
|
|
||||||
|
|
||||||
const CFTimeInterval GPUTime = cb.GPUEndTime - cb.GPUStartTime;
|
|
||||||
const double alpha = 0.25f;
|
|
||||||
self->_averageGPUTime = (GPUTime * alpha) + (self->_averageGPUTime * (1.0 - alpha));
|
|
||||||
|
|
||||||
// Free textures after completion of rendering
|
|
||||||
for (size_t i = 0; i < planes; i++) {
|
|
||||||
if (self->_cvMetalTextures[i]) {
|
|
||||||
CVBufferRelease(self->_cvMetalTextures[i]);
|
|
||||||
self->_cvMetalTextures[i] = nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CVMetalTextureCacheFlush(self->_textureCache, 0);
|
|
||||||
}];
|
|
||||||
|
|
||||||
[renderEncoder setFragmentBuffer:_CscParamsBuffer offset:0 atIndex:0];
|
|
||||||
[renderEncoder setVertexBuffer:_VideoVertexBuffer offset:0 atIndex:0];
|
|
||||||
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
|
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
|
||||||
[renderEncoder endEncoding];
|
[renderEncoder endEncoding];
|
||||||
|
|
||||||
__weak typeof(self) self_ = self;
|
__weak typeof(self) weakSelf = self;
|
||||||
[_nextDrawable addPresentedHandler:^(id<MTLDrawable> d) {
|
[_nextDrawable addPresentedHandler:^(id<MTLDrawable> d) {
|
||||||
if (self_) {
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||||
[self_ plotFrametime:d.presentedTime];
|
if (strongSelf) {
|
||||||
|
[strongSelf plotFrametime:d.presentedTime];
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
@@ -500,6 +491,19 @@ static const NSUInteger MaxFramesInFlight = 3;
|
|||||||
// Wait for the command buffer to complete and free our CVMetalTextureCache references
|
// Wait for the command buffer to complete and free our CVMetalTextureCache references
|
||||||
[commandBuffer waitUntilCompleted];
|
[commandBuffer waitUntilCompleted];
|
||||||
|
|
||||||
|
// Perform cleanup
|
||||||
|
const CFTimeInterval GPUTime = commandBuffer.GPUEndTime - commandBuffer.GPUStartTime;
|
||||||
|
const double alpha = 0.25f;
|
||||||
|
_averageGPUTime = (GPUTime * alpha) + (_averageGPUTime * (1.0 - alpha));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < planes; i++) {
|
||||||
|
if (_cvMetalTextures[i]) {
|
||||||
|
CVBufferRelease(_cvMetalTextures[i]);
|
||||||
|
_cvMetalTextures[i] = nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CVMetalTextureCacheFlush(_textureCache, 0);
|
||||||
|
|
||||||
_nextDrawable = nil;
|
_nextDrawable = nil;
|
||||||
} }
|
} }
|
||||||
|
|
||||||
@@ -522,7 +526,6 @@ static const NSUInteger MaxFramesInFlight = 3;
|
|||||||
|
|
||||||
// Wait to ensure only `MaxFramesInFlight` number of frames are getting processed
|
// Wait to ensure only `MaxFramesInFlight` number of frames are getting processed
|
||||||
// by any stage in the Metal pipeline (CPU, GPU, Metal, Drivers, etc.).
|
// by any stage in the Metal pipeline (CPU, GPU, Metal, Drivers, etc.).
|
||||||
dispatch_semaphore_wait(_inFlightSemaphore, DISPATCH_TIME_FOREVER);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,4 +538,7 @@ static const NSUInteger MaxFramesInFlight = 3;
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)stop {
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -33,4 +33,8 @@
|
|||||||
- (void)resizeDrawable:(CGFloat)scaleFactor;
|
- (void)resizeDrawable:(CGFloat)scaleFactor;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
- (void)stop;
|
||||||
|
- (void)pause;
|
||||||
|
- (void)resume;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
+58
-27
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
// The flag to indicate that rendering needs to cease on the main thread.
|
// The flag to indicate that rendering needs to cease on the main thread.
|
||||||
BOOL _continueRunLoop;
|
BOOL _continueRunLoop;
|
||||||
|
dispatch_semaphore_t _renderThreadSemaphore;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Initialization and Setup.
|
#pragma mark - Initialization and Setup.
|
||||||
@@ -41,6 +42,7 @@
|
|||||||
_metalLayer = (CAMetalLayer *)self.layer;
|
_metalLayer = (CAMetalLayer *)self.layer;
|
||||||
|
|
||||||
self.layer.delegate = self;
|
self.layer.delegate = self;
|
||||||
|
_renderThreadSemaphore = dispatch_semaphore_create(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if TARGET_OS_IOS || TARGET_OS_TV
|
#if TARGET_OS_IOS || TARGET_OS_TV
|
||||||
@@ -61,37 +63,44 @@
|
|||||||
}
|
}
|
||||||
#endif // END TARGET_OS_IOS || TARGET_OS_TV
|
#endif // END TARGET_OS_IOS || TARGET_OS_TV
|
||||||
|
|
||||||
- (void)movedToWindow {
|
- (void)startRenderThread {
|
||||||
// Protect _continueRunLoop with a `@synchronized` block because it's accessed by the separate
|
|
||||||
// animation thread.
|
|
||||||
@synchronized(self) {
|
@synchronized(self) {
|
||||||
// Stop the animation loop, allowing it to complete if it's in progress.
|
// Don't start a new thread if one is already running
|
||||||
_continueRunLoop = NO;
|
if (_renderThread) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_continueRunLoop = YES;
|
||||||
|
_renderThread = [[NSThread alloc] initWithTarget:self selector:@selector(runThread) object:nil];
|
||||||
|
_renderThread.qualityOfService = NSQualityOfServiceUserInteractive;
|
||||||
|
[_renderThread start];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create and start a secondary NSThread that has another runloop. The NSThread
|
- (void)movedToWindow {
|
||||||
// class calls the 'runThread' method at the start of the secondary thread's execution.
|
if (self.window) {
|
||||||
_renderThread = [[NSThread alloc] initWithTarget:self selector:@selector(runThread) object:nil];
|
// The view was added to a window, so start rendering.
|
||||||
_continueRunLoop = YES;
|
[self resume];
|
||||||
_renderThread.qualityOfService = NSQualityOfServiceUserInteractive;
|
|
||||||
[_renderThread start];
|
|
||||||
|
|
||||||
// Perform any actions that need to know the size and scale of the drawable. When UIKit calls
|
// Notify the delegate of the drawable's size.
|
||||||
// didMoveToWindow after the view initialization, this is the first opportunity to notify
|
CGSize defaultDrawableSize = self.bounds.size;
|
||||||
// components of the drawable's size.
|
defaultDrawableSize.width *= self.layer.contentsScale;
|
||||||
#if AUTOMATICALLY_RESIZE
|
defaultDrawableSize.height *= self.layer.contentsScale;
|
||||||
#if TARGET_OS_IOS || TARGET_OS_TV
|
[self.delegate drawableResize:defaultDrawableSize];
|
||||||
[self resizeDrawable:self.window.screen.nativeScale];
|
} else {
|
||||||
#else
|
// The view was removed from a window, so stop rendering.
|
||||||
[self resizeDrawable:self.window.screen.backingScaleFactor];
|
[self pause];
|
||||||
#endif
|
}
|
||||||
#else
|
}
|
||||||
// Notify the delegate of the default drawable size when the system can calculate it.
|
|
||||||
CGSize defaultDrawableSize = self.bounds.size;
|
- (void)pause {
|
||||||
defaultDrawableSize.width *= self.layer.contentsScale;
|
// Pausing is implemented by simply stopping the render thread.
|
||||||
defaultDrawableSize.height *= self.layer.contentsScale;
|
[self stop];
|
||||||
[self.delegate drawableResize:defaultDrawableSize];
|
}
|
||||||
#endif
|
|
||||||
|
- (void)resume {
|
||||||
|
// Resuming is implemented by starting a new render thread.
|
||||||
|
[self startRenderThread];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)runThread {
|
- (void)runThread {
|
||||||
@@ -118,6 +127,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dispatch_semaphore_signal(self->_renderThreadSemaphore);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Resizing
|
#pragma mark - Resizing
|
||||||
@@ -188,4 +198,25 @@
|
|||||||
}
|
}
|
||||||
#endif // END AUTOMATICALLY_RESIZE
|
#endif // END AUTOMATICALLY_RESIZE
|
||||||
|
|
||||||
|
- (void)stop {
|
||||||
|
@synchronized(self) {
|
||||||
|
// If already stopping, do nothing.
|
||||||
|
if (!_continueRunLoop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_continueRunLoop = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_renderThread && _renderThread != [NSThread currentThread]) {
|
||||||
|
// Wait for the render thread to finish its loop and signal the semaphore.
|
||||||
|
// We use a 1-second timeout to prevent the app from hanging indefinitely
|
||||||
|
// if the thread gets stuck for some reason.
|
||||||
|
long timeoutResult = dispatch_semaphore_wait(_renderThreadSemaphore, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
|
||||||
|
if (timeoutResult != 0) {
|
||||||
|
Log(LOG_E, @"MetalView render thread failed to stop gracefully within 1 second.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderThread = nil;
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
@@ -21,4 +21,8 @@
|
|||||||
enableHdr:(BOOL)enableHdr
|
enableHdr:(BOOL)enableHdr
|
||||||
metricsHandler:(MetricsHandler _Nonnull)metricsHandler;
|
metricsHandler:(MetricsHandler _Nonnull)metricsHandler;
|
||||||
|
|
||||||
|
- (void)stop;
|
||||||
|
- (void)pause;
|
||||||
|
- (void)resume;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -31,6 +31,7 @@ The implementation of the cross-platform game view controller.
|
|||||||
_enableHdr = enableHdr;
|
_enableHdr = enableHdr;
|
||||||
_metricsHandler = metricsHandler;
|
_metricsHandler = metricsHandler;
|
||||||
_stopping = NO;
|
_stopping = NO;
|
||||||
|
[_frameQueue clear];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -86,7 +87,9 @@ The implementation of the cross-platform game view controller.
|
|||||||
[_renderer waitToRenderTo:layer];
|
[_renderer waitToRenderTo:layer];
|
||||||
|
|
||||||
// If we don't have a frame yet, wait on that too
|
// If we don't have a frame yet, wait on that too
|
||||||
[_frameQueue waitForEnqueue];
|
if (!_stopping) {
|
||||||
|
[_frameQueue waitForEnqueue];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,4 +140,51 @@ The implementation of the cross-platform game view controller.
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
- (void)stop {
|
||||||
|
// 1. Signal that we are stopping to prevent new work from starting.
|
||||||
|
_stopping = YES;
|
||||||
|
|
||||||
|
// 2. Invalidate the CAMetalLayer by removing its device.
|
||||||
|
// This should cause any blocking calls like `nextDrawable` on the render thread to fail
|
||||||
|
// and return immediately, breaking the deadlock.
|
||||||
|
if (_metalView) {
|
||||||
|
_metalView.metalLayer.device = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Unblock the render thread from any other potential waiting points.
|
||||||
|
if (_renderer) {
|
||||||
|
[_renderer stop];
|
||||||
|
}
|
||||||
|
[_frameQueue stop];
|
||||||
|
|
||||||
|
// 4. Now that the thread is unblocked, wait for it to finish its execution.
|
||||||
|
if (_metalView) {
|
||||||
|
[_metalView stop];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Once the thread has terminated, it's safe to deallocate all resources.
|
||||||
|
_renderer = nil;
|
||||||
|
_metalView = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)pause {
|
||||||
|
if (_renderer) {
|
||||||
|
// Add this line to discard any stale drawable before pausing the thread.
|
||||||
|
// This forces the renderer to get a fresh one on resume.
|
||||||
|
[_renderer discardNextDrawable];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_metalView) {
|
||||||
|
[_metalView pause];
|
||||||
|
Log(LOG_I, @"Metal rendering paused.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)resume {
|
||||||
|
if (_metalView) {
|
||||||
|
[_metalView resume];
|
||||||
|
Log(LOG_I, @"Metal rendering resumed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -26,6 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
- (CFTimeInterval)estimatedFramerate;
|
- (CFTimeInterval)estimatedFramerate;
|
||||||
- (int)currentSoftCap;
|
- (int)currentSoftCap;
|
||||||
- (void)waitForEnqueue;
|
- (void)waitForEnqueue;
|
||||||
|
- (void)stop;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
int _count;
|
int _count;
|
||||||
|
|
||||||
BOOL _droppedLast;
|
BOOL _droppedLast;
|
||||||
|
BOOL _stopping;
|
||||||
int _framesIn;
|
int _framesIn;
|
||||||
CMTime _ptsCorrection;
|
CMTime _ptsCorrection;
|
||||||
os_unfair_lock _lock;
|
os_unfair_lock _lock;
|
||||||
@@ -75,6 +76,7 @@
|
|||||||
|
|
||||||
// ping estimatedFramerate to set initial last value
|
// ping estimatedFramerate to set initial last value
|
||||||
[self estimatedFramerate];
|
[self estimatedFramerate];
|
||||||
|
_stopping = NO;
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -171,6 +173,10 @@
|
|||||||
// enqueue with simple alternate-drop logic
|
// enqueue with simple alternate-drop logic
|
||||||
- (int)enqueue:(Frame *)frame {
|
- (int)enqueue:(Frame *)frame {
|
||||||
os_unfair_lock_lock(&_lock);
|
os_unfair_lock_lock(&_lock);
|
||||||
|
if (_stopping) {
|
||||||
|
os_unfair_lock_unlock(&_lock);
|
||||||
|
return 0; // Don't enqueue if stopping
|
||||||
|
}
|
||||||
int dropCount = [self _unsafeEnqueue:frame withDropTarget:_highWaterMark];
|
int dropCount = [self _unsafeEnqueue:frame withDropTarget:_highWaterMark];
|
||||||
os_unfair_lock_unlock(&_lock);
|
os_unfair_lock_unlock(&_lock);
|
||||||
return dropCount;
|
return dropCount;
|
||||||
@@ -179,6 +185,10 @@
|
|||||||
// enqueue that is a bit more flexixble, using the same 500ms queue size history method as moonlight-qt.
|
// enqueue that is a bit more flexixble, using the same 500ms queue size history method as moonlight-qt.
|
||||||
- (int)enqueue:(Frame *)frame withSlackSize:(int)slack {
|
- (int)enqueue:(Frame *)frame withSlackSize:(int)slack {
|
||||||
os_unfair_lock_lock(&_lock);
|
os_unfair_lock_lock(&_lock);
|
||||||
|
if (_stopping) {
|
||||||
|
os_unfair_lock_unlock(&_lock);
|
||||||
|
return 0; // Don't enqueue if stopping
|
||||||
|
}
|
||||||
CFTimeInterval now = CACurrentMediaTime();
|
CFTimeInterval now = CACurrentMediaTime();
|
||||||
|
|
||||||
// new data point for queue health
|
// new data point for queue health
|
||||||
@@ -226,7 +236,13 @@
|
|||||||
// Allows the render loop to wait if the queue is empty
|
// Allows the render loop to wait if the queue is empty
|
||||||
- (void)waitForEnqueue {
|
- (void)waitForEnqueue {
|
||||||
while ([self isEmpty]) {
|
while ([self isEmpty]) {
|
||||||
|
if (_stopping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
dispatch_semaphore_wait(_frameSemaphore, DISPATCH_TIME_FOREVER);
|
dispatch_semaphore_wait(_frameSemaphore, DISPATCH_TIME_FOREVER);
|
||||||
|
if (_stopping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,6 +272,9 @@
|
|||||||
|
|
||||||
// Always attempt to dequeue at least once
|
// Always attempt to dequeue at least once
|
||||||
do {
|
do {
|
||||||
|
if (_stopping) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
if (round > 0) {
|
if (round > 0) {
|
||||||
usleep(100); // 0.1ms
|
usleep(100); // 0.1ms
|
||||||
}
|
}
|
||||||
@@ -285,6 +304,7 @@
|
|||||||
os_unfair_lock_lock(&_lock);
|
os_unfair_lock_lock(&_lock);
|
||||||
_head = _tail = _count = 0;
|
_head = _tail = _count = 0;
|
||||||
_frameDropMetrics = [[FloatBuffer alloc] initWithCapacity:512];
|
_frameDropMetrics = [[FloatBuffer alloc] initWithCapacity:512];
|
||||||
|
_stopping = NO;
|
||||||
os_unfair_lock_unlock(&_lock);
|
os_unfair_lock_unlock(&_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,4 +347,29 @@
|
|||||||
return [NSString stringWithFormat:@"[%@]", [parts componentsJoinedByString:@",\n"]];
|
return [NSString stringWithFormat:@"[%@]", [parts componentsJoinedByString:@",\n"]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)stop {
|
||||||
|
os_unfair_lock_lock(&_lock);
|
||||||
|
// Prevent redundant calls
|
||||||
|
if (_stopping) {
|
||||||
|
os_unfair_lock_unlock(&_lock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_stopping = YES;
|
||||||
|
|
||||||
|
// Clear the buffer's contents
|
||||||
|
_head = _tail = _count = 0;
|
||||||
|
for (int i = 0; i < _capacity; i++) {
|
||||||
|
[_buffer replaceObjectAtIndex:i withObject:[NSNull null]];
|
||||||
|
}
|
||||||
|
|
||||||
|
os_unfair_lock_unlock(&_lock);
|
||||||
|
|
||||||
|
// Unblock any threads waiting on the semaphore.
|
||||||
|
// Signaling multiple times ensures all potential consumer threads are released.
|
||||||
|
for (int i = 0; i < _maxCapacity; i++) {
|
||||||
|
dispatch_semaphore_signal(_frameSemaphore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -668,7 +668,9 @@
|
|||||||
enableHdr:self->_settings.enableHdr
|
enableHdr:self->_settings.enableHdr
|
||||||
metricsHandler:self.imguiView.metricsHandler];
|
metricsHandler:self.imguiView.metricsHandler];
|
||||||
self.metalViewController.view.userInteractionEnabled = NO;
|
self.metalViewController.view.userInteractionEnabled = NO;
|
||||||
|
[self addChildViewController:self.metalViewController];
|
||||||
[self.view addSubview:self.metalViewController.view];
|
[self.view addSubview:self.metalViewController.view];
|
||||||
|
[self.metalViewController didMoveToParentViewController:self];
|
||||||
[self.view bringSubviewToFront:self.metalViewController.view];
|
[self.view bringSubviewToFront:self.metalViewController.view];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -742,6 +744,12 @@
|
|||||||
[_inactivityTimer invalidate];
|
[_inactivityTimer invalidate];
|
||||||
_inactivityTimer = nil;
|
_inactivityTimer = nil;
|
||||||
}
|
}
|
||||||
|
if (self.metalViewController) {
|
||||||
|
[self.metalViewController stop];
|
||||||
|
[self.metalViewController.view removeFromSuperview];
|
||||||
|
self.metalViewController = nil;
|
||||||
|
NSLog(@"Metal renderer stopped and cleaned up.");
|
||||||
|
}
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -918,6 +926,10 @@
|
|||||||
|
|
||||||
// This will fire if the user opens control center or gets a low battery message
|
// This will fire if the user opens control center or gets a low battery message
|
||||||
- (void)applicationWillResignActive:(NSNotification *)notification {
|
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||||
|
if (self.metalViewController) {
|
||||||
|
[self.metalViewController pause];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//[self.pipController startPictureInPicture];
|
//[self.pipController startPictureInPicture];
|
||||||
//sleep(1);
|
//sleep(1);
|
||||||
@@ -935,6 +947,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||||
|
if (self.metalViewController) {
|
||||||
|
[self.metalViewController resume];
|
||||||
|
}
|
||||||
// Stop the background timer, since we're foregrounded again
|
// Stop the background timer, since we're foregrounded again
|
||||||
if (_inactivityTimer != nil) {
|
if (_inactivityTimer != nil) {
|
||||||
Log(LOG_I, @"Stopping inactivity timer after becoming active again");
|
Log(LOG_I, @"Stopping inactivity timer after becoming active again");
|
||||||
|
|||||||
Reference in new issue
Block a user