Skip to content

Commit 6393109

Browse files
committed
apple: small cleanups to the metal video driver. don't use it.
fixes #8655 #8983 #15747 #17860 #18442 #18408
1 parent e18c297 commit 6393109

File tree

5 files changed

+316
-89
lines changed

5 files changed

+316
-89
lines changed

gfx/common/metal/menu_pipeline.metal

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,10 @@ fragment float4 ribbon_fragment(RibbonOutIn in [[ stage_in ]])
111111
const float3 up = float3(0.0, 0.0, 1.0);
112112
float3 x = dfdx(in.vEC);
113113
float3 y = dfdy(in.vEC);
114+
y = -y; /* Flip Y derivative to match Vulkan's yflip */
114115
float3 normal = normalize(cross(x, y));
115116
float c = 1.0 - dot(normal, up);
116-
c = (1.0 - cos(c * c)) / 13.0;
117+
c = (1.0 - cos(c * c)) / 3.0;
117118
return float4(c, c, c, 1.0);
118119
}
119120

@@ -195,6 +196,7 @@ fragment float4 bokeh_fragment(FontFragmentIn in [[ stage_in ]],
195196
{
196197
float speed = constants.time * 4.0;
197198
float2 uv = -1.0 + 2.0 * in.position.xy / constants.outputSize;
199+
uv.y = -uv.y; /* Flip Y to match Vulkan's yflip */
198200
uv.x *= constants.outputSize.x / constants.outputSize.y;
199201
float3 color = float3(0.0);
200202

@@ -270,7 +272,7 @@ fragment float4 snowflake_fragment(FontFragmentIn in [[ stage_in
270272
uv = uv * 2.0 - 1.0;
271273
float2 p = uv;
272274
p.x *= constants.outputSize.x / constants.outputSize.y;
273-
//p.y = -p.y;
275+
p.y = -p.y; /* Flip Y so snowflakes fall down */
274276

275277
float c = snowflake::col(p, constants);
276278
return float4(c,c,c,c);

gfx/common/metal/metal_common.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@
2525

2626
#include "../../gfx_display.h"
2727

28-
/* TODO/FIXME: implement triple buffering */
29-
/*! @brief maximum inflight frames */
30-
#define MAX_INFLIGHT 1
28+
/*! @brief maximum inflight frames for triple buffering */
29+
#define MAX_INFLIGHT 3
3130
#define CHAIN_LENGTH 3
3231

3332
/* macOS requires constants in a buffer to have a 256 byte alignment. */
@@ -139,6 +138,10 @@ typedef NS_ENUM(NSUInteger, ViewportResetMode) {
139138
/*! @brief end commits the command buffer */
140139
- (void)end;
141140

141+
/*! @brief swapBuffers acquires the next drawable, blocking if needed for vsync.
142+
* This should be called after end to match Vulkan's swap_buffers timing. */
143+
- (void)swapBuffers;
144+
142145
- (void)setRotation:(unsigned)rotation;
143146
- (bool)readBackBuffer:(uint8_t *)buffer;
144147

gfx/common/metal/metal_renderer.m

Lines changed: 90 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ - (instancetype)initWithDevice:(id<MTLDevice>)d
178178
_layer.framebufferOnly = NO;
179179
_layer.displaySyncEnabled = YES;
180180
#endif
181+
/* Configure drawable pool for triple-buffering */
182+
if (@available(iOS 13.0, macOS 10.15.4, tvOS 13.0, *))
183+
_layer.maximumDrawableCount = MAX_INFLIGHT;
181184
_library = l;
182185
_commandQueue = [_device newCommandQueue];
183186
_clearColor = MTLClearColorMake(0, 0, 0, 1);
@@ -319,6 +322,12 @@ - (bool)_initClearState
319322
psd.vertexFunction = [_library newFunctionWithName:@"stock_vertex"];
320323
psd.fragmentFunction = [_library newFunctionWithName:@"stock_fragment_color"];
321324

325+
if (!psd.vertexFunction || !psd.fragmentFunction)
326+
{
327+
RARCH_ERR("[Metal] Failed to load clear state shader functions.\n");
328+
return NO;
329+
}
330+
322331
_clearState = [_device newRenderPipelineStateWithDescriptor:psd error:&err];
323332
if (err != nil)
324333
{
@@ -349,6 +358,12 @@ - (bool)_initMenuStates
349358
psd.vertexFunction = [_library newFunctionWithName:@"stock_vertex"];
350359
psd.fragmentFunction = [_library newFunctionWithName:@"stock_fragment"];
351360

361+
if (!psd.vertexFunction || !psd.fragmentFunction)
362+
{
363+
RARCH_ERR("[Metal] Failed to load stock shader functions.\n");
364+
return NO;
365+
}
366+
352367
_states[VIDEO_SHADER_STOCK_BLEND][0] = [_device newRenderPipelineStateWithDescriptor:psd error:&err];
353368
if (err != nil)
354369
{
@@ -572,10 +587,22 @@ - (Texture *)newTexture:(struct texture_image)image filter:(enum texture_filter_
572587

573588
- (void)convertFormat:(RPixelFormat)fmt from:(id<MTLTexture>)src to:(id<MTLTexture>)dst
574589
{
575-
assert(src.width == dst.width && src.height == dst.height);
576-
assert(fmt >= 0 && fmt < RPixelFormatCount);
590+
if (src.width != dst.width || src.height != dst.height)
591+
{
592+
RARCH_ERR("[Metal] convertFormat: texture dimensions mismatch\n");
593+
return;
594+
}
595+
if (fmt < 0 || fmt >= RPixelFormatCount)
596+
{
597+
RARCH_ERR("[Metal] convertFormat: invalid pixel format %u\n", (unsigned)fmt);
598+
return;
599+
}
577600
Filter *conv = _filters[fmt];
578-
assert(conv != nil);
601+
if (!conv)
602+
{
603+
RARCH_ERR("[Metal] convertFormat: no filter for format %u\n", (unsigned)fmt);
604+
return;
605+
}
579606
[conv apply:self.blitCommandBuffer in:src out:dst];
580607
}
581608

@@ -656,24 +683,46 @@ - (bool)readBackBuffer:(uint8_t *)buffer
656683

657684
- (void)begin
658685
{
659-
assert(_commandBuffer == nil);
660-
dispatch_semaphore_wait(_inflightSemaphore, DISPATCH_TIME_FOREVER);
686+
if (_commandBuffer != nil)
687+
{
688+
RARCH_WARN("[Metal] begin called with active command buffer - resetting\n");
689+
_commandBuffer = nil;
690+
}
691+
692+
/* Don't use semaphore for frame pacing - let nextDrawable handle it.
693+
* CAMetalLayer.nextDrawable will block if no drawable is available,
694+
* which naturally paces us to the display refresh rate.
695+
* Using a semaphore on top of this causes timing mismatches because
696+
* the semaphore signals on presentation but the drawable isn't
697+
* released until the NEXT vsync. */
698+
661699
_commandBuffer = [_commandQueue commandBuffer];
662700
_commandBuffer.label = @"Frame command buffer";
663701
_backBuffer = nil;
664702
}
665703

666704
- (id<MTLRenderCommandEncoder>)rce
667705
{
668-
assert(_commandBuffer != nil);
706+
if (_commandBuffer == nil)
707+
{
708+
RARCH_ERR("[Metal] rce called without active command buffer\n");
709+
return nil;
710+
}
669711
if (_rce == nil)
670712
{
713+
id<CAMetalDrawable> drawable = self.nextDrawable;
714+
if (!drawable || !drawable.texture)
715+
{
716+
RARCH_WARN("[Metal] Failed to acquire drawable - frame dropped\n");
717+
return nil;
718+
}
719+
671720
MTLRenderPassDescriptor *rpd = [MTLRenderPassDescriptor new];
672721
rpd.colorAttachments[0].clearColor = _clearColor;
673722
rpd.colorAttachments[0].loadAction = MTLLoadActionClear;
674-
rpd.colorAttachments[0].texture = self.nextDrawable.texture;
723+
rpd.colorAttachments[0].texture = drawable.texture;
675724
if (_captureEnabled)
676-
_backBuffer = self.nextDrawable.texture;
725+
_backBuffer = drawable.texture;
677726
_rce = [_commandBuffer renderCommandEncoderWithDescriptor:rpd];
678727
_rce.label = @"Frame command encoder";
679728
}
@@ -729,7 +778,11 @@ - (void)drawQuadX:(float)x y:(float)y w:(float)w h:(float)h
729778

730779
- (void)end
731780
{
732-
assert(_commandBuffer != nil);
781+
if (_commandBuffer == nil)
782+
{
783+
RARCH_WARN("[Metal] end called without active command buffer\n");
784+
return;
785+
}
733786

734787
[_chain[_currentChain] commitRanges];
735788

@@ -743,9 +796,10 @@ - (void)end
743796
[bce endEncoding];
744797
}
745798
#endif
746-
/* Pending blits for mipmaps or render passes for slang shaders */
799+
/* Pending blits for mipmaps or render passes for slang shaders.
800+
* Metal command queues guarantee commit-order execution, so we don't
801+
* need to block the CPU waiting for completion. */
747802
[_blitCommandBuffer commit];
748-
[_blitCommandBuffer waitUntilCompleted];
749803
_blitCommandBuffer = nil;
750804
}
751805

@@ -755,23 +809,40 @@ - (void)end
755809
_rce = nil;
756810
}
757811

758-
__block dispatch_semaphore_t inflight = _inflightSemaphore;
759-
[_commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> _) {
760-
dispatch_semaphore_signal(inflight);
761-
}];
812+
id<CAMetalDrawable> drawable = self.nextDrawable;
762813

763-
if (self.nextDrawable)
814+
if (drawable)
764815
{
765-
[_commandBuffer presentDrawable:self.nextDrawable];
816+
/* Use addScheduledHandler to present, following Apple's recommendation.
817+
* According to Apple (and used by MoltenVK), it is more performant to call
818+
* [drawable present] from within a scheduled-handler than to use
819+
* [commandBuffer presentDrawable:]. This provides better frame pacing
820+
* because presentation is queued when the command buffer is scheduled
821+
* (added to GPU queue), not when it completes. */
822+
[_commandBuffer addScheduledHandler:^(id<MTLCommandBuffer> _Nonnull buffer) {
823+
[drawable present];
824+
}];
766825
}
767826

768827
[_commandBuffer commit];
769828

770829
_commandBuffer = nil;
771-
_drawable = nil;
772830
[self _nextChain];
773831
}
774832

833+
- (void)swapBuffers
834+
{
835+
/* Acquire the next drawable after presentation, matching Vulkan's
836+
* swap_buffers timing where acquisition happens AFTER presenting.
837+
*
838+
* We explicitly clear _drawable first to force a fresh acquisition.
839+
* nextDrawable will block if no drawable is available (all 3 are
840+
* in-flight), which naturally paces us to the display refresh rate.
841+
* This blocking behavior is intentional for proper frame pacing. */
842+
_drawable = nil;
843+
_drawable = _layer.nextDrawable;
844+
}
845+
775846
- (bool)allocRange:(BufferRange *)range length:(NSUInteger)length
776847
{
777848
return [_chain[_currentChain] allocRange:range length:length];
@@ -981,7 +1052,7 @@ - (void)apply:(id<MTLCommandBuffer>)cb inBuf:(id<MTLBuffer>)tin outTex:(id<MTLTe
9811052
[self.delegate configure:ce];
9821053

9831054
MTLSize size = MTLSizeMake(32, 1, 1);
984-
MTLSize count = MTLSizeMake((tin.length + 00) / 32, 1, 1);
1055+
MTLSize count = MTLSizeMake((tin.length + 31) / 32, 1, 1);
9851056

9861057
[ce dispatchThreadgroups:count threadsPerThreadgroup:size];
9871058
[ce endEncoding];

gfx/common/metal_common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ extern MTLPixelFormat SelectOptimalPixelFormat(MTLPixelFormat fmt);
5959

6060
- (void)setFilteringIndex:(int)index smooth:(bool)smooth;
6161
- (BOOL)setShaderFromPath:(NSString *)path;
62+
- (void)clearShader;
6263
- (void)updateFrame:(void const *)src pitch:(NSUInteger)pitch;
6364
- (bool)readViewport:(uint8_t *)buffer isIdle:(bool)isIdle;
6465

0 commit comments

Comments
 (0)