feat: pause Metal renderer and graph when app is in background
This commit is contained in:
@@ -128,6 +128,12 @@
|
||||
- (void)drawInMTKView:(MTKView *)view
|
||||
{
|
||||
#if !defined(IMGUI_DISABLE)
|
||||
// Don't render if app is in background to avoid GPU submission errors
|
||||
// Allow rendering when inactive (control center, notifications)
|
||||
if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
io.DisplaySize.x = view.bounds.size.width;
|
||||
io.DisplaySize.y = view.bounds.size.height;
|
||||
|
||||
@@ -22,4 +22,7 @@
|
||||
enableHdr:(BOOL)enableHdr
|
||||
metricsHandler:(MetricsHandler _Nonnull)metricsHandler;
|
||||
|
||||
- (void)pauseRendering;
|
||||
- (void)resumeRendering;
|
||||
|
||||
@end
|
||||
|
||||
@@ -91,6 +91,11 @@
|
||||
}
|
||||
|
||||
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer {
|
||||
// Skip waiting when renderer is paused
|
||||
if (_renderer.isStopping) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Renderer obtains a nextDrawable, waiting if necessary
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[_renderer waitToRenderTo:layer];
|
||||
@@ -102,14 +107,20 @@
|
||||
|
||||
/// Draw frame (used by manual loop)
|
||||
- (void)renderTo:(nonnull CAMetalLayer *)layer {
|
||||
CFTimeInterval timeout = (1.0f / _framerate) - _renderer.averageGPUTime;
|
||||
Frame *frame = [_frameQueue dequeueWithTimeout:timeout];
|
||||
|
||||
if (!_renderer.isStopping) {
|
||||
CFTimeInterval timeout = (1.0f / _framerate) - _renderer.averageGPUTime;
|
||||
Frame *frame = [_frameQueue dequeueWithTimeout:timeout];
|
||||
// Only render if not paused
|
||||
if (frame) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[_renderer renderFrame:frame toLayer:layer];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// When paused, we still dequeue frames to prevent accumulation
|
||||
// but don't render them. Also sleep a bit to reduce CPU usage
|
||||
usleep(100000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +128,26 @@
|
||||
[_renderer drawableResize:size];
|
||||
}
|
||||
|
||||
- (void)pauseRendering {
|
||||
if (_displayLink) {
|
||||
_displayLink.paused = YES;
|
||||
}
|
||||
if (_renderer) {
|
||||
_renderer.isStopping = YES;
|
||||
}
|
||||
Log(LOG_I, @"[MetalViewController] Rendering paused");
|
||||
}
|
||||
|
||||
- (void)resumeRendering {
|
||||
if (_renderer) {
|
||||
_renderer.isStopping = NO;
|
||||
}
|
||||
if (_displayLink) {
|
||||
_displayLink.paused = NO;
|
||||
}
|
||||
Log(LOG_I, @"[MetalViewController] Rendering resumed");
|
||||
}
|
||||
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[super viewDidDisappear:animated];
|
||||
|
||||
|
||||
@@ -291,7 +291,9 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
||||
// we missed a callback
|
||||
// Log(LOG_W, @"*** slow frametime %.3f ms", frametime * 1000.0);
|
||||
}
|
||||
[[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:frametime * 1000.0];
|
||||
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
|
||||
[[ImGuiPlots sharedInstance] observeFloat:PLOT_FRAMETIME value:frametime * 1000.0];
|
||||
}
|
||||
}
|
||||
lastTargetLocal = targetLocal;
|
||||
|
||||
@@ -843,11 +845,24 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
||||
frameNumber:(int)frameNumber
|
||||
frameType:(int)frameType
|
||||
decodeStartTime:(CFTimeInterval)decodeStartTime {
|
||||
if (frameType == FRAME_TYPE_IDR || _decompressionSession == nil) {
|
||||
// Check if we need to create/recreate the decompression session
|
||||
BOOL needsNewSession = (frameType == FRAME_TYPE_IDR || _decompressionSession == nil);
|
||||
|
||||
// Also check if the session might have been invalidated by iOS during background
|
||||
if (!needsNewSession && _decompressionSession != nil) {
|
||||
Boolean isValid = VTDecompressionSessionCanAcceptFormatDescription(_decompressionSession, _formatDesc);
|
||||
if (!isValid) {
|
||||
Log(LOG_W, @"Decompression session is invalid, needs recreation");
|
||||
needsNewSession = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsNewSession) {
|
||||
[self setupDecompressionSession];
|
||||
}
|
||||
|
||||
if (_decompressionSession == nil) {
|
||||
Log(LOG_E, @"Failed to create decompression session");
|
||||
return kVTInvalidSessionErr;
|
||||
}
|
||||
|
||||
@@ -910,24 +925,26 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
||||
}
|
||||
int framesDropped = [self->_frameQueue enqueue:frame withSlackSize:3];
|
||||
|
||||
static PlotMetrics frameQueueMetrics = {};
|
||||
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_QUEUED_FRAMES value:[self->_frameQueue count] plotMetrics:&frameQueueMetrics];
|
||||
[self safeCopyMetricsTo:&self->_frameQueueMetrics from:&frameQueueMetrics];
|
||||
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
|
||||
static PlotMetrics frameQueueMetrics = {};
|
||||
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_QUEUED_FRAMES value:[self->_frameQueue count] plotMetrics:&frameQueueMetrics];
|
||||
[self safeCopyMetricsTo:&self->_frameQueueMetrics from:&frameQueueMetrics];
|
||||
|
||||
[[ImGuiPlots sharedInstance] observeFloat:PLOT_DROPPED value:framesDropped];
|
||||
[[ImGuiPlots sharedInstance] observeFloat:PLOT_DROPPED value:framesDropped];
|
||||
|
||||
// It's important we capture host metrics on the incoming thread, as this frame object
|
||||
// may have been dropped by the above enqueue
|
||||
static CFTimeInterval lastHostFrame = 0.0f;
|
||||
if (lastHostFrame != 0) {
|
||||
[[ImGuiPlots sharedInstance] observeFloat:PLOT_HOST_FRAMETIME value:(frame.pts - lastHostFrame) * 1000.0];
|
||||
// It's important we capture host metrics on the incoming thread, as this frame object
|
||||
// may have been dropped by the above enqueue
|
||||
static CFTimeInterval lastHostFrame = 0.0f;
|
||||
if (lastHostFrame != 0) {
|
||||
[[ImGuiPlots sharedInstance] observeFloat:PLOT_HOST_FRAMETIME value:(frame.pts - lastHostFrame) * 1000.0];
|
||||
}
|
||||
lastHostFrame = frame.pts;
|
||||
|
||||
// Decode time is not graphed because it is marked as hidden, but we can use the same mechanism for the value used by stats
|
||||
static PlotMetrics decodeMetrics = {};
|
||||
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_DECODE value:(CACurrentMediaTime() - decodeStartTime) * 1000.0 plotMetrics:&decodeMetrics];
|
||||
[self safeCopyMetricsTo:&self->_decodeMetrics from:&decodeMetrics];
|
||||
}
|
||||
lastHostFrame = frame.pts;
|
||||
|
||||
// Decode time is not graphed because it is marked as hidden, but we can use the same mechanism for the value used by stats
|
||||
static PlotMetrics decodeMetrics = {};
|
||||
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_DECODE value:(CACurrentMediaTime() - decodeStartTime) * 1000.0 plotMetrics:&decodeMetrics];
|
||||
[self safeCopyMetricsTo:&self->_decodeMetrics from:&decodeMetrics];
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1042,6 +1042,17 @@
|
||||
[self.pipController stopPictureInPicture];
|
||||
}
|
||||
|
||||
if ([_settings.renderingBackend intValue] == RENDER_METAL && self.metalViewController) {
|
||||
Log(LOG_I, @"Resuming Metal renderer on foreground");
|
||||
[self.metalViewController resumeRendering];
|
||||
}
|
||||
|
||||
if (self.imguiView && self.imguiView.mtkView && _settings.enableGraphs) {
|
||||
self.imguiView.mtkView.enableSetNeedsDisplay = YES;
|
||||
self.imguiView.mtkView.paused = NO;
|
||||
Log(LOG_I, @"Resuming ImGui renderer on foreground");
|
||||
}
|
||||
|
||||
[self->_streamMan.videoRenderer resetFramePacing];
|
||||
_isRestoringFromPiP = NO;
|
||||
}
|
||||
@@ -1052,6 +1063,17 @@
|
||||
NSLog(@"did enter background, %d, %@, %d", _settings.enablePIP, self.pipController, self.pipController.isPictureInPictureActive);
|
||||
if (_settings.enablePIP && self.pipController && self.pipController.isPictureInPictureActive) {
|
||||
//Log(LOG_I, @"PIP is active, not terminating stream");
|
||||
} else {
|
||||
if ([_settings.renderingBackend intValue] == RENDER_METAL && self.metalViewController) {
|
||||
Log(LOG_I, @"Pausing Metal renderer on background");
|
||||
[self.metalViewController pauseRendering];
|
||||
}
|
||||
|
||||
if (self.imguiView && self.imguiView.mtkView) {
|
||||
self.imguiView.mtkView.paused = YES;
|
||||
self.imguiView.mtkView.enableSetNeedsDisplay = NO;
|
||||
Log(LOG_I, @"Pausing ImGui renderer on background");
|
||||
}
|
||||
}
|
||||
|
||||
if (_inactivityTimer != nil) {
|
||||
|
||||
Reference in New Issue
Block a user