Merge commit '6879bf63791c3422fd88095237bee508524b6959' into frameInterpolation
# Conflicts: # VoidLink/Localization/Localizable.xcstrings # VoidLink/Stream/VideoDecoderRenderer.m
This commit is contained in:
commit
b0fd0222df
13 files changed
+343
-79
No files matched your search
@@ -5090,6 +5090,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Apply" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "应用"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "套用"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-HK" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "套用"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Arrow direction pad" : {
|
"Arrow direction pad" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -8208,6 +8231,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Enable Metal Renderer?" : {
|
||||||
|
"extractionState" : "stale",
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "启用 Metal 渲染?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "啟用 Metal 渲染?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-HK" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "啟用 Metal 渲染?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"enableOswForNativeTouchSwitchTip" : {
|
"enableOswForNativeTouchSwitchTip" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -10923,6 +10969,98 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Interpolation failed for an unknown reason." : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "未知原因导致的插帧失败"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "未知原因導致插幀失敗"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-HK" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "未知原因導致插幀失敗"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Interpolation overhead is too high. Falling back to normal mode." : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "插帧性能开销过大,回退普通模式"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "插幀效能開銷過大,回退至一般模式"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-HK" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "插幀效能開銷過大,回退至一般模式"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Interpolator error:" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "插帧器错误:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "插幀器錯誤:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-HK" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "插幀器錯誤:"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Interpolator started." : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "插帧器已启动。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "插幀器已啟動。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-HK" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "插幀器已啟動。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Interval of Multi-Touch Move Events: %d μs" : {
|
"Interval of Multi-Touch Move Events: %d μs" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -12233,25 +12371,25 @@
|
|||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "\nHold up! \n\n- Standard render has better performance on most devices now.\n\n- Changing the rendering mode requires restarting the application to ensure a clean start.\n\n- Metal rendering is an experimental feature that may cause performance degradation on some devices.\n\n- Metal render currently is unable to handle HDR content correctly on most devices.\n\n- Metal rendering mode does not support PiP.\n\n- Tapping “Quit Now” means I acknowledge the above information."
|
"value" : "\nHold up! \n\n- Standard render has better performance on most devices now.\n\n- The change takes effect the next time you start a stream. No app restart is needed.\n\n- Metal rendering is an experimental feature that may cause performance degradation on some devices.\n\n- Metal render currently is unable to handle HDR content correctly on most devices.\n\n- Metal rendering mode does not support PiP.\n\n- Tapping “Apply” means I acknowledge the above information."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "\n等等!\n\n- 标准渲染模式目前在大多数设备上具有更好的性能。\n\n- 切换渲染模式需要重启应用程序,以确保一次干净的启动。\n\n- Metal 渲染是一项实验性功能,在某些设备上可能会导致性能下降。\n\n- Metal 渲染模式下不支持画中画。\n\n- 目前 Metal 渲染器在大多数设备上无法正确处理 HDR 内容。\n\n点击“立即退出”表示我已知晓并认可以上信息。"
|
"value" : "\n等等!\n\n- 标准渲染模式目前在大多数设备上具有更好的性能。\n\n- 渲染模式的更改将在下次开始串流时生效,无需重启应用程序。\n\n- Metal 渲染是一项实验性功能,在某些设备上可能会导致性能下降。\n\n- Metal 渲染模式下不支持画中画。\n\n- 目前 Metal 渲染器在大多数设备上无法正确处理 HDR 内容。\n\n点击“应用”表示我已知晓并认可以上信息。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-Hant" : {
|
"zh-Hant" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "\n等等!\n\n- 目前在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 变更渲染模式需要重新启动应用程式,以确保干净的启动流程。\n\n- Metal 渲染为实验性功能,可能在某些装置上造成效能下降。\n\n- Metal 渲染模式下不支援画中画。\n\n- Metal 渲染器目前在大多数装置上无法正确处理 HDR 内容。\n\n点选「立即结束」表示我已了解并同意上述说明。"
|
"value" : "\n等等!\n\n- 目前在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 变更渲染模式将在下次开始串流时生效,无需重新启动应用程式。\n\n- Metal 渲染为实验性功能,可能在某些装置上造成效能下降。\n\n- Metal 渲染模式下不支援画中画。\n\n- Metal 渲染器目前在大多数装置上无法正确处理 HDR 内容。\n\n点选「套用」表示我已了解并同意上述说明。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-HK" : {
|
"zh-HK" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "\n等等!\n\n- 目前在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 变更渲染模式需要重新启动应用程式,以确保干净的启动流程。\n\n- Metal 渲染为实验性功能,可能在某些装置上造成效能下降。\n\n- Metal 渲染模式下不支援画中画。\n\n- Metal 渲染器目前在大多数装置上无法正确处理 HDR 内容。\n\n点选「立即结束」表示我已了解并同意上述说明。"
|
"value" : "\n等等!\n\n- 目前在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 变更渲染模式将在下次开始串流时生效,无需重新启动应用程式。\n\n- Metal 渲染为实验性功能,可能在某些装置上造成效能下降。\n\n- Metal 渲染模式下不支援画中画。\n\n- Metal 渲染器目前在大多数装置上无法正确处理 HDR 内容。\n\n点选「套用」表示我已了解并同意上述说明。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15659,25 +15797,25 @@
|
|||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "- Standard render has better performance on most devices now.\n\n- Changing the rendering mode requires restarting the application to ensure a clean start.\n\n- Metal rendering is an experimental feature that may cause performance degradation on some devices.\n\n- Metal render currently is unable to handle HDR content correctly on most devices.\n\n- Metal rendering mode does not support PiP."
|
"value" : "- Standard render has better performance on most devices now.\n\n- Changing the rendering mode takes effect the next time you start a stream.\n\n- Metal rendering is an experimental feature that may cause performance degradation on some devices.\n\n- Metal render currently is unable to handle HDR content correctly on most devices.\n\n- Metal rendering mode does not support PiP."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "- 目前, 标准渲染模式在大多数设备上具有更好的性能。\n\n- 切换渲染模式需要重启应用程序,以确保一次干净的启动。\n\n- Metal 渲染是一项实验性功能,在某些设备上可能会导致性能下降。\n\n- Metal 渲染模式下不支持画中画。\n\n- 目前 Metal 渲染器在大多数设备上无法正确处理 HDR 内容。\n"
|
"value" : "- 目前, 标准渲染模式在大多数设备上具有更好的性能。\n\n- 渲染模式的更改将在下次开始串流时生效。\n\n- Metal 渲染是一项实验性功能,在某些设备上可能会导致性能下降。\n\n- Metal 渲染模式下不支持画中画。\n\n- 目前 Metal 渲染器在大多数设备上无法正确处理 HDR 内容。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-Hant" : {
|
"zh-Hant" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "- 目前,在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 变更渲染模式需要重新启动应用程式,以确保干净的启动流程。\n\n- Metal 渲染为实验性功能,可能在某些装置上造成效能下降。\n\n- Metal 渲染模式下不支援画中画。\n\n- Metal 渲染器目前在大多数装置上无法正确处理 HDR 内容。"
|
"value" : "- 目前,在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 变更渲染模式将在下次开始串流时生效。\n\n- Metal 渲染为实验性功能,可能在某些装置上造成效能下降。\n\n- Metal 渲染模式下不支援画中画。\n\n- Metal 渲染器目前在大多数装置上无法正确处理 HDR 内容。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-HK" : {
|
"zh-HK" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "- 目前,在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 变更渲染模式需要重新启动应用程式,以确保干净的启动流程。\n\n- Metal 渲染为实验性功能,可能在某些装置上造成效能下降。\n\n- Metal 渲染模式下不支援画中画。\n\n- Metal 渲染器目前在大多数装置上无法正确处理 HDR 内容。"
|
"value" : "- 目前,在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 变更渲染模式将在下次开始串流时生效。\n\n- Metal 渲染为实验性功能,可能在某些装置上造成效能下降。\n\n- Metal 渲染模式下不支援画中画。\n\n- Metal 渲染器目前在大多数装置上无法正确处理 HDR 内容。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17613,25 +17751,25 @@
|
|||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "\n- Standard render has better performance on most devices now.\n\n- Changing the rendering mode requires restarting the application to ensure a clean start."
|
"value" : "\n- Standard render has better performance on most devices now.\n\n- The change takes effect the next time you start a stream."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "\n- 标准渲染模式目前在大多数设备上具有更好的性能。\n\n- 更改渲染模式需要重启应用以确保干净启动。您是否要立即退出应用?"
|
"value" : "\n- 目前, 标准渲染模式在大多数设备上具有更好的性能。\n\n- 渲染模式的更改将在下次开始串流时生效。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-Hant" : {
|
"zh-Hant" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "\n- 目前在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 更改渲染模式需要重新啟動應用程式以確保乾淨啟動。您是否要立即退出應用程式?"
|
"value" : "\n- 目前,在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 变更渲染模式将在下次开始串流时生效。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-HK" : {
|
"zh-HK" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "\n- 目前在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 更改渲染模式需要重新啟動應用程式以確保乾淨啟動。您是否要立即退出應用程式?"
|
"value" : "\n- 目前,在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 变更渲染模式将在下次开始串流时生效。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,17 @@ static const NSUInteger MaxFramesInFlight = 3;
|
|||||||
// best to tone-map Windows content
|
// best to tone-map Windows content
|
||||||
static BOOL useEDR = YES;
|
static BOOL useEDR = YES;
|
||||||
|
|
||||||
CFStringRef __currentColorSpace;
|
// Name of the colorspace most recently applied to the layer, for the stats overlay.
|
||||||
|
// Class-level because the stats path only has access to the class; guarded by a lock
|
||||||
|
// since it is written on the render thread and read from other threads, and multiple
|
||||||
|
// renderer instances can briefly overlap during a session switch.
|
||||||
|
static NSString *__currentColorSpace;
|
||||||
|
|
||||||
|
static void setCurrentColorSpaceName(NSString *name) {
|
||||||
|
@synchronized ([MetalVideoRenderer class]) {
|
||||||
|
__currentColorSpace = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@implementation MetalVideoRenderer {
|
@implementation MetalVideoRenderer {
|
||||||
dispatch_queue_t _sq;
|
dispatch_queue_t _sq;
|
||||||
@@ -225,11 +235,9 @@ CFStringRef __currentColorSpace;
|
|||||||
if (_renderPassDescriptor) {
|
if (_renderPassDescriptor) {
|
||||||
_renderPassDescriptor = nil;
|
_renderPassDescriptor = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__currentColorSpace) {
|
// Do NOT clear __currentColorSpace here: a new renderer instance may already be
|
||||||
CFRelease(__currentColorSpace);
|
// live (session switch) and have set its own value.
|
||||||
__currentColorSpace = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !TARGET_OS_TV
|
#if !TARGET_OS_TV
|
||||||
@@ -405,9 +413,11 @@ CFStringRef __currentColorSpace;
|
|||||||
paramBuffer.cscParams = (fullRange ? k_CscParams_Bt709Full : k_CscParams_Bt709Lim);
|
paramBuffer.cscParams = (fullRange ? k_CscParams_Bt709Full : k_CscParams_Bt709Lim);
|
||||||
break;
|
break;
|
||||||
case COLORSPACE_REC_2020: {
|
case COLORSPACE_REC_2020: {
|
||||||
|
// Frames without a format description (e.g. produced for the AVSB
|
||||||
|
// backend) must not crash us here
|
||||||
CFDictionaryRef ext = [frame getFormatDescExtensions];
|
CFDictionaryRef ext = [frame getFormatDescExtensions];
|
||||||
CFStringRef frame_trc = CFDictionaryGetValue(ext, kCVImageBufferTransferFunctionKey);
|
CFStringRef frame_trc = ext ? CFDictionaryGetValue(ext, kCVImageBufferTransferFunctionKey) : NULL;
|
||||||
if (CFEqual(frame_trc, kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ)) {
|
if (frame_trc && CFEqual(frame_trc, kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ)) {
|
||||||
isHDR = YES;
|
isHDR = YES;
|
||||||
newColorSpace = CGColorSpaceCreateWithName(_nonFullHdrColorSpace);
|
newColorSpace = CGColorSpaceCreateWithName(_nonFullHdrColorSpace);
|
||||||
newPixelFormat = _nonFullHdrPixelFormat;
|
newPixelFormat = _nonFullHdrPixelFormat;
|
||||||
@@ -478,11 +488,7 @@ CFStringRef __currentColorSpace;
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (__currentColorSpace) {
|
setCurrentColorSpaceName(newColorSpace ? CFBridgingRelease(CGColorSpaceCopyName(newColorSpace)) : nil);
|
||||||
CFRelease(__currentColorSpace);
|
|
||||||
__currentColorSpace = NULL;
|
|
||||||
}
|
|
||||||
__currentColorSpace = CGColorSpaceCopyName(newColorSpace);
|
|
||||||
CGColorSpaceRelease(newColorSpace);
|
CGColorSpaceRelease(newColorSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -904,10 +910,9 @@ CFStringRef __currentColorSpace;
|
|||||||
}
|
}
|
||||||
|
|
||||||
+ (NSString *)currentColorSpace {
|
+ (NSString *)currentColorSpace {
|
||||||
if (__currentColorSpace) {
|
@synchronized ([MetalVideoRenderer class]) {
|
||||||
return (__bridge NSString *)__currentColorSpace;
|
return __currentColorSpace;
|
||||||
}
|
}
|
||||||
return nil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -24,7 +24,9 @@
|
|||||||
@interface MetalView : UIView <CALayerDelegate>
|
@interface MetalView : UIView <CALayerDelegate>
|
||||||
|
|
||||||
@property (nonatomic, nonnull, readonly) CAMetalLayer *metalLayer;
|
@property (nonatomic, nonnull, readonly) CAMetalLayer *metalLayer;
|
||||||
@property (nonatomic, nullable) id<MetalViewDelegate> delegate;
|
// weak: the delegate (MetalViewController) strongly owns this view; a strong
|
||||||
|
// reference here creates a retain cycle that leaks the whole Metal stack.
|
||||||
|
@property (nonatomic, weak, nullable) id<MetalViewDelegate> delegate;
|
||||||
@property (nonatomic) float framerate;
|
@property (nonatomic) float framerate;
|
||||||
|
|
||||||
- (void)initCommon;
|
- (void)initCommon;
|
||||||
|
|||||||
@@ -45,10 +45,20 @@
|
|||||||
Log(LOG_I, @"[MetalView] sending renderThread a cancel message");
|
Log(LOG_I, @"[MetalView] sending renderThread a cancel message");
|
||||||
[_renderThread cancel];
|
[_renderThread cancel];
|
||||||
Log(LOG_I, @"[MetalView] waiting on renderThread to finish");
|
Log(LOG_I, @"[MetalView] waiting on renderThread to finish");
|
||||||
while (!_renderThread.isFinished) {
|
// Bounded wait only: shutdown typically runs on the main thread, and the render
|
||||||
|
// thread may be blocked in a dispatch_sync onto the main queue (layer colorspace
|
||||||
|
// changes). Spinning here forever would deadlock; if we time out, the thread
|
||||||
|
// exits on its own once the main queue is serviced again (it retains the view
|
||||||
|
// via its block, so this is safe).
|
||||||
|
CFTimeInterval deadline = CACurrentMediaTime() + 1.0;
|
||||||
|
while (!_renderThread.isFinished && CACurrentMediaTime() < deadline) {
|
||||||
usleep(100);
|
usleep(100);
|
||||||
}
|
}
|
||||||
Log(LOG_I, @"[MetalView] renderThread has finished");
|
if (_renderThread.isFinished) {
|
||||||
|
Log(LOG_I, @"[MetalView] renderThread has finished");
|
||||||
|
} else {
|
||||||
|
Log(LOG_W, @"[MetalView] renderThread still busy after 1s, letting it exit asynchronously");
|
||||||
|
}
|
||||||
_renderThread = nil;
|
_renderThread = nil;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,6 +78,13 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Never run two render loops at once (the view can move between windows,
|
||||||
|
// e.g. external display or re-parenting)
|
||||||
|
if (_renderThread) {
|
||||||
|
Log(LOG_W, @"[MetalView] movedToWindow with a live renderThread, restarting it");
|
||||||
|
[self shutdown];
|
||||||
|
}
|
||||||
|
|
||||||
// Render on a new thread
|
// Render on a new thread
|
||||||
_renderThread = [[NSThread alloc] initWithBlock:^{
|
_renderThread = [[NSThread alloc] initWithBlock:^{
|
||||||
while (![NSThread currentThread].isCancelled) {
|
while (![NSThread currentThread].isCancelled) {
|
||||||
|
|||||||
@@ -24,4 +24,8 @@
|
|||||||
- (void)pauseRendering;
|
- (void)pauseRendering;
|
||||||
- (void)resumeRendering;
|
- (void)resumeRendering;
|
||||||
|
|
||||||
|
// Idempotent teardown: stops the render thread, display link and renderer.
|
||||||
|
// Must be called when the stream session ends; also runs from viewDidDisappear.
|
||||||
|
- (void)shutdown;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -109,13 +109,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer {
|
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer {
|
||||||
// Skip waiting when renderer is paused
|
// Snapshot into a strong local: shutdown (main thread) can nil _renderer while
|
||||||
if (_renderer.isStopping) {
|
// the render thread is inside this method.
|
||||||
|
MetalVideoRenderer *renderer = _renderer;
|
||||||
|
|
||||||
|
// Skip waiting when renderer is paused or gone
|
||||||
|
if (!renderer || renderer.isStopping) {
|
||||||
|
_frameSlotAcquired = NO;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (@available(iOS 13.0, *)) {
|
if (@available(iOS 13.0, *)) {
|
||||||
_frameSlotAcquired = [_renderer waitToRenderTo:layer];
|
_frameSlotAcquired = [renderer waitToRenderTo:layer];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_frameSlotAcquired) {
|
if (_frameSlotAcquired) {
|
||||||
@@ -128,22 +133,27 @@
|
|||||||
if (!_frameSlotAcquired) {
|
if (!_frameSlotAcquired) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CFTimeInterval timeout = (1.0f / _framerate) - _renderer.averageGPUTime;
|
// Snapshot for the same reason as in waitToRenderTo
|
||||||
|
MetalVideoRenderer *renderer = _renderer;
|
||||||
|
if (!renderer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CFTimeInterval timeout = (1.0f / _framerate) - renderer.averageGPUTime;
|
||||||
Frame *frame = [_frameQueue dequeueWithTimeoutSync:timeout];
|
Frame *frame = [_frameQueue dequeueWithTimeoutSync:timeout];
|
||||||
|
|
||||||
if (!_renderer.isStopping) {
|
if (!renderer.isStopping) {
|
||||||
// Only render if not paused
|
// Only render if not paused
|
||||||
if (frame) {
|
if (frame) {
|
||||||
//if (@available(iOS 13.0, *)) {
|
//if (@available(iOS 13.0, *)) {
|
||||||
[_renderer renderFrame:frame toLayer:layer];
|
[renderer renderFrame:frame toLayer:layer];
|
||||||
//}
|
//}
|
||||||
} else {
|
} else {
|
||||||
dispatch_semaphore_signal([_renderer inFlightSemaphore]);
|
dispatch_semaphore_signal([renderer inFlightSemaphore]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// When paused, we still dequeue frames to prevent accumulation
|
// When paused, we still dequeue frames to prevent accumulation
|
||||||
// but don't render them. Also sleep a bit to reduce CPU usage
|
// but don't render them. Also sleep a bit to reduce CPU usage
|
||||||
dispatch_semaphore_signal([_renderer inFlightSemaphore]);
|
dispatch_semaphore_signal([renderer inFlightSemaphore]);
|
||||||
usleep(100000);
|
usleep(100000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,11 +187,18 @@
|
|||||||
|
|
||||||
Log(LOG_I, @"[MetalViewController] viewDidDisappear");
|
Log(LOG_I, @"[MetalViewController] viewDidDisappear");
|
||||||
|
|
||||||
|
[self shutdown];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)shutdown {
|
||||||
if (_displayLink) {
|
if (_displayLink) {
|
||||||
[_displayLink invalidate];
|
[_displayLink invalidate];
|
||||||
_displayLink = nil;
|
_displayLink = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop the renderer before the render thread: isStopping makes the thread's
|
||||||
|
// renderFrame return early, so it can't enter the dispatch_sync-to-main path
|
||||||
|
// while we wait for it below.
|
||||||
if (_renderer) {
|
if (_renderer) {
|
||||||
[_renderer shutdown];
|
[_renderer shutdown];
|
||||||
_renderer = nil;
|
_renderer = nil;
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ int DrDecoderSetup(int videoFormat, int width, int height, int redrawRate, void*
|
|||||||
void DrCleanup(void)
|
void DrCleanup(void)
|
||||||
{
|
{
|
||||||
[renderer cleanup];
|
[renderer cleanup];
|
||||||
|
// Drop the static reference so the old renderer (and its decoder resources)
|
||||||
|
// doesn't outlive the session; otherwise it stays alive until the next
|
||||||
|
// Connection init overwrites it, which can interleave with a new session.
|
||||||
|
renderer = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
-(BandwidthTracker *) getBwTracker
|
-(BandwidthTracker *) getBwTracker
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
FramePacingMode _framePacingMode;
|
FramePacingMode _framePacingMode;
|
||||||
bool _enableTimebase;
|
bool _enableTimebase;
|
||||||
bool _asyncFrameDequeue;
|
bool _asyncFrameDequeue;
|
||||||
|
|
||||||
|
// Cached UIApplication background state. UIKit's applicationState must only be
|
||||||
|
// read on the main thread, but the decode path needs it on the VTDecoder queue.
|
||||||
|
// Updated on the main thread via notifications; a stale read here is harmless
|
||||||
|
// (it only gates metrics collection).
|
||||||
|
volatile BOOL _appInBackground;
|
||||||
|
|
||||||
// CMTime playTime;
|
// CMTime playTime;
|
||||||
// NSTimeInterval previousLinkTime;
|
// NSTimeInterval previousLinkTime;
|
||||||
@@ -207,7 +213,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
_needRequeuing = _queueSize>0;
|
_needRequeuing = _queueSize>0;
|
||||||
|
|
||||||
_frameQueue = [FrameQueue sharedInstance];
|
_frameQueue = [FrameQueue sharedInstance];
|
||||||
[_frameQueue start];
|
[_frameQueue startForOwner:self];
|
||||||
[_frameQueue setHighWaterMark:MAX(1, _queueSize)];
|
[_frameQueue setHighWaterMark:MAX(1, _queueSize)];
|
||||||
|
|
||||||
[self reinitializeDisplayLayer];
|
[self reinitializeDisplayLayer];
|
||||||
@@ -222,6 +228,18 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
sActiveRenderer = self;
|
sActiveRenderer = self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Renderer init runs on the main thread, so reading applicationState here is legal.
|
||||||
|
// The decode queue reads the cached flag instead (UIKit forbids off-main reads).
|
||||||
|
_appInBackground = ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground);
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(appDidEnterBackground)
|
||||||
|
name:UIApplicationDidEnterBackgroundNotification
|
||||||
|
object:nil];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(appWillEnterForeground)
|
||||||
|
name:UIApplicationWillEnterForegroundNotification
|
||||||
|
object:nil];
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,6 +345,14 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)appDidEnterBackground {
|
||||||
|
_appInBackground = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)appWillEnterForeground {
|
||||||
|
_appInBackground = NO;
|
||||||
|
}
|
||||||
|
|
||||||
# pragma mark DisplayLink vsync callback
|
# pragma mark DisplayLink vsync callback
|
||||||
|
|
||||||
- (void)setupWithVideoFormat:(int)videoFormat width:(int)videoWidth height:(int)videoHeight frameRate:(int)frameRate fullRange:(BOOL)fullRange request10BitCodec:(BOOL)request10BitCodec
|
- (void)setupWithVideoFormat:(int)videoFormat width:(int)videoWidth height:(int)videoHeight frameRate:(int)frameRate fullRange:(BOOL)fullRange request10BitCodec:(BOOL)request10BitCodec
|
||||||
@@ -570,7 +596,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
// we missed a callback
|
// we missed a callback
|
||||||
// Log(LOG_W, @"*** slow frametime %.3f ms", frametime * 1000.0);
|
// Log(LOG_W, @"*** slow frametime %.3f ms", frametime * 1000.0);
|
||||||
}
|
}
|
||||||
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
|
if (!self->_appInBackground) {
|
||||||
[[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:frametime * 1000.0];
|
[[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:frametime * 1000.0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -675,7 +701,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[self->_frameQueue stop];
|
[self->_frameQueue stopForOwner:self];
|
||||||
|
|
||||||
if (self->_renderingBackend == RENDER_AVSB) {
|
if (self->_renderingBackend == RENDER_AVSB) {
|
||||||
[self->_displayLink invalidate];
|
[self->_displayLink invalidate];
|
||||||
@@ -1310,7 +1336,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
framesDropped += [self->_frameQueue enqueue:outputFrame withSlackSize:3];
|
framesDropped += [self->_frameQueue enqueue:outputFrame withSlackSize:3];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
|
if (!self->_appInBackground) {
|
||||||
static PlotMetrics frameQueueMetrics = {};
|
static PlotMetrics frameQueueMetrics = {};
|
||||||
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_QUEUED_FRAMES value:[self->_frameQueue count] plotMetrics:&frameQueueMetrics];
|
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_QUEUED_FRAMES value:[self->_frameQueue count] plotMetrics:&frameQueueMetrics];
|
||||||
[self safeCopyMetricsTo:&self->_frameQueueMetrics from:&frameQueueMetrics];
|
[self safeCopyMetricsTo:&self->_frameQueueMetrics from:&frameQueueMetrics];
|
||||||
@@ -1334,7 +1360,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
|
|
||||||
int framesDropped = [self->_frameQueue enqueue:frame withSlackSize:3];
|
int framesDropped = [self->_frameQueue enqueue:frame withSlackSize:3];
|
||||||
|
|
||||||
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
|
if (!self->_appInBackground) {
|
||||||
static PlotMetrics frameQueueMetrics = {};
|
static PlotMetrics frameQueueMetrics = {};
|
||||||
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_QUEUED_FRAMES value:[self->_frameQueue count] plotMetrics:&frameQueueMetrics];
|
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_QUEUED_FRAMES value:[self->_frameQueue count] plotMetrics:&frameQueueMetrics];
|
||||||
[self safeCopyMetricsTo:&self->_frameQueueMetrics from:&frameQueueMetrics];
|
[self safeCopyMetricsTo:&self->_frameQueueMetrics from:&frameQueueMetrics];
|
||||||
|
|||||||
@@ -108,7 +108,11 @@ static char FrameInterpolatedKey;
|
|||||||
|
|
||||||
- (CFDictionaryRef)getFormatDescExtensions {
|
- (CFDictionaryRef)getFormatDescExtensions {
|
||||||
if (!_formatDescExt && _formatDesc) {
|
if (!_formatDescExt && _formatDesc) {
|
||||||
_formatDescExt = CFRetain(CMFormatDescriptionGetExtensions(_formatDesc));
|
// Can legitimately be NULL, and CFRetain(NULL) crashes
|
||||||
|
CFDictionaryRef ext = CMFormatDescriptionGetExtensions(_formatDesc);
|
||||||
|
if (ext) {
|
||||||
|
_formatDescExt = CFRetain(ext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return _formatDescExt;
|
return _formatDescExt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
- (CFTimeInterval)estimatedFramerate;
|
- (CFTimeInterval)estimatedFramerate;
|
||||||
- (int)currentSoftCap;
|
- (int)currentSoftCap;
|
||||||
- (void)waitForEnqueue;
|
- (void)waitForEnqueue;
|
||||||
- (void)start;
|
- (void)startForOwner:(id)owner;
|
||||||
- (void)stop;
|
- (void)stopForOwner:(id)owner;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
int _currentSoftCap;
|
int _currentSoftCap;
|
||||||
dispatch_queue_t _sq;
|
dispatch_queue_t _sq;
|
||||||
dispatch_semaphore_t _frameSemaphore;
|
dispatch_semaphore_t _frameSemaphore;
|
||||||
|
|
||||||
|
// The renderer session currently consuming from the queue. A stale session's
|
||||||
|
// async stop must not pause a newer session's queue.
|
||||||
|
__weak id _owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)sharedInstance {
|
+ (instancetype)sharedInstance {
|
||||||
@@ -301,7 +305,14 @@
|
|||||||
|
|
||||||
- (void)clear {
|
- (void)clear {
|
||||||
os_unfair_lock_lock(&_lock);
|
os_unfair_lock_lock(&_lock);
|
||||||
|
// Release any frames still held in the ring buffer, otherwise their
|
||||||
|
// pixel/sample buffers stay retained for the lifetime of the singleton.
|
||||||
|
for (int i = 0; i < _capacity; i++) {
|
||||||
|
[_buffer replaceObjectAtIndex:i withObject:[NSNull null]];
|
||||||
|
}
|
||||||
_head = _tail = _count = 0;
|
_head = _tail = _count = 0;
|
||||||
|
_droppedLast = NO;
|
||||||
|
_ptsCorrection = CMTimeMake(0, 90000);
|
||||||
_frameDropMetrics = [[FloatBuffer alloc] initWithCapacity:512];
|
_frameDropMetrics = [[FloatBuffer alloc] initWithCapacity:512];
|
||||||
os_unfair_lock_unlock(&_lock);
|
os_unfair_lock_unlock(&_lock);
|
||||||
}
|
}
|
||||||
@@ -336,15 +347,31 @@
|
|||||||
return cap;
|
return cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)stop {
|
- (void)stopForOwner:(id)owner {
|
||||||
|
@synchronized (self) {
|
||||||
|
// Ignore stops from a previous session: cleanup is dispatched async and can
|
||||||
|
// land after a new renderer has already (re)started the queue.
|
||||||
|
if (_owner != nil && owner != _owner) {
|
||||||
|
Log(LOG_W, @"FrameQueue stop ignored: %p is not the current owner", owner);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_owner = nil;
|
||||||
|
}
|
||||||
// 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.paused = YES;
|
self.paused = YES;
|
||||||
Log(LOG_I, @"FrameQueue stopped");
|
Log(LOG_I, @"FrameQueue stopped");
|
||||||
dispatch_semaphore_signal(_frameSemaphore);
|
dispatch_semaphore_signal(_frameSemaphore);
|
||||||
|
// Don't keep the previous session's frames (and their pixel buffers) alive
|
||||||
|
[self clear];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)start {
|
- (void)startForOwner:(id)owner {
|
||||||
// (re)start for a new renderer
|
@synchronized (self) {
|
||||||
|
_owner = owner;
|
||||||
|
}
|
||||||
|
// Drop any frames left over from a previous renderer session; feeding them to a
|
||||||
|
// renderer with a different backend or video format can crash it.
|
||||||
|
[self clear];
|
||||||
self.paused = NO;
|
self.paused = NO;
|
||||||
Log(LOG_I, @"FrameQueue started");
|
Log(LOG_I, @"FrameQueue started");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2681,39 +2681,55 @@ BOOL isCustomResolution(int resolutionSelected) {
|
|||||||
|
|
||||||
// Check if the rendering backend has actually changed
|
// Check if the rendering backend has actually changed
|
||||||
if (previousBackend != sender.selectedSegmentIndex) {
|
if (previousBackend != sender.selectedSegmentIndex) {
|
||||||
// Show alert to prompt user to restart the app
|
// The renderer is picked up fresh at every stream start, so the change takes
|
||||||
NSString *message = [LocalizationHelper localizedStringForKey: sender.selectedSegmentIndex == 1 ? @"metalRenderTip" : @"standardRenderTip"];
|
// effect on the next session — no app restart needed.
|
||||||
|
void (^applyChange)(void) = ^{
|
||||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Restart Required"]
|
|
||||||
message:message
|
|
||||||
preferredStyle:UIAlertControllerStyleAlert];
|
|
||||||
|
|
||||||
UIAlertAction *quitAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Quit Now"]
|
|
||||||
style:UIAlertActionStyleDefault
|
|
||||||
handler:^(UIAlertAction * _Nonnull action) {
|
|
||||||
|
|
||||||
Settings* directSettings = [self->dataMan retrieveSettings];
|
Settings* directSettings = [self->dataMan retrieveSettings];
|
||||||
directSettings.renderingBackend = [NSNumber numberWithInteger:sender.selectedSegmentIndex];
|
directSettings.renderingBackend = [NSNumber numberWithInteger:sender.selectedSegmentIndex];
|
||||||
[self->dataMan saveData];
|
[self->dataMan saveData];
|
||||||
[self saveSettings];
|
[self saveSettings];
|
||||||
|
};
|
||||||
exit(0);
|
|
||||||
}];
|
void (^revertChange)(void) = ^{
|
||||||
|
self.renderingBackendSelector.selectedSegmentIndex = previousBackend;
|
||||||
UIAlertAction *laterAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Learn More"]
|
|
||||||
style:UIAlertActionStyleCancel
|
|
||||||
handler:^(UIAlertAction * _Nonnull action) {
|
|
||||||
self.renderingBackendSelector.selectedSegmentIndex = 0;
|
|
||||||
[self.renderingBackendSelector sendActionsForControlEvents:UIControlEventValueChanged];
|
[self.renderingBackendSelector sendActionsForControlEvents:UIControlEventValueChanged];
|
||||||
NSURL *url = [NSURL URLWithString:[LocalizationHelper localizedStringForKey:@"betterPerformanceLink"]];
|
};
|
||||||
if ([[UIApplication sharedApplication] canOpenURL:url]) {
|
|
||||||
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
|
if (sender.selectedSegmentIndex == RENDER_METAL) {
|
||||||
}
|
// Metal is experimental: confirm its caveats before applying
|
||||||
}];
|
NSString *message = [LocalizationHelper localizedStringForKey:@"metalRenderTip"];
|
||||||
|
|
||||||
[alertController addAction:laterAction];
|
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Enable Metal Renderer?"]
|
||||||
[alertController addAction:quitAction];
|
message:message
|
||||||
[self presentViewController:alertController animated:YES completion:nil];
|
preferredStyle:UIAlertControllerStyleAlert];
|
||||||
|
|
||||||
|
[alertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Apply"]
|
||||||
|
style:UIAlertActionStyleDefault
|
||||||
|
handler:^(UIAlertAction * _Nonnull action) {
|
||||||
|
applyChange();
|
||||||
|
}]];
|
||||||
|
|
||||||
|
[alertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Learn More"]
|
||||||
|
style:UIAlertActionStyleDefault
|
||||||
|
handler:^(UIAlertAction * _Nonnull action) {
|
||||||
|
revertChange();
|
||||||
|
NSURL *url = [NSURL URLWithString:[LocalizationHelper localizedStringForKey:@"betterPerformanceLink"]];
|
||||||
|
if ([[UIApplication sharedApplication] canOpenURL:url]) {
|
||||||
|
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
|
||||||
|
}
|
||||||
|
}]];
|
||||||
|
|
||||||
|
[alertController addAction:[UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Cancel"]
|
||||||
|
style:UIAlertActionStyleCancel
|
||||||
|
handler:^(UIAlertAction * _Nonnull action) {
|
||||||
|
revertChange();
|
||||||
|
}]];
|
||||||
|
|
||||||
|
[self presentViewController:alertController animated:YES completion:nil];
|
||||||
|
} else {
|
||||||
|
// Switching back to the standard renderer needs no confirmation
|
||||||
|
applyChange();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1244,8 +1244,8 @@ static __weak StreamFrameViewController *VLSharedStreamFrameViewController = nil
|
|||||||
- (void)willMoveToParentViewController:(UIViewController *)parent {
|
- (void)willMoveToParentViewController:(UIViewController *)parent {
|
||||||
// Only cleanup when we're being destroyed
|
// Only cleanup when we're being destroyed
|
||||||
if (parent == nil) {
|
if (parent == nil) {
|
||||||
_streamView = nil;
|
|
||||||
[_streamView cleanUp];
|
[_streamView cleanUp];
|
||||||
|
_streamView = nil;
|
||||||
[_controllerSupport cleanup];
|
[_controllerSupport cleanup];
|
||||||
|
|
||||||
[UIApplication sharedApplication].idleTimerDisabled = NO;
|
[UIApplication sharedApplication].idleTimerDisabled = NO;
|
||||||
@@ -1255,6 +1255,10 @@ static __weak StreamFrameViewController *VLSharedStreamFrameViewController = nil
|
|||||||
_inactivityTimer = nil;
|
_inactivityTimer = nil;
|
||||||
}
|
}
|
||||||
if (self.metalViewController) {
|
if (self.metalViewController) {
|
||||||
|
// Explicit shutdown: viewDidDisappear is not guaranteed to fire here
|
||||||
|
// (e.g. teardown while backgrounded), and it's what stops the render
|
||||||
|
// thread, display link and renderer.
|
||||||
|
[self.metalViewController shutdown];
|
||||||
[self.metalViewController.view removeFromSuperview];
|
[self.metalViewController.view removeFromSuperview];
|
||||||
[self.metalViewController removeFromParentViewController];
|
[self.metalViewController removeFromParentViewController];
|
||||||
self.metalViewController = nil;
|
self.metalViewController = nil;
|
||||||
|
|||||||
Reference in new issue
Block a user