feat: pause Metal renderer and graph when app is in background
This commit is contained in:
1 parent
a2c4d440db
commit
c8f61b0b87
5 files changed
+98
-19
No files matched your search
@@ -128,6 +128,12 @@
|
|||||||
- (void)drawInMTKView:(MTKView *)view
|
- (void)drawInMTKView:(MTKView *)view
|
||||||
{
|
{
|
||||||
#if !defined(IMGUI_DISABLE)
|
#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();
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
io.DisplaySize.x = view.bounds.size.width;
|
io.DisplaySize.x = view.bounds.size.width;
|
||||||
io.DisplaySize.y = view.bounds.size.height;
|
io.DisplaySize.y = view.bounds.size.height;
|
||||||
|
|||||||
@@ -22,4 +22,7 @@
|
|||||||
enableHdr:(BOOL)enableHdr
|
enableHdr:(BOOL)enableHdr
|
||||||
metricsHandler:(MetricsHandler _Nonnull)metricsHandler;
|
metricsHandler:(MetricsHandler _Nonnull)metricsHandler;
|
||||||
|
|
||||||
|
- (void)pauseRendering;
|
||||||
|
- (void)resumeRendering;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -91,6 +91,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer {
|
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer {
|
||||||
|
// Skip waiting when renderer is paused
|
||||||
|
if (_renderer.isStopping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Renderer obtains a nextDrawable, waiting if necessary
|
// Renderer obtains a nextDrawable, waiting if necessary
|
||||||
if (@available(iOS 13.0, *)) {
|
if (@available(iOS 13.0, *)) {
|
||||||
[_renderer waitToRenderTo:layer];
|
[_renderer waitToRenderTo:layer];
|
||||||
@@ -102,14 +107,20 @@
|
|||||||
|
|
||||||
/// Draw frame (used by manual loop)
|
/// Draw frame (used by manual loop)
|
||||||
- (void)renderTo:(nonnull CAMetalLayer *)layer {
|
- (void)renderTo:(nonnull CAMetalLayer *)layer {
|
||||||
|
CFTimeInterval timeout = (1.0f / _framerate) - _renderer.averageGPUTime;
|
||||||
|
Frame *frame = [_frameQueue dequeueWithTimeout:timeout];
|
||||||
|
|
||||||
if (!_renderer.isStopping) {
|
if (!_renderer.isStopping) {
|
||||||
CFTimeInterval timeout = (1.0f / _framerate) - _renderer.averageGPUTime;
|
// Only render if not paused
|
||||||
Frame *frame = [_frameQueue dequeueWithTimeout:timeout];
|
|
||||||
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 {
|
||||||
|
// 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];
|
[_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 {
|
- (void)viewDidDisappear:(BOOL)animated {
|
||||||
[super viewDidDisappear:animated];
|
[super viewDidDisappear:animated];
|
||||||
|
|
||||||
|
|||||||
@@ -291,7 +291,9 @@ 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);
|
||||||
}
|
}
|
||||||
[[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;
|
lastTargetLocal = targetLocal;
|
||||||
|
|
||||||
@@ -843,11 +845,24 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
frameNumber:(int)frameNumber
|
frameNumber:(int)frameNumber
|
||||||
frameType:(int)frameType
|
frameType:(int)frameType
|
||||||
decodeStartTime:(CFTimeInterval)decodeStartTime {
|
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];
|
[self setupDecompressionSession];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_decompressionSession == nil) {
|
if (_decompressionSession == nil) {
|
||||||
|
Log(LOG_E, @"Failed to create decompression session");
|
||||||
return kVTInvalidSessionErr;
|
return kVTInvalidSessionErr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -910,24 +925,26 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit);
|
|||||||
}
|
}
|
||||||
int framesDropped = [self->_frameQueue enqueue:frame withSlackSize:3];
|
int framesDropped = [self->_frameQueue enqueue:frame withSlackSize:3];
|
||||||
|
|
||||||
static PlotMetrics frameQueueMetrics = {};
|
if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
|
||||||
[[ImGuiPlots sharedInstance] observeFloatReturnMetrics:PLOT_QUEUED_FRAMES value:[self->_frameQueue count] plotMetrics:&frameQueueMetrics];
|
static PlotMetrics frameQueueMetrics = {};
|
||||||
[self safeCopyMetricsTo:&self->_frameQueueMetrics from:&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
|
// It's important we capture host metrics on the incoming thread, as this frame object
|
||||||
// may have been dropped by the above enqueue
|
// may have been dropped by the above enqueue
|
||||||
static CFTimeInterval lastHostFrame = 0.0f;
|
static CFTimeInterval lastHostFrame = 0.0f;
|
||||||
if (lastHostFrame != 0) {
|
if (lastHostFrame != 0) {
|
||||||
[[ImGuiPlots sharedInstance] observeFloat:PLOT_HOST_FRAMETIME value:(frame.pts - lastHostFrame) * 1000.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];
|
[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];
|
[self->_streamMan.videoRenderer resetFramePacing];
|
||||||
_isRestoringFromPiP = NO;
|
_isRestoringFromPiP = NO;
|
||||||
}
|
}
|
||||||
@@ -1052,6 +1063,17 @@
|
|||||||
NSLog(@"did enter background, %d, %@, %d", _settings.enablePIP, self.pipController, self.pipController.isPictureInPictureActive);
|
NSLog(@"did enter background, %d, %@, %d", _settings.enablePIP, self.pipController, self.pipController.isPictureInPictureActive);
|
||||||
if (_settings.enablePIP && self.pipController && self.pipController.isPictureInPictureActive) {
|
if (_settings.enablePIP && self.pipController && self.pipController.isPictureInPictureActive) {
|
||||||
//Log(LOG_I, @"PIP is active, not terminating stream");
|
//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) {
|
if (_inactivityTimer != nil) {
|
||||||
|
|||||||
Reference in new issue
Block a user