From 5903b358ee0ce1923d87e83c265664337e0085e4 Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Sat, 30 Aug 2025 18:54:30 +0800 Subject: [PATCH] - unify pixel format to MTLPixelFormatRGBA16Float - move renderingBackEnd to experimental section, add a few tips for performance mode --- VoidLink/Localization/Localizable.xcstrings | 35 +++++++++++++++++-- VoidLink/Metal/MetalVideoRenderer.h | 5 ++- VoidLink/Metal/MetalVideoRenderer.m | 31 +++++++++------- VoidLink/Metal/MetalViewController.h | 7 ++-- VoidLink/Metal/MetalViewController.m | 11 +++--- .../ViewControllers/SettingsViewController.m | 11 +++--- .../StreamFrameViewController.m | 2 +- 7 files changed, 71 insertions(+), 31 deletions(-) diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index 5618caa5..e574d103 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -3537,6 +3537,35 @@ } } }, + "PerfModeTip" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "- Changing the rendering mode requires restarting the application to ensure a clean start.\n\n- Performance mode is an experimental feature and cannot guarantee a positive impact on the streaming experience.\n\n- HDR under performance mode for devices of limited dynamic range like iPad mini(LCD display) could be broken.\n\n- Tapping “Quit Now” means I acknowledge the above information." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "- 更改渲染模式需要重启应用以确保干净启动。\n\n- 性能模式为实验性功能,无法保证开启后对串流体验产生正向优化。 \n\n- 有限动态范围设备,如采用LCD的iPad mini,开启性能模式后可能无法正常显示HDR画面。\n\n- 点击“立即退出”代表我已知悉以上内容。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "- 更改渲染模式需要重新啟動應用程式以確保乾淨啟動。\n\n- 效能模式為實驗性功能,無法保證開啟後對串流體驗產生正向優化。\n\n- 有限動態範圍裝置,如採用 LCD 的 iPad mini,開啟效能模式後可能無法正常顯示 HDR 畫面。\n\n- 點擊「立即退出」代表我已知悉以上內容。" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "- 更改渲染模式需要重新啟動應用程式以確保乾淨啟動。\n\n- 效能模式為實驗性功能,無法保證開啟後對串流體驗產生正向優化。\n\n- 有限動態範圍裝置,如採用 LCD 的 iPad mini,開啟效能模式後可能無法正常顯示 HDR 畫面。\n\n- 點擊「立即退出」代表我已知悉以上內容。" + } + } + } + }, "Performance" : { "extractionState" : "manual", "localizations" : { @@ -4275,19 +4304,19 @@ "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "需要重启" + "value" : "需要重启App" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "需要重新啟動" + "value" : "需要重新啟動App" } }, "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "需要重新啟動" + "value" : "需要重新啟動App" } } } diff --git a/VoidLink/Metal/MetalVideoRenderer.h b/VoidLink/Metal/MetalVideoRenderer.h index c798df75..f9eaf430 100644 --- a/VoidLink/Metal/MetalVideoRenderer.h +++ b/VoidLink/Metal/MetalVideoRenderer.h @@ -12,6 +12,7 @@ #import "ConnectionCallbacks.h" #import "Frame.h" #import "Plot.h" +#import "TemporarySettings.h" @interface MetalVideoRenderer : NSObject @@ -23,7 +24,9 @@ @property (atomic) BOOL isStopping; @property (nonatomic) BOOL hdrEnabled; -- (nonnull instancetype)initWithMetalDevice:(nonnull id)device drawablePixelFormat:(MTLPixelFormat)drawablePixelFormat framerate:(float)framerate hdrEnabled:(BOOL)hdrEnabled; +- (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)); - (void)drawableResize:(CGSize)drawableSize; diff --git a/VoidLink/Metal/MetalVideoRenderer.m b/VoidLink/Metal/MetalVideoRenderer.m index 750f04d4..54b8ad36 100644 --- a/VoidLink/Metal/MetalVideoRenderer.m +++ b/VoidLink/Metal/MetalVideoRenderer.m @@ -130,21 +130,24 @@ CFStringRef __currentColorSpace; size_t _lastDrawableHeight; id _CscParamsBuffer; id _VideoVertexBuffer; + + CFStringRef _nonFullHdrColorSpace; + MTLPixelFormat _nonFullHdrPixelFormat; // https://developer.apple.com/documentation/metal/synchronizing-cpu-and-gpu-work?language=objc dispatch_semaphore_t _inFlightSemaphore; } -- (instancetype)initWithMetalDevice:(id)device drawablePixelFormat:(MTLPixelFormat)drawablePixelFormat framerate:(float)framerate hdrEnabled:(BOOL)hdrEnabled { +- (instancetype)initWithMetalDevice:(id)device drawablePixelFormat:(MTLPixelFormat)drawablePixelFormat settings:(TemporarySettings* )currentSettings { self = [super init]; if (self) { _sq = dispatch_queue_create("com.moonlight.MetalVideoRenderer", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0)); - _averageGPUTime = (1.0f / framerate) / 2; + _averageGPUTime = (1.0f / currentSettings.framerate.floatValue) / 2; _device = device; _colorPixelFormat = drawablePixelFormat; - _framerate = framerate; - _hdrEnabled = hdrEnabled; + _framerate = currentSettings.framerate.floatValue; + _hdrEnabled = currentSettings.enableHdr; _commandQueue = [_device newCommandQueue]; _currentEDRHeadroom = 1.0f; _lastColorSpace = -1; @@ -170,6 +173,10 @@ CFStringRef __currentColorSpace; _renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0); _renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; + if (@available(iOS 14.0, *)) _nonFullHdrColorSpace = kCGColorSpaceITUR_2100_PQ; + else _nonFullHdrColorSpace = kCGColorSpaceITUR_2020; + _nonFullHdrPixelFormat = MTLPixelFormatRGBA16Float; + // Initialize texture array to NULL for (int i = 0; i < MAX_VIDEO_PLANES; i++) { _cvMetalTextures[i] = NULL; @@ -399,16 +406,12 @@ CFStringRef __currentColorSpace; CFStringRef frame_trc = CFDictionaryGetValue(ext, kCVImageBufferTransferFunctionKey); if (CFEqual(frame_trc, kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ)) { isHDR = YES; - if (@available(iOS 14.0, *)) { - newColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2100_PQ); - } else { - newColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2020); - } - newPixelFormat = MTLPixelFormatBGRA10_XR; + newColorSpace = CGColorSpaceCreateWithName(_nonFullHdrColorSpace); + newPixelFormat = _nonFullHdrPixelFormat; } else { // SDR 2020, I'm not sure it's possible to stream this though - newColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceITUR_2020); - newPixelFormat = MTLPixelFormatBGRA10_XR; + newColorSpace = CGColorSpaceCreateWithName(_nonFullHdrColorSpace); + newPixelFormat = _nonFullHdrPixelFormat; } if (isHDR) { paramBuffer.cscParams = (fullRange ? k_CscParams_Bt2020Full_10bit : k_CscParams_Bt2020Lim_10bit); @@ -494,6 +497,10 @@ CFStringRef __currentColorSpace; _lastColorSpace = colorspace; _lastFullRange = fullRange; } + + + // NSLog(@"layer.pixelFormat %lu", (unsigned long)layer.pixelFormat); + return YES; } diff --git a/VoidLink/Metal/MetalViewController.h b/VoidLink/Metal/MetalViewController.h index 870e2b37..c9264a39 100644 --- a/VoidLink/Metal/MetalViewController.h +++ b/VoidLink/Metal/MetalViewController.h @@ -12,15 +12,14 @@ #import "ImGuiRenderer.h" #import "MetalVideoRenderer.h" #import "MetalView.h" +#import "TemporarySettings.h" @interface MetalViewController : UIViewController @property (nonatomic) CGRect bounds; -- (nonnull instancetype)initWithFrame:(CGRect)bounds - framerate:(float)framerate - enableHdr:(BOOL)enableHdr - metricsHandler:(MetricsHandler _Nonnull)metricsHandler; +- (nonnull instancetype)initWithFrame:(CGRect)bounds framerate:(float)framerate settings:(TemporarySettings* _Nonnull )settings metricsHandler:(MetricsHandler _Nonnull)metricsHandler; + - (void)pauseRendering; - (void)resumeRendering; diff --git a/VoidLink/Metal/MetalViewController.m b/VoidLink/Metal/MetalViewController.m index 089bc84d..2fce5da0 100644 --- a/VoidLink/Metal/MetalViewController.m +++ b/VoidLink/Metal/MetalViewController.m @@ -14,20 +14,20 @@ @implementation MetalViewController { FrameQueue *_frameQueue; float _framerate; - BOOL _enableHdr; + TemporarySettings* _currentSettings; MetalView *_metalView; MetalVideoRenderer *_renderer; MetricsHandler _metricsHandler; CADisplayLink *_displayLink; } -- (nonnull instancetype)initWithFrame:(CGRect)bounds framerate:(float)framerate enableHdr:(BOOL)enableHdr metricsHandler:(MetricsHandler)metricsHandler { +- (nonnull instancetype)initWithFrame:(CGRect)bounds framerate:(float)framerate settings:(TemporarySettings* )settings metricsHandler:(MetricsHandler)metricsHandler { self = [super init]; if (self) { _bounds = bounds; _frameQueue = [FrameQueue sharedInstance]; _framerate = framerate; - _enableHdr = enableHdr; + _currentSettings = settings; _metricsHandler = metricsHandler; } return self; @@ -68,7 +68,7 @@ Log(LOG_W, @"Running on iOS Simulator, using BGRA8Unorm pixel format"); #else // On real devices, check if we should enable HDR - if (_enableHdr) { + if (_currentSettings.enableHdr) { pixelFormat = MTLPixelFormatBGRA10_XR; Log(LOG_I, @"HDR enabled, using BGRA10_XR pixel format"); } else { @@ -80,8 +80,7 @@ // Initialize the renderer. MetalVideoRenderer *renderer = [[MetalVideoRenderer alloc] initWithMetalDevice:device drawablePixelFormat:pixelFormat - framerate:self->_framerate - hdrEnabled:self->_enableHdr]; + settings:_currentSettings]; if (!renderer) { Log(LOG_E, @"The renderer couldn't be initialized."); return; diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index 29ae16f8..c247a2a9 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -42,6 +42,7 @@ MenuSectionView *touchAndControlSection; MenuSectionView *videoSection; MenuSectionView *otherSection; + MenuSectionView *experimentalSection; NSMutableSet* hiddenStacks; } @@ -570,7 +571,6 @@ BOOL isCustomResolution(int resolutionSelected) { [self addSetting:self.pipStack ofId:@"pipStack" withInfoTag:YES withDynamicLabel:NO to:videoSection]; [self addSetting:self.framePacingStack ofId:@"framePacingStack" withInfoTag:YES withDynamicLabel:NO to:videoSection]; [self addSetting:self.frameQueueSizeStack ofId:@"frameQueueSizeStack" withInfoTag:NO withDynamicLabel:YES to:videoSection]; - [self addSetting:self.renderingBackendStack ofId:@"renderingBackendStack" withInfoTag:YES withDynamicLabel:NO to:videoSection]; [videoSection addToParentStack:_parentStack]; [videoSection setExpanded:YES]; @@ -657,13 +657,16 @@ BOOL isCustomResolution(int resolutionSelected) { [otherSection setExpanded:YES]; - MenuSectionView *experimentalSection = [[MenuSectionView alloc] init]; + experimentalSection = [[MenuSectionView alloc] init]; experimentalSection.delegate = self; experimentalSection.sectionTitle = [LocalizationHelper localizedStringForKey:@"Experimental"]; if (@available(iOS 13.0, *)) { [experimentalSection setSectionWithIcon:[UIImage imageNamed:@"flask"] andSize:20]; } [self addSetting:self.touchMoveEventIntervalStack ofId:@"touchMoveEventIntervalStack" withInfoTag:NO withDynamicLabel:YES to:experimentalSection]; + + [self addSetting:self.renderingBackendStack ofId:@"renderingBackendStack" withInfoTag:YES withDynamicLabel:NO to:experimentalSection]; + [experimentalSection addToParentStack:_parentStack]; [experimentalSection setExpanded:YES]; } @@ -1300,6 +1303,7 @@ BOOL isCustomResolution(int resolutionSelected) { - (void)viewDidLoad { //[self updateTheme]; + settingStackWillBeRelocatedToLowestPosition = false; hiddenStacks = [[NSMutableSet alloc] init]; @@ -1710,12 +1714,11 @@ BOOL isCustomResolution(int resolutionSelected) { DataManager* dataMan = [[DataManager alloc] init]; TemporarySettings* currentSettings = [dataMan getSettings]; NSInteger previousBackend = [currentSettings.renderingBackend integerValue]; - // Check if the rendering backend has actually changed if (previousBackend != sender.selectedSegmentIndex) { // Show alert to prompt user to restart the app - NSString *message = [LocalizationHelper localizedStringForKey:@"Rendering mode change requires app restart"]; + NSString *message = [LocalizationHelper localizedStringForKey: sender.selectedSegmentIndex == 1 ? @"PerfModeTip" : @"Rendering mode change requires app restart"]; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Restart Required"] message:message diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index 7479311f..0a29c7bf 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -714,7 +714,7 @@ Log(LOG_I, @"StreamFrameViewController creating MetalViewController"); self.metalViewController = [[MetalViewController alloc] initWithFrame:self.view.bounds framerate:[self->_settings.framerate floatValue] - enableHdr:self->_settings.enableHdr + settings:self->_settings metricsHandler:self.imguiView.metricsHandler]; self.metalViewController.view.userInteractionEnabled = NO; [self addChildViewController:self.metalViewController];