diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index edf4111d..e679a03e 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -9350,138 +9350,6 @@ } } }, - "Interpolation Resolution" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "插帧分辨率" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "影格插補解析度" - } - }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "影格插補解析度" - } - } - } - }, - "Most devices currently support frame interpolation only for 8-bit YUV 4:2:0 SDR video. In this case, if HDR or YUV 4:4:4 is enabled, intermediate frames will still be generated in 8-bit YUV 4:2:0 SDR." : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "目前多数设备仅支持 8-bit YUV420 SDR 视频插帧。这种情况下,如果您打开了 HDR 或 YUV444 选项,中间帧将依然按 8-bit YUV420 SDR 格式生成。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "目前多數裝置僅支援 8-bit YUV420 SDR 影片影格插補。在這種情況下,如果您開啟 HDR 或 YUV444 選項,中間影格仍會以 8-bit YUV420 SDR 格式產生。" - } - } - } - }, - "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" : "插幀器已啟動。" - } - } - } - }, "framePacingStackTip" : { "extractionState" : "manual", "localizations" : { @@ -11015,6 +10883,29 @@ } } }, + "Interpolation Resolution" : { + "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" : { @@ -12371,25 +12262,25 @@ "en" : { "stringUnit" : { "state" : "translated", - "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." + "value" : "\nWait!\n\n- VoidLink's standard rendering mode already delivers extremely strong performance, on par with Metal rendering.\n\n- Metal rendering consumes more GPU resources and power, and on some devices it may result in reduced performance.\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" : { "stringUnit" : { - "state" : "translated", - "value" : "\n等等!\n\n- 标准渲染模式目前在大多数设备上具有更好的性能。\n\n- 渲染模式的更改将在下次开始串流时生效,无需重启应用程序。\n\n- Metal 渲染是一项实验性功能,在某些设备上可能会导致性能下降。\n\n- Metal 渲染模式下不支持画中画。\n\n- 目前 Metal 渲染器在大多数设备上无法正确处理 HDR 内容。\n\n点击“应用”表示我已知晓并认可以上信息。" + "state" : "needs_review", + "value" : "\n等等!\n\n- VoidLink 标准渲染模式的性能已经非常强,与Metal 渲染处于同一水准。\n\n- Metal 渲染需消耗更多GPU资源和电量,在某些设备上可能会导致性能下降。\n\n- Metal 渲染模式下不支持画中画。\n\n- 目前 Metal 渲染器在大多数设备上无法正确处理 HDR 内容。\n\n点击“应用”表示我已知晓并认可以上信息。" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "\n等等!\n\n- 目前在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 变更渲染模式将在下次开始串流时生效,无需重新启动应用程式。\n\n- Metal 渲染为实验性功能,可能在某些装置上造成效能下降。\n\n- Metal 渲染模式下不支援画中画。\n\n- Metal 渲染器目前在大多数装置上无法正确处理 HDR 内容。\n\n点选「套用」表示我已了解并同意上述说明。" + "value" : "\n等等!\n\n- VoidLink 標準渲染模式的效能已經非常強大,與 Metal 渲染處於同等水準。\n\n- Metal 渲染需要消耗更多 GPU 資源與電量,在部分裝置上可能反而會導致效能下降。\n\n- Metal 渲染模式下不支援画中画。\n\n- Metal 渲染器目前在大多数装置上无法正确处理 HDR 内容。\n\n点选「套用」表示我已了解并同意上述说明。" } }, "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "\n等等!\n\n- 目前在大多数装置上,標準渲染模式具有较佳的效能。\n\n- 变更渲染模式将在下次开始串流时生效,无需重新启动应用程式。\n\n- Metal 渲染为实验性功能,可能在某些装置上造成效能下降。\n\n- Metal 渲染模式下不支援画中画。\n\n- Metal 渲染器目前在大多数装置上无法正确处理 HDR 内容。\n\n点选「套用」表示我已了解并同意上述说明。" + "value" : "\n等等!\n\n- VoidLink 標準渲染模式的效能已經非常強大,與 Metal 渲染處於同等水準。\n\n- Metal 渲染需要消耗更多 GPU 資源與電量,在部分裝置上可能反而會導致效能下降。\n\n- Metal 渲染模式下不支援画中画。\n\n- Metal 渲染器目前在大多数装置上无法正确处理 HDR 内容。\n\n点选「套用」表示我已了解并同意上述说明。" } } } @@ -12567,6 +12458,23 @@ } } }, + "Most devices currently support frame interpolation only for 8-bit YUV 4:2:0 SDR video. In this case, if HDR or YUV 4:4:4 is enabled, intermediate frames will still be generated in 8-bit YUV 4:2:0 SDR." : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "目前多数设备仅支持 8-bit YUV420 SDR 视频插帧。这种情况下,如果您打开了 HDR 或 YUV444 选项,中间帧将依然按 8-bit YUV420 SDR 格式生成。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "目前多數裝置僅支援 8-bit YUV420 SDR 影片影格插補。在這種情況下,如果您開啟 HDR 或 YUV444 選項,中間影格仍會以 8-bit YUV420 SDR 格式產生。" + } + } + } + }, "Motion Control" : { "extractionState" : "manual", "localizations" : { @@ -16482,6 +16390,29 @@ } } }, + "Scaled Stream Resolution" : { + "extractionState" : "manual", + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "串流分辨率缩放" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "串流解析度縮放" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "串流解析度縮放" + } + } + } + }, "screenRotationTIp" : { "extractionState" : "manual", "localizations" : { @@ -17214,6 +17145,35 @@ } } }, + "simplifiedOsdTextWithInterpolation" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "FPS: %6.2f+%.2f Dropped frames: %4.1f%% Bitrate: %5.1f Mbps Latency: %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "帧率: %6.2f+%.2f 丢帧: %4.1f%% 码率: %5.1f Mbps 延迟: %@" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "FPS: %6.2f+%.2f 掉幀: %4.1f%% 位元速率: %5.1f Mbps 延遲: %@" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "FPS: %6.2f+%.2f 掉幀: %4.1f%% 位元速率: %5.1f Mbps 延遲: %@" + } + } + } + }, "Size" : { "extractionState" : "manual", "localizations" : { @@ -17387,29 +17347,6 @@ } } }, - "Scaled Stream Resolution" : { - "extractionState" : "manual", - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "串流分辨率缩放" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "串流解析度縮放" - } - }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "串流解析度縮放" - } - } - } - }, "Slow connection to PC, Reduce your bitrate" : { "extractionState" : "manual", "localizations" : { @@ -20116,4 +20053,4 @@ } }, "version" : "1.1" -} +} \ No newline at end of file diff --git a/VoidLink/Stream/Connection.m b/VoidLink/Stream/Connection.m index b51d6f8a..a22b21fc 100644 --- a/VoidLink/Stream/Connection.m +++ b/VoidLink/Stream/Connection.m @@ -43,6 +43,7 @@ static int activeVideoFormat; static video_stats_t currentVideoStats; static video_stats_t lastVideoStats; static NSLock* videoStatsLock; +static uint64_t lastRenderedInterpolatedFrameCount; static SDL_AudioDeviceID audioDevice; static OPUS_MULTISTREAM_CONFIGURATION audioConfig; @@ -73,6 +74,7 @@ int DrDecoderSetup(int videoFormat, int width, int height, int redrawRate, void* Log(LOG_I, @"Active video format: 0x%x", activeVideoFormat); memset(¤tVideoStats, 0, sizeof(currentVideoStats)); memset(&lastVideoStats, 0, sizeof(lastVideoStats)); + lastRenderedInterpolatedFrameCount = [renderer renderedInterpolatedFrameCount]; bwTracker = [[BandwidthTracker alloc] initWithWindowSeconds:10 bucketIntervalMs:250]; return 0; } @@ -178,6 +180,9 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit) // Flip stats roughly every second if (now - currentVideoStats.startTime >= 1.0f) { currentVideoStats.endTime = now; + uint64_t renderedInterpolatedFrameCount = [renderer renderedInterpolatedFrameCount]; + currentVideoStats.interpolatedFrames = (int)(renderedInterpolatedFrameCount - lastRenderedInterpolatedFrameCount); + lastRenderedInterpolatedFrameCount = renderedInterpolatedFrameCount; [videoStatsLock lock]; lastVideoStats = currentVideoStats; diff --git a/VoidLink/Stream/StreamManager.m b/VoidLink/Stream/StreamManager.m index 9423718a..f2f98ec9 100644 --- a/VoidLink/Stream/StreamManager.m +++ b/VoidLink/Stream/StreamManager.m @@ -209,17 +209,38 @@ float scalePlotMetrics = stats.frameDropMetrics.nsamples > 0 ? ((float)stats.frameDropMetrics.nsamples / stats.totalFrames) : 1.0f; fps = (stats.totalFrames - stats.networkDroppedFrames - (stats.frameDropMetrics.total / scalePlotMetrics)) / interval; } + float interpolatedFps = stats.interpolatedFrames / interval; + + NSString *renderingFpsString; + if (framePacingMode == FramePacingModeInterpolation) { + renderingFpsString = [NSString stringWithFormat:@"Rendering FPS: %dx%d %.2f+%.2f", + _config.width, _config.height, fps, interpolatedFps]; + } + else { + renderingFpsString = [NSString stringWithFormat:@"Rendering FPS: %dx%d %.2f", + _config.width, _config.height, fps]; + } double avgVideoMbps = [_connection getBwTracker].averageMbps; double peakVideoMbps = [_connection getBwTracker].peakMbps; NSString* colorRange = _config.fullColorRange ? @"Full" : @"Limited"; - if(overlayLevel == 1) return [LocalizationHelper localizedStringForKey:@"simplifiedOsdText", - fps, - stats.networkDroppedFrames / interval, - avgVideoMbps, - latencyString]; + if (overlayLevel == 1) { + if (framePacingMode == FramePacingModeInterpolation) { + return [LocalizationHelper localizedStringForKey:@"simplifiedOsdTextWithInterpolation", + fps, + interpolatedFps, + stats.networkDroppedFrames / interval, + avgVideoMbps, + latencyString]; + } + return [LocalizationHelper localizedStringForKey:@"simplifiedOsdText", + fps, + stats.networkDroppedFrames / interval, + avgVideoMbps, + latencyString]; + } else { if (framePacingMode == FramePacingModeLegacy || framePacingMode == FramePacingModeOff) { NSString* rendererWithPacing = stats.renderingBackendString; @@ -231,15 +252,13 @@ } } - return [LocalizationHelper localizedStringForKey:@"Video stream: %dx%d %.2f FPS (Codec: %@, %@)\n" + return [LocalizationHelper localizedStringForKey:@"%@ (Codec: %@, %@)\n" "Bitrate: %.1f Mbps, Peak: %.1f\n" "%@" "Renderer: %@\n" "Frames dropped by network: %.1f%%\n" "Average network latency: %@", - _config.width, - _config.height, - fps, + renderingFpsString, [_connection getActiveCodecName], colorRange, avgVideoMbps, peakVideoMbps, @@ -253,16 +272,14 @@ rendererWithPacing = @"AVSampleBuffer (Queue Pacing)"; } - return [LocalizationHelper localizedStringForKey:@"Video stream: %dx%d %.2f FPS (Codec: %@, %@)\n" + return [LocalizationHelper localizedStringForKey:@"%@ (Codec: %@, %@)\n" "Bitrate: %.1f Mbps, Peak: %.1f, Frames buffered: %.1f\n" "%@" "Renderer: %@\n" "Frames dropped by network/pacing jitter: %.1f%% / %.1f%%\n" "Average network latency: %@\n" "Decode time: %.2f/%.2f/%.2f ms %@", - _config.width, - _config.height, - fps, + renderingFpsString, [_connection getActiveCodecName], colorRange, avgVideoMbps, peakVideoMbps, stats.frameQueueMetrics.avg, diff --git a/VoidLink/Stream/VideoDecoderRenderer.h b/VoidLink/Stream/VideoDecoderRenderer.h index c6f55f96..8c024c0c 100644 --- a/VoidLink/Stream/VideoDecoderRenderer.h +++ b/VoidLink/Stream/VideoDecoderRenderer.h @@ -33,6 +33,7 @@ - (void)setHdrMode:(BOOL)enabled; - (void)safeCopyMetricsTo:(PlotMetrics *)dst from:(PlotMetrics *)src; - (void)getAllStats:(video_stats_t *)stats; +- (uint64_t)renderedInterpolatedFrameCount; - (void)optimizeRefreshRate; - (void)resetFramePacing; diff --git a/VoidLink/Stream/VideoDecoderRenderer.m b/VoidLink/Stream/VideoDecoderRenderer.m index f1b8d73e..8f335eb6 100644 --- a/VoidLink/Stream/VideoDecoderRenderer.m +++ b/VoidLink/Stream/VideoDecoderRenderer.m @@ -31,6 +31,7 @@ #include #include #include +#include // Define for extra logging related to frame pacing //#define DISPLAYLINK_VERBOSE @@ -47,6 +48,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, - (void)stopFrameInterpolation; - (NSInteger)displayLinkFrameRateForInterpolationEnabled:(BOOL)enabled; - (void)restartDisplayLinkForInterpolationEnabled:(BOOL)enabled; +// - (void)logColorMetadataForFrameIfNeeded:(Frame *)frame; @end @implementation VideoDecoderRenderer { @@ -69,6 +71,9 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, VTDecompressionSessionRef _decompressionSession; FrameInterpolator *_frameInterpolator; BOOL _frameInterpolationPaused; + BOOL _loggedSourceFrameColorMetadata; + BOOL _loggedInterpolatedFrameColorMetadata; + atomic_uint_fast64_t _renderedInterpolatedFrameCount; CADisplayLink *_displayLink; NSInteger _maxRefreshRate; @@ -201,6 +206,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, _streamAspectRatio = aspectRatio; _maxRefreshRate = [[UIScreen mainScreen] maximumFramesPerSecond]; _parameterSetBuffers = [[NSMutableArray alloc] init]; + atomic_init(&_renderedInterpolatedFrameCount, 0); DataManager* dataMan = [[DataManager alloc] init]; TemporarySettings* tempSettings = [dataMan getSettings]; @@ -638,6 +644,9 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit); // Render frame at a specific targetTime - (void)renderFrame:(Frame *)frame atTime:(CMTime)targetTime { + if (kEnableFrameInterpolation && self->_frameInterpolator != nil) { + // [self logColorMetadataForFrameIfNeeded:frame]; + } CMSampleBufferSetOutputPresentationTimeStamp(frame.sampleBuffer, targetTime); if (_enableTimebase && [self->_displayLayer controlTimebase] == NULL) { @@ -659,8 +668,15 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit); Log(LOG_I, @"Setting timebase for stream to %d / %d", pts.value, pts.timescale); } - if(appDidEnterBackgroundWithoutPip) [self->_displayLayer flush]; - else [self->_displayLayer enqueueSampleBuffer:frame.sampleBuffer]; + if(appDidEnterBackgroundWithoutPip) { + [self->_displayLayer flush]; + } + else { + [self->_displayLayer enqueueSampleBuffer:frame.sampleBuffer]; + if (kEnableFrameInterpolation && frame.isInterpolated) { + atomic_fetch_add_explicit(&_renderedInterpolatedFrameCount, 1, memory_order_relaxed); + } + } #ifdef DISPLAYLINK_VERBOSE // Some OS-level metrics I'm not sure what to do with @@ -688,6 +704,78 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit); } } +/* +- (void)logColorMetadataForFrameIfNeeded:(Frame *)frame { + BOOL *logged = frame.isInterpolated ? + &_loggedInterpolatedFrameColorMetadata : + &_loggedSourceFrameColorMetadata; + if (*logged || frame.sampleBuffer == nil) { + return; + } + *logged = YES; + + CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(frame.sampleBuffer); + CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(frame.sampleBuffer); + if (imageBuffer == nil) { + Log(LOG_W, @"[FrameColor] %@ frame has no image buffer", + frame.isInterpolated ? @"interpolated" : @"source"); + return; + } + + OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer); + char fourCC[5] = { + (char)((pixelFormat >> 24) & 0xff), + (char)((pixelFormat >> 16) & 0xff), + (char)((pixelFormat >> 8) & 0xff), + (char)(pixelFormat & 0xff), + '\0' + }; + + CFTypeRef bufferTransfer = CVBufferGetAttachment( + imageBuffer, kCVImageBufferTransferFunctionKey, NULL); + CFTypeRef bufferPrimaries = CVBufferGetAttachment( + imageBuffer, kCVImageBufferColorPrimariesKey, NULL); + CFTypeRef bufferMatrix = CVBufferGetAttachment( + imageBuffer, kCVImageBufferYCbCrMatrixKey, NULL); + CFTypeRef bufferMastering = CVBufferGetAttachment( + imageBuffer, kCVImageBufferMasteringDisplayColorVolumeKey, NULL); + CFTypeRef bufferContentLight = CVBufferGetAttachment( + imageBuffer, kCVImageBufferContentLightLevelInfoKey, NULL); + + CFDictionaryRef extensions = formatDescription != nil ? + CMFormatDescriptionGetExtensions(formatDescription) : NULL; + CFTypeRef formatTransfer = extensions != NULL ? + CFDictionaryGetValue(extensions, kCMFormatDescriptionExtension_TransferFunction) : NULL; + CFTypeRef formatPrimaries = extensions != NULL ? + CFDictionaryGetValue(extensions, kCMFormatDescriptionExtension_ColorPrimaries) : NULL; + CFTypeRef formatMatrix = extensions != NULL ? + CFDictionaryGetValue(extensions, kCMFormatDescriptionExtension_YCbCrMatrix) : NULL; + CFTypeRef formatMastering = extensions != NULL ? + CFDictionaryGetValue(extensions, kCMFormatDescriptionExtension_MasteringDisplayColorVolume) : NULL; + CFTypeRef formatContentLight = extensions != NULL ? + CFDictionaryGetValue(extensions, kCMFormatDescriptionExtension_ContentLightLevelInfo) : NULL; + + Log(LOG_I, + @"[FrameColor] %@ frame: %dx%d pixelFormat=%s (%u); " + "buffer transfer=%@ primaries=%@ matrix=%@ mastering=%@ contentLight=%@; " + "format transfer=%@ primaries=%@ matrix=%@ mastering=%@ contentLight=%@", + frame.isInterpolated ? @"interpolated" : @"source", + (int)CVPixelBufferGetWidth(imageBuffer), + (int)CVPixelBufferGetHeight(imageBuffer), + fourCC, + (unsigned int)pixelFormat, + (__bridge id)bufferTransfer, + (__bridge id)bufferPrimaries, + (__bridge id)bufferMatrix, + bufferMastering != NULL ? @"present" : @"nil", + bufferContentLight != NULL ? @"present" : @"nil", + (__bridge id)formatTransfer, + (__bridge id)formatPrimaries, + (__bridge id)formatMatrix, + formatMastering != NULL ? @"present" : @"nil", + formatContentLight != NULL ? @"present" : @"nil"); +} */ + - (void)stop{ [[NSNotificationCenter defaultCenter] removeObserver:self]; [_displayLink invalidate]; @@ -1487,6 +1575,10 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit); }); } +- (uint64_t)renderedInterpolatedFrameCount { + return atomic_load_explicit(&_renderedInterpolatedFrameCount, memory_order_relaxed); +} + // When streaming lower framerate content on a ProMotion display, the screen refresh rate can be // reduced, optimizing battery life. Not currently used, it doesn't seem as reliable as I'd like. - (void)optimizeRefreshRate { diff --git a/VoidLink/Utility/Plot.h b/VoidLink/Utility/Plot.h index de78bf03..050b213a 100644 --- a/VoidLink/Utility/Plot.h +++ b/VoidLink/Utility/Plot.h @@ -40,6 +40,7 @@ typedef struct { int totalFrames; int receivedFrames; int networkDroppedFrames; + int interpolatedFrames; int totalHostProcessingLatency; int framesWithHostProcessingLatency; int maxHostProcessingLatency; diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings index 2067037b..63f49acc 100644 --- a/VoidLink/mul.lproj/iPad.xcstrings +++ b/VoidLink/mul.lproj/iPad.xcstrings @@ -2689,20 +2689,20 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", - "value" : "悬停模式" + "state" : "translated", + "value" : "Pencil模式" } }, "zh-Hant" : { "stringUnit" : { - "state" : "needs_review", - "value" : "懸停模式" + "state" : "translated", + "value" : "Pencil模式" } }, "zh-HK" : { "stringUnit" : { - "state" : "needs_review", - "value" : "懸停模式" + "state" : "translated", + "value" : "Pencil模式" } } }