Files
VoidLink/VoidLink/Metal/MetalViewController.m
T
Acaki 628ce0f12e feat: use CAMetalDisplayLink for metal renderer to fix refresh rate issues
- update imgui to use stream fps instead of hardcoded 60
- hide renderer setting for iOS < 17 as CAMetalDisplayLink only supports later versions
2025-08-10 20:58:56 +08:00

134 lines
4.2 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
See the LICENSE.txt file for this sample’s licensing information.
Abstract:
The implementation of the cross-platform game view controller.
*/
#import "MetalViewController.h"
#import "FrameQueue.h"
#import "ImGuiRenderer.h"
#import "MetalVideoRenderer.h"
@implementation MetalViewController {
FrameQueue *_frameQueue;
float _framerate;
BOOL _enableHdr;
MetalView *_metalView;
MetalVideoRenderer *_renderer;
MetricsHandler _metricsHandler;
}
- (nonnull instancetype)initWithFrame:(CGRect)bounds framerate:(float)framerate enableHdr:(BOOL)enableHdr metricsHandler:(MetricsHandler)metricsHandler {
self = [super init];
if (self) {
_bounds = bounds;
_frameQueue = [FrameQueue sharedInstance];
_framerate = framerate;
_enableHdr = enableHdr;
_metricsHandler = metricsHandler;
}
return self;
}
- (void)loadView {
self.view = [[MetalView alloc] initWithFrame:_bounds];
Log(LOG_I, @"[MetalViewController] created MetalView %@", (MetalView *)self.view);
}
- (void)viewDidLoad {
[super viewDidLoad];
__block MetalView *view = (MetalView *)self.view;
if (!view) {
Log(LOG_E, @"The view attached to MetalViewController isn't a MetalView.");
return;
}
_metalView = view;
_metalView.delegate = self;
_metalView.framerate = _framerate;
// Select the device to render with.
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
if (!device) {
Log(LOG_E, @"Metal isn't supported on this device.");
self.view = [[UIView alloc] initWithFrame:self.view.frame];
return;
}
view.metalLayer.device = device;
// Initialize the renderer.
MetalVideoRenderer *renderer = [[MetalVideoRenderer alloc] initWithMetalDevice:device
drawablePixelFormat:MTLPixelFormatBGR10A2Unorm
framerate:self->_framerate];
if (!renderer) {
Log(LOG_E, @"The renderer couldn't be initialized.");
return;
}
self->_renderer = renderer;
Log(LOG_I, @"[MetalViewController] viewDidLoad, created renderer: %@", renderer);
// Initialize the renderer-dependent view properties.
view.metalLayer.pixelFormat = renderer.colorPixelFormat;
view.metalLayer.maximumDrawableCount = 3;
}
- (void)waitToRenderTo:(nonnull CAMetalLayer *)layer {
// Renderer obtains a nextDrawable, waiting if necessary
[_renderer waitToRenderTo:layer];
// If we don't have a frame yet, wait on that too
[_frameQueue waitForEnqueue];
}
// New method for CAMetalDisplayLink that provides the drawable
- (void)renderWithDrawable:(nonnull id<CAMetalDrawable>)drawable toLayer:(nonnull CAMetalLayer *)layer API_AVAILABLE(ios(17.0)) {
if (!_renderer.isStopping) {
CFTimeInterval timeout = (1.0f / _framerate) - _renderer.averageGPUTime;
Frame *frame = [_frameQueue dequeueWithTimeout:timeout];
if (frame) {
[_renderer renderFrame:frame withDrawable:drawable];
}
}
}
// Enhanced method that receives timing information from CAMetalDisplayLink
- (void)renderWithDrawable:(nonnull id<CAMetalDrawable>)drawable toLayer:(nonnull CAMetalLayer *)layer targetPresentationTimestamp:(CFTimeInterval)targetPresentationTimestamp API_AVAILABLE(ios(17.0)) {
if (!_renderer.isStopping) {
CFTimeInterval timeout = (1.0f / _framerate) - _renderer.averageGPUTime;
Frame *frame = [_frameQueue dequeueWithTimeout:timeout];
if (frame) {
[_renderer renderFrame:frame withDrawable:drawable targetPresentationTimestamp:targetPresentationTimestamp];
}
}
}
- (void)drawableResize:(CGSize)size {
[_renderer drawableResize:size];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
Log(LOG_I, @"[MetalViewController] viewDidDisappear");
// Shutdown the renderer first
[_renderer shutdown];
_renderer = nil;
// Then shutdown the view's display link
if (_metalView) {
[_metalView shutdown];
}
}
#if TARGET_OS_IOS
// Hides the Home indicator button automatically.
- (BOOL)prefersHomeIndicatorAutoHidden {
return YES;
}
#endif
@end