Misc: fix bugs that were preventing starting a new stream after disconnecting. Known issue: this leaks a bit of memory. This also removes all OSX-specific code from the Apple example I used, as I have no intention of using this on Mac.

This commit is contained in:
Andy Grundman authored and Acaki committed 2025-07-25 01:46:49 +08:00
1 parent e57f0cd723
commit e5fc439da2
11 files changed
+71 -152

No files matched your search

+20 -20
View File
@@ -158,6 +158,11 @@ CFStringRef __currentColorSpace;
} }
- (void)dealloc { - (void)dealloc {
Log(LOG_I, @"MetalVideoRenderer dealloc");
if (_commandQueue) {
_commandQueue = nil;
}
if (_CscParamsBuffer) { if (_CscParamsBuffer) {
_CscParamsBuffer = nil; _CscParamsBuffer = nil;
} }
@@ -172,6 +177,9 @@ CFStringRef __currentColorSpace;
if (_renderPassDescriptor) { if (_renderPassDescriptor) {
_renderPassDescriptor = nil; _renderPassDescriptor = nil;
} }
if (_textureCache) {
_textureCache = nil;
}
if (__currentColorSpace) { if (__currentColorSpace) {
CFRelease(__currentColorSpace); CFRelease(__currentColorSpace);
} }
@@ -179,12 +187,7 @@ CFStringRef __currentColorSpace;
#if !TARGET_OS_TV #if !TARGET_OS_TV
- (void)reportMaxEDRHeadroom { - (void)reportMaxEDRHeadroom {
CGFloat maxHeadroom = 1.0f; CGFloat maxHeadroom = [[UIScreen mainScreen] potentialEDRHeadroom];
#if TARGET_OS_OSX
maxHeadroom = [[NSScreen mainScreen] maximumPotentialExtendedDynamicRangeColorComponentValue];
#else
maxHeadroom = [[UIScreen mainScreen] potentialEDRHeadroom];
#endif
if (maxHeadroom > 1.0) { if (maxHeadroom > 1.0) {
LogOnce(LOG_I, @"Display supports EDR with a max headroom of %.1f", maxHeadroom); LogOnce(LOG_I, @"Display supports EDR with a max headroom of %.1f", maxHeadroom);
} else { } else {
@@ -193,12 +196,7 @@ CFStringRef __currentColorSpace;
} }
- (void)pollCurrentEDRHeadroom { - (void)pollCurrentEDRHeadroom {
CGFloat headroom = 1.0f; CGFloat headroom = [[UIScreen mainScreen] currentEDRHeadroom];
#if TARGET_OS_OSX
headroom = [[NSScreen mainScreen] maximumExtendedDynamicRangeColorComponentValue];
#else
headroom = [[UIScreen mainScreen] currentEDRHeadroom];
#endif
if (headroom != _currentEDRHeadroom) { if (headroom != _currentEDRHeadroom) {
Log(LOG_I, @"EDR headroom changed to %.1f", headroom); Log(LOG_I, @"EDR headroom changed to %.1f", headroom);
_currentEDRHeadroom = (float)headroom; _currentEDRHeadroom = (float)headroom;
@@ -448,7 +446,7 @@ CFStringRef __currentColorSpace;
- (void)renderFrame:(Frame *)frame toLayer:(CAMetalLayer *)layer { - (void)renderFrame:(Frame *)frame toLayer:(CAMetalLayer *)layer {
@autoreleasepool { @autoreleasepool {
if (self.isStopping) { if (self.isStopping) {
Log(LOG_I, @"XXX Metal renderThread is stopping. returning from renderFrame"); Log(LOG_I, @"[MetalVideoRenderer] isStopping");
return; return;
} }
@@ -573,10 +571,12 @@ CFStringRef __currentColorSpace;
[renderEncoder setVertexBuffer:_VideoVertexBuffer offset:0 atIndex:0]; [renderEncoder setVertexBuffer:_VideoVertexBuffer offset:0 atIndex:0];
[renderEncoder setFragmentBuffer:_CscParamsBuffer offset:0 atIndex:0]; [renderEncoder setFragmentBuffer:_CscParamsBuffer offset:0 atIndex:0];
#if !TARGET_OS_TV
if (layer.pixelFormat == MTLPixelFormatRGBA16Float) { if (layer.pixelFormat == MTLPixelFormatRGBA16Float) {
[self pollCurrentEDRHeadroom]; [self pollCurrentEDRHeadroom];
[renderEncoder setFragmentBytes:&_currentEDRHeadroom length:sizeof(float) atIndex:1]; [renderEncoder setFragmentBytes:&_currentEDRHeadroom length:sizeof(float) atIndex:1];
} }
#endif
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
[renderEncoder endEncoding]; [renderEncoder endEncoding];
@@ -617,8 +617,6 @@ CFStringRef __currentColorSpace;
#endif #endif
[commandBuffer commit]; [commandBuffer commit];
// Wait for the command buffer to complete and free our CVMetalTextureCache references
[commandBuffer waitUntilCompleted]; [commandBuffer waitUntilCompleted];
} }
} }
@@ -633,12 +631,14 @@ CFStringRef __currentColorSpace;
} }
- (void)shutdown { - (void)shutdown {
Log(LOG_I, @"XXX MetalVideoRenderer shutodwn"); if (!self.isStopping) {
self.isStopping = YES; self.isStopping = YES;
Log(LOG_I, @"[MetalVideoRenderer] shutdown");
// Ensure no rendering is in flight // Ensure no rendering is in flight
for (NSUInteger i = 0; i < MaxFramesInFlight; i++) { for (NSUInteger i = 0; i < MaxFramesInFlight; i++) {
dispatch_semaphore_signal(_inFlightSemaphore); dispatch_semaphore_signal(_inFlightSemaphore);
}
} }
} }
+2 -11
View File
@@ -1,15 +1,8 @@
#import <Metal/Metal.h> #import <Metal/Metal.h>
#import <QuartzCore/CAMetalDisplayLink.h> #import <QuartzCore/CAMetalDisplayLink.h>
#import <QuartzCore/CAMetalLayer.h> #import <QuartzCore/CAMetalLayer.h>
#import "MetalConfig.h"
#if TARGET_OS_IOS || TARGET_OS_TV
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#define PlatformView UIView #import "MetalConfig.h"
#else
#import <AppKit/AppKit.h>
#define PlatformView NSView
#endif
// The protocol to provide resize and redraw callbacks to a delegate. // The protocol to provide resize and redraw callbacks to a delegate.
@protocol MetalViewDelegate <NSObject> @protocol MetalViewDelegate <NSObject>
@@ -17,15 +10,13 @@
- (void)drawableResize:(CGSize)size; - (void)drawableResize:(CGSize)size;
- (void)renderTo:(nonnull CAMetalLayer *)layer; - (void)renderTo:(nonnull CAMetalLayer *)layer;
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer; - (void)waitToRenderTo:(nonnull CAMetalLayer *)layer;
- (void)shutdown;
@end @end
// The Metal game view base class. // The Metal game view base class.
@interface MetalView : PlatformView <CALayerDelegate> @interface MetalView : UIView <CALayerDelegate>
@property (nonatomic, nonnull, readonly) CAMetalLayer *metalLayer; @property (nonatomic, nonnull, readonly) CAMetalLayer *metalLayer;
@property (nonatomic, getter=isPaused) BOOL paused;
@property (nonatomic, nullable) id<MetalViewDelegate> delegate; @property (nonatomic, nullable) id<MetalViewDelegate> delegate;
@property (nonatomic) float framerate; @property (nonatomic) float framerate;
+9 -53
View File
@@ -1,4 +1,4 @@
// This is based on the following Apple example: // This is based on the following Apple example
// https://developer.apple.com/documentation/metal/achieving-smooth-frame-rates-with-a-metal-display-link?language=objc // https://developer.apple.com/documentation/metal/achieving-smooth-frame-rates-with-a-metal-display-link?language=objc
// https://developer.apple.com/wwdc23/10123/ // https://developer.apple.com/wwdc23/10123/
@@ -29,29 +29,23 @@
} }
- (void)initCommon { - (void)initCommon {
#if TARGET_OS_OSX
self.wantsLayer = YES;
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
#endif
_metalLayer = (CAMetalLayer *)self.layer; _metalLayer = (CAMetalLayer *)self.layer;
self.layer.delegate = self; self.layer.delegate = self;
} }
- (void)shutdown { - (void)shutdown {
if (_renderThread) { if (_renderThread) {
Log(LOG_I, @"[MetalView] sending renderThread a cancel message");
[_renderThread cancel]; [_renderThread cancel];
// wait for thread to exist Log(LOG_I, @"[MetalView] waiting on renderThread to finish");
while (!_renderThread.isFinished) { while (!_renderThread.isFinished) {
Log(LOG_I, @"XXX MetalView waiting on renderThread to finish");
usleep(100); usleep(100);
} }
Log(LOG_I, @"[MetalView] renderThread has finished");
_renderThread = nil;
} }
} }
#if TARGET_OS_IOS || TARGET_OS_TV
+ (Class)layerClass { + (Class)layerClass {
return [CAMetalLayer class]; return [CAMetalLayer class];
} }
@@ -59,29 +53,11 @@
- (void)didMoveToWindow { - (void)didMoveToWindow {
[self movedToWindow]; [self movedToWindow];
} }
#else
- (CALayer *)makeBackingLayer {
return [CAMetalLayer layer];
}
- (void)viewDidMoveToWindow {
[self movedToWindow];
}
#endif // END TARGET_OS_IOS || TARGET_OS_TV
- (void)movedToWindow { - (void)movedToWindow {
if (!self.window) { if (!self.window) {
return; Log(LOG_I, @"[MetalView] movedToWindow(nil): shutting down...");
[self shutdown];
// We have been removed
if (_renderThread) {
[_renderThread cancel];
// wait for thread to exist
while (!_renderThread.isFinished) {
Log(LOG_I, @"XXX MetalView waiting on renderThread to finish");
usleep(100);
}
}
return; return;
} }
@@ -93,21 +69,18 @@
[self.delegate renderTo:self.metalLayer]; [self.delegate renderTo:self.metalLayer];
} }
} }
Log(LOG_I, @"XXX Metal renderThread shutting down"); Log(LOG_I, @"[MetalView] renderThread is exiting");
}]; }];
_renderThread.name = @"MetalVideoRenderer"; _renderThread.name = @"MetalVideoRenderer";
_renderThread.qualityOfService = NSQualityOfServiceUserInteractive; _renderThread.qualityOfService = NSQualityOfServiceUserInteractive;
[_renderThread start]; [_renderThread start];
Log(LOG_I, @"[MetalView] started renderThread %@", _renderThread);
// Perform any actions that need to know the size and scale of the drawable. When UIKit calls // Perform any actions that need to know the size and scale of the drawable. When UIKit calls
// didMoveToWindow after the view initialization, this is the first opportunity to notify // didMoveToWindow after the view initialization, this is the first opportunity to notify
// components of the drawable's size. // components of the drawable's size.
#if AUTOMATICALLY_RESIZE #if AUTOMATICALLY_RESIZE
#if TARGET_OS_IOS || TARGET_OS_TV
[self resizeDrawable:self.window.screen.nativeScale]; [self resizeDrawable:self.window.screen.nativeScale];
#else
[self resizeDrawable:self.window.screen.backingScaleFactor];
#endif
#else #else
// Notify the delegate of the default drawable size when the system can calculate it. // Notify the delegate of the default drawable size when the system can calculate it.
CGSize defaultDrawableSize = self.bounds.size; CGSize defaultDrawableSize = self.bounds.size;
@@ -123,7 +96,6 @@
// Override all methods that indicate the view's size has changed. // Override all methods that indicate the view's size has changed.
#if TARGET_OS_IOS || TARGET_OS_TV
- (void)setContentScaleFactor:(CGFloat)contentScaleFactor { - (void)setContentScaleFactor:(CGFloat)contentScaleFactor {
[super setContentScaleFactor:contentScaleFactor]; [super setContentScaleFactor:contentScaleFactor];
[self resizeDrawable:self.window.screen.nativeScale]; [self resizeDrawable:self.window.screen.nativeScale];
@@ -143,22 +115,6 @@
[super setBounds:bounds]; [super setBounds:bounds];
[self resizeDrawable:self.window.screen.nativeScale]; [self resizeDrawable:self.window.screen.nativeScale];
} }
#else
- (void)viewDidChangeBackingProperties {
[super viewDidChangeBackingProperties];
[self resizeDrawable:self.window.screen.backingScaleFactor];
}
- (void)setFrameSize:(NSSize)size {
[super setFrameSize:size];
[self resizeDrawable:self.window.screen.backingScaleFactor];
}
- (void)setBoundsSize:(NSSize)size {
[super setBoundsSize:size];
[self resizeDrawable:self.window.screen.backingScaleFactor];
}
#endif
- (void)resizeDrawable:(CGFloat)scaleFactor { - (void)resizeDrawable:(CGFloat)scaleFactor {
CGSize newSize = self.bounds.size; CGSize newSize = self.bounds.size;
+2 -9
View File
@@ -1,18 +1,11 @@
#import <Metal/Metal.h> #import <Metal/Metal.h>
#import <UIKit/UIKit.h>
#import "FrameQueue.h" #import "FrameQueue.h"
#import "ImGuiRenderer.h" #import "ImGuiRenderer.h"
#import "MetalVideoRenderer.h" #import "MetalVideoRenderer.h"
#import "MetalView.h" #import "MetalView.h"
#if TARGET_OS_IOS || TARGET_OS_TV @interface MetalViewController : UIViewController <MetalViewDelegate>
#import <UIKit/UIKit.h>
#define PlatformViewController UIViewController
#else
#import <AppKit/AppKit.h>
#define PlatformViewController NSViewController
#endif
@interface MetalViewController : PlatformViewController <MetalViewDelegate>
@property (nonatomic) CGRect bounds; @property (nonatomic) CGRect bounds;
+8 -33
View File
@@ -33,6 +33,7 @@ The implementation of the cross-platform game view controller.
- (void)loadView { - (void)loadView {
self.view = [[MetalView alloc] initWithFrame:_bounds]; self.view = [[MetalView alloc] initWithFrame:_bounds];
Log(LOG_I, @"[MetalViewController] created MetalView %@", (MetalView *)self.view);
} }
- (void)viewDidLoad { - (void)viewDidLoad {
@@ -51,7 +52,7 @@ The implementation of the cross-platform game view controller.
id<MTLDevice> device = MTLCreateSystemDefaultDevice(); id<MTLDevice> device = MTLCreateSystemDefaultDevice();
if (!device) { if (!device) {
Log(LOG_E, @"Metal isn't supported on this device."); Log(LOG_E, @"Metal isn't supported on this device.");
self.view = [[PlatformView alloc] initWithFrame:self.view.frame]; self.view = [[UIView alloc] initWithFrame:self.view.frame];
return; return;
} }
view.metalLayer.device = device; view.metalLayer.device = device;
@@ -64,12 +65,12 @@ The implementation of the cross-platform game view controller.
Log(LOG_E, @"The renderer couldn't be initialized."); Log(LOG_E, @"The renderer couldn't be initialized.");
return; return;
} }
self->_renderer = renderer;
Log(LOG_I, @"[MetalViewController] viewDidLoad, created renderer: %@", renderer);
// Initialize the renderer-dependent view properties. // Initialize the renderer-dependent view properties.
view.metalLayer.pixelFormat = renderer.colorPixelFormat; view.metalLayer.pixelFormat = renderer.colorPixelFormat;
view.metalLayer.maximumDrawableCount = 3; view.metalLayer.maximumDrawableCount = 3;
self->_renderer = renderer;
} }
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer { - (void)waitToRenderTo:(nonnull CAMetalLayer *)layer {
@@ -95,46 +96,20 @@ The implementation of the cross-platform game view controller.
[_renderer drawableResize:size]; [_renderer drawableResize:size];
} }
- (void)shutdown {
[_renderer shutdown];
}
- (void)viewDidDisappear:(BOOL)animated { - (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated]; [super viewDidDisappear:animated];
Log(LOG_I, @"XXX MetalViewController viewDidDisappear"); Log(LOG_I, @"[MetalViewController] viewDidDisappear");
[_metalView shutdown]; [_renderer shutdown];
_renderer = nil;
} }
#if TARGET_OS_IOS #if TARGET_OS_IOS
/// Hides the Home indicator button automatically. // Hides the Home indicator button automatically.
- (BOOL)prefersHomeIndicatorAutoHidden { - (BOOL)prefersHomeIndicatorAutoHidden {
return YES; return YES;
} }
#endif #endif
#if TARGET_OS_OSX
/// Makes the view controller the first responder to receive keyboard events.
- (void)viewDidAppear {
[_metalView.window makeFirstResponder:self];
}
/// Receives the keydown events to avoid system beeps.
///
/// The `GameInputKeyboardMouse` class handles keyboard events.
- (void)keyDown:(NSEvent *)event {
// Reference the parameter to avoid an unused parameter warning.
(void)(event);
}
/// Receives the keyup events to avoid system beeps.
///
/// The `GameInputKeyboardMouse` class handles keyboard events.
- (void)keyUp:(NSEvent *)event {
// Reference the parameter to avoid an unused parameter warning.
(void)(event);
}
#endif
@end @end
+1
View File
@@ -24,6 +24,7 @@ struct PlotDef {
- (instancetype _Nonnull)init NS_UNAVAILABLE; - (instancetype _Nonnull)init NS_UNAVAILABLE;
+ (instancetype _Nonnull)new NS_UNAVAILABLE; + (instancetype _Nonnull)new NS_UNAVAILABLE;
- (void)clearData;
- (void)observeFloat:(int)plotId value:(CFTimeInterval)value; - (void)observeFloat:(int)plotId value:(CFTimeInterval)value;
- (void)observeFloatReturnMetrics:(int)plotId value:(CFTimeInterval)value plotMetrics:(PlotMetrics * _Nullable)plotMetrics; - (void)observeFloatReturnMetrics:(int)plotId value:(CFTimeInterval)value plotMetrics:(PlotMetrics * _Nullable)plotMetrics;
+8
View File
@@ -92,4 +92,12 @@
} }
} }
- (void) clearData {
for (int i = 0; i < PlotCount; i++) {
if (_plots[i].buffer) {
[_plots[i].buffer clear];
}
}
}
@end @end
+7 -5
View File
@@ -122,14 +122,13 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
_view = view; _view = view;
_callbacks = callbacks; _callbacks = callbacks;
_streamAspectRatio = aspectRatio; _streamAspectRatio = aspectRatio;
_parameterSetBuffers = [[NSMutableArray alloc] init];
_frameQueue = [FrameQueue sharedInstance];
[_frameQueue start];
_maxRefreshRate = [[UIScreen mainScreen] maximumFramesPerSecond]; _maxRefreshRate = [[UIScreen mainScreen] maximumFramesPerSecond];
_parameterSetBuffers = [[NSMutableArray alloc] init];
DataManager* dataMan = [[DataManager alloc] init]; DataManager* dataMan = [[DataManager alloc] init];
_frameQueue = [FrameQueue sharedInstance];
[_frameQueue start];
[_frameQueue setHighWaterMark:(int)[[dataMan getSettings].frameQueueSize integerValue]]; [_frameQueue setHighWaterMark:(int)[[dataMan getSettings].frameQueueSize integerValue]];
[self reinitializeDisplayLayer]; [self reinitializeDisplayLayer];
@@ -149,6 +148,9 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
self->_videoFormat = videoFormat; self->_videoFormat = videoFormat;
self->_frameRate = frameRate; self->_frameRate = frameRate;
// reset plot data in case we've already used it for a previous renderer
[[ImGuiPlots sharedInstance] clearData];
DataManager* dataMan = [[DataManager alloc] init]; DataManager* dataMan = [[DataManager alloc] init];
if ([[dataMan getSettings].renderingBackend integerValue] == RENDER_AVSB) { if ([[dataMan getSettings].renderingBackend integerValue] == RENDER_AVSB) {
// PACING_MODE_VSYNC: // PACING_MODE_VSYNC:
@@ -349,7 +351,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
} }
- (void)cleanup - (void)cleanup
{ {
[_frameQueue shutdown]; [_frameQueue stop];
if (_renderingBackend == RENDER_AVSB) { if (_renderingBackend == RENDER_AVSB) {
[_displayLink invalidate]; [_displayLink invalidate];
+2 -2
View File
@@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) FloatBuffer *frameDropMetrics; @property (nonatomic) FloatBuffer *frameDropMetrics;
@property (nonatomic) int highWaterMark; @property (nonatomic) int highWaterMark;
@property (nonatomic, readonly) int maxCapacity; @property (nonatomic, readonly) int maxCapacity;
@property (atomic) BOOL isStopping; @property (atomic) BOOL paused;
+ (instancetype)sharedInstance; + (instancetype)sharedInstance;
@@ -27,8 +27,8 @@ NS_ASSUME_NONNULL_BEGIN
- (CFTimeInterval)estimatedFramerate; - (CFTimeInterval)estimatedFramerate;
- (int)currentSoftCap; - (int)currentSoftCap;
- (void)waitForEnqueue; - (void)waitForEnqueue;
- (void)shutdown;
- (void)start; - (void)start;
- (void)stop;
@end @end
+11 -18
View File
@@ -60,7 +60,7 @@
_ptsCorrection = CMTimeMake(0, 90000); _ptsCorrection = CMTimeMake(0, 90000);
_queueSizeHistory = [[FloatBuffer alloc] initWithCapacity:64]; _queueSizeHistory = [[FloatBuffer alloc] initWithCapacity:64];
_lock = OS_UNFAIR_LOCK_INIT; _lock = OS_UNFAIR_LOCK_INIT;
_isStopping = NO; _paused = YES; // caller will call start()
// ring buffer // ring buffer
_capacity = _maxCapacity; _capacity = _maxCapacity;
@@ -226,7 +226,7 @@
// 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.isStopping && [self isEmpty]) { while (!self.paused && [self isEmpty]) {
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)); // 100ms dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)); // 100ms
dispatch_semaphore_wait(_frameSemaphore, timeout); dispatch_semaphore_wait(_frameSemaphore, timeout);
} }
@@ -256,7 +256,7 @@
CFTimeInterval deadline = start + timeout; CFTimeInterval deadline = start + timeout;
int round = 0; int round = 0;
if (self.isStopping) { if (self.paused) {
return nil; return nil;
} }
@@ -287,15 +287,10 @@
return [self count] == 0; return [self count] == 0;
} }
- (void)_clear_unsafe {
_head = _tail = _count = 0;
_frameDropMetrics = [[FloatBuffer alloc] initWithCapacity:512];
}
// The public clear method now safely calls the unsafe version.
- (void)clear { - (void)clear {
os_unfair_lock_lock(&_lock); os_unfair_lock_lock(&_lock);
[self _clear_unsafe]; _head = _tail = _count = 0;
_frameDropMetrics = [[FloatBuffer alloc] initWithCapacity:512];
os_unfair_lock_unlock(&_lock); os_unfair_lock_unlock(&_lock);
} }
@@ -329,19 +324,17 @@
return cap; return cap;
} }
- (void)shutdown { - (void)stop {
// new frames will no longer be coming in, make sure consumer side is not left waiting // new frames will no longer be coming in, make sure consumer side is not left waiting
self.isStopping = YES; self.paused = YES;
Log(LOG_I, @"XXX FrameQueue shutting down"); Log(LOG_I, @"FrameQueue stopped");
dispatch_semaphore_signal(_frameSemaphore); dispatch_semaphore_signal(_frameSemaphore);
} }
- (void)start { - (void)start {
os_unfair_lock_lock(&_lock); // (re)start for a new renderer
[self _clear_unsafe]; // Use the version without the lock self.paused = NO;
self.isStopping = NO; Log(LOG_I, @"FrameQueue started");
os_unfair_lock_unlock(&_lock);
Log(LOG_I, @"FrameQueue (re)started");
} }
// For use with NSLog("%@", franeQueue); // For use with NSLog("%@", franeQueue);
@@ -663,6 +663,7 @@
if ([_settings.renderingBackend intValue] == RENDER_METAL) { if ([_settings.renderingBackend intValue] == RENDER_METAL) {
// Metal view for video // Metal view for video
Log(LOG_I, @"StreamFrameViewController creating MetalViewController");
self.metalViewController = [[MetalViewController alloc] initWithFrame:self.view.bounds self.metalViewController = [[MetalViewController alloc] initWithFrame:self.view.bounds
framerate:[self->_settings.framerate floatValue] framerate:[self->_settings.framerate floatValue]
enableHdr:self->_settings.enableHdr enableHdr:self->_settings.enableHdr
@@ -745,7 +746,6 @@
_inactivityTimer = nil; _inactivityTimer = nil;
} }
if (self.metalViewController) { if (self.metalViewController) {
[self.metalViewController shutdown];
[self.metalViewController.view removeFromSuperview]; [self.metalViewController.view removeFromSuperview];
[self.metalViewController removeFromParentViewController]; [self.metalViewController removeFromParentViewController];
self.metalViewController = nil; self.metalViewController = nil;