summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/render/metal/SDL_render_metal.m
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/render/metal/SDL_render_metal.m')
-rw-r--r--contrib/SDL-3.2.8/src/render/metal/SDL_render_metal.m2196
1 files changed, 2196 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/render/metal/SDL_render_metal.m b/contrib/SDL-3.2.8/src/render/metal/SDL_render_metal.m
new file mode 100644
index 0000000..63d9770
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/render/metal/SDL_render_metal.m
@@ -0,0 +1,2196 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_RENDER_METAL
24
25#include "../SDL_sysrender.h"
26#include "../../video/SDL_pixels_c.h"
27
28#import <CoreVideo/CoreVideo.h>
29#import <Metal/Metal.h>
30#import <QuartzCore/CAMetalLayer.h>
31
32#ifdef SDL_VIDEO_DRIVER_COCOA
33#import <AppKit/NSWindow.h>
34#import <AppKit/NSView.h>
35#endif
36#ifdef SDL_VIDEO_DRIVER_UIKIT
37#import <UIKit/UIKit.h>
38#endif
39
40// Regenerate these with build-metal-shaders.sh
41#ifdef SDL_PLATFORM_MACOS
42#include "SDL_shaders_metal_macos.h"
43#elif defined(SDL_PLATFORM_TVOS)
44#if TARGET_OS_SIMULATOR
45#include "SDL_shaders_metal_tvsimulator.h"
46#else
47#include "SDL_shaders_metal_tvos.h"
48#endif
49#else
50#if TARGET_OS_SIMULATOR
51#include "SDL_shaders_metal_iphonesimulator.h"
52#else
53#include "SDL_shaders_metal_ios.h"
54#endif
55#endif
56
57// Apple Metal renderer implementation
58
59// macOS requires constants in a buffer to have a 256 byte alignment.
60// Use native type alignments from https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
61#if defined(SDL_PLATFORM_MACOS) || TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST
62#define CONSTANT_ALIGN(x) (256)
63#else
64#define CONSTANT_ALIGN(x) (x < 4 ? 4 : x)
65#endif
66
67#define DEVICE_ALIGN(x) (x < 4 ? 4 : x)
68
69#define ALIGN_CONSTANTS(align, size) ((size + CONSTANT_ALIGN(align) - 1) & (~(CONSTANT_ALIGN(align) - 1)))
70
71static const size_t CONSTANTS_OFFSET_INVALID = 0xFFFFFFFF;
72static const size_t CONSTANTS_OFFSET_IDENTITY = 0;
73static const size_t CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_IDENTITY + sizeof(float) * 16);
74static const size_t CONSTANTS_OFFSET_DECODE_BT601_LIMITED = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM + sizeof(float) * 16);
75static const size_t CONSTANTS_OFFSET_DECODE_BT601_FULL = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_DECODE_BT601_LIMITED + sizeof(float) * 4 * 4);
76static const size_t CONSTANTS_OFFSET_DECODE_BT709_LIMITED = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_DECODE_BT601_FULL + sizeof(float) * 4 * 4);
77static const size_t CONSTANTS_OFFSET_DECODE_BT709_FULL = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_DECODE_BT709_LIMITED + sizeof(float) * 4 * 4);
78static const size_t CONSTANTS_OFFSET_DECODE_BT2020_LIMITED = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_DECODE_BT709_FULL + sizeof(float) * 4 * 4);
79static const size_t CONSTANTS_OFFSET_DECODE_BT2020_FULL = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_DECODE_BT2020_LIMITED + sizeof(float) * 4 * 4);
80static const size_t CONSTANTS_LENGTH = CONSTANTS_OFFSET_DECODE_BT2020_FULL + sizeof(float) * 4 * 4;
81
82// Sampler types
83typedef enum
84{
85 SDL_METAL_SAMPLER_NEAREST_CLAMP,
86 SDL_METAL_SAMPLER_NEAREST_WRAP,
87 SDL_METAL_SAMPLER_LINEAR_CLAMP,
88 SDL_METAL_SAMPLER_LINEAR_WRAP,
89 SDL_NUM_METAL_SAMPLERS
90} SDL_METAL_sampler_type;
91
92typedef enum SDL_MetalVertexFunction
93{
94 SDL_METAL_VERTEX_SOLID,
95 SDL_METAL_VERTEX_COPY,
96} SDL_MetalVertexFunction;
97
98typedef enum SDL_MetalFragmentFunction
99{
100 SDL_METAL_FRAGMENT_SOLID = 0,
101 SDL_METAL_FRAGMENT_COPY,
102 SDL_METAL_FRAGMENT_YUV,
103 SDL_METAL_FRAGMENT_NV12,
104 SDL_METAL_FRAGMENT_COUNT,
105} SDL_MetalFragmentFunction;
106
107typedef struct METAL_PipelineState
108{
109 SDL_BlendMode blendMode;
110 void *pipe;
111} METAL_PipelineState;
112
113typedef struct METAL_PipelineCache
114{
115 METAL_PipelineState *states;
116 int count;
117 SDL_MetalVertexFunction vertexFunction;
118 SDL_MetalFragmentFunction fragmentFunction;
119 MTLPixelFormat renderTargetFormat;
120 const char *label;
121} METAL_PipelineCache;
122
123/* Each shader combination used by drawing functions has a separate pipeline
124 * cache, and we have a separate list of caches for each render target pixel
125 * format. This is more efficient than iterating over a global cache to find
126 * the pipeline based on the specified shader combination and RT pixel format,
127 * since we know what the RT pixel format is when we set the render target, and
128 * we know what the shader combination is inside each drawing function's code. */
129typedef struct METAL_ShaderPipelines
130{
131 MTLPixelFormat renderTargetFormat;
132 METAL_PipelineCache caches[SDL_METAL_FRAGMENT_COUNT];
133} METAL_ShaderPipelines;
134
135@interface SDL3METAL_RenderData : NSObject
136@property(nonatomic, retain) id<MTLDevice> mtldevice;
137@property(nonatomic, retain) id<MTLCommandQueue> mtlcmdqueue;
138@property(nonatomic, retain) id<MTLCommandBuffer> mtlcmdbuffer;
139@property(nonatomic, retain) id<MTLRenderCommandEncoder> mtlcmdencoder;
140@property(nonatomic, retain) id<MTLLibrary> mtllibrary;
141@property(nonatomic, retain) id<CAMetalDrawable> mtlbackbuffer;
142@property(nonatomic, retain) NSMutableArray<id<MTLSamplerState>> *mtlsamplers;
143@property(nonatomic, retain) id<MTLBuffer> mtlbufconstants;
144@property(nonatomic, retain) id<MTLBuffer> mtlbufquadindices;
145@property(nonatomic, assign) SDL_MetalView mtlview;
146@property(nonatomic, retain) CAMetalLayer *mtllayer;
147@property(nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
148@property(nonatomic, assign) METAL_ShaderPipelines *activepipelines;
149@property(nonatomic, assign) METAL_ShaderPipelines *allpipelines;
150@property(nonatomic, assign) int pipelinescount;
151@end
152
153@implementation SDL3METAL_RenderData
154@end
155
156@interface SDL3METAL_TextureData : NSObject
157@property(nonatomic, retain) id<MTLTexture> mtltexture;
158@property(nonatomic, retain) id<MTLTexture> mtltextureUv;
159@property(nonatomic, assign) SDL_MetalFragmentFunction fragmentFunction;
160#ifdef SDL_HAVE_YUV
161@property(nonatomic, assign) BOOL yuv;
162@property(nonatomic, assign) BOOL nv12;
163@property(nonatomic, assign) size_t conversionBufferOffset;
164#endif
165@property(nonatomic, assign) BOOL hasdata;
166@property(nonatomic, retain) id<MTLBuffer> lockedbuffer;
167@property(nonatomic, assign) SDL_Rect lockedrect;
168@end
169
170@implementation SDL3METAL_TextureData
171@end
172
173static const MTLBlendOperation invalidBlendOperation = (MTLBlendOperation)0xFFFFFFFF;
174static const MTLBlendFactor invalidBlendFactor = (MTLBlendFactor)0xFFFFFFFF;
175
176static MTLBlendOperation GetBlendOperation(SDL_BlendOperation operation)
177{
178 switch (operation) {
179 case SDL_BLENDOPERATION_ADD:
180 return MTLBlendOperationAdd;
181 case SDL_BLENDOPERATION_SUBTRACT:
182 return MTLBlendOperationSubtract;
183 case SDL_BLENDOPERATION_REV_SUBTRACT:
184 return MTLBlendOperationReverseSubtract;
185 case SDL_BLENDOPERATION_MINIMUM:
186 return MTLBlendOperationMin;
187 case SDL_BLENDOPERATION_MAXIMUM:
188 return MTLBlendOperationMax;
189 default:
190 return invalidBlendOperation;
191 }
192}
193
194static MTLBlendFactor GetBlendFactor(SDL_BlendFactor factor)
195{
196 switch (factor) {
197 case SDL_BLENDFACTOR_ZERO:
198 return MTLBlendFactorZero;
199 case SDL_BLENDFACTOR_ONE:
200 return MTLBlendFactorOne;
201 case SDL_BLENDFACTOR_SRC_COLOR:
202 return MTLBlendFactorSourceColor;
203 case SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR:
204 return MTLBlendFactorOneMinusSourceColor;
205 case SDL_BLENDFACTOR_SRC_ALPHA:
206 return MTLBlendFactorSourceAlpha;
207 case SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA:
208 return MTLBlendFactorOneMinusSourceAlpha;
209 case SDL_BLENDFACTOR_DST_COLOR:
210 return MTLBlendFactorDestinationColor;
211 case SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR:
212 return MTLBlendFactorOneMinusDestinationColor;
213 case SDL_BLENDFACTOR_DST_ALPHA:
214 return MTLBlendFactorDestinationAlpha;
215 case SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA:
216 return MTLBlendFactorOneMinusDestinationAlpha;
217 default:
218 return invalidBlendFactor;
219 }
220}
221
222static NSString *GetVertexFunctionName(SDL_MetalVertexFunction function)
223{
224 switch (function) {
225 case SDL_METAL_VERTEX_SOLID:
226 return @"SDL_Solid_vertex";
227 case SDL_METAL_VERTEX_COPY:
228 return @"SDL_Copy_vertex";
229 default:
230 return nil;
231 }
232}
233
234static NSString *GetFragmentFunctionName(SDL_MetalFragmentFunction function)
235{
236 switch (function) {
237 case SDL_METAL_FRAGMENT_SOLID:
238 return @"SDL_Solid_fragment";
239 case SDL_METAL_FRAGMENT_COPY:
240 return @"SDL_Copy_fragment";
241 case SDL_METAL_FRAGMENT_YUV:
242 return @"SDL_YUV_fragment";
243 case SDL_METAL_FRAGMENT_NV12:
244 return @"SDL_NV12_fragment";
245 default:
246 return nil;
247 }
248}
249
250static id<MTLRenderPipelineState> MakePipelineState(SDL3METAL_RenderData *data, METAL_PipelineCache *cache,
251 NSString *blendlabel, SDL_BlendMode blendmode)
252{
253 MTLRenderPipelineDescriptor *mtlpipedesc;
254 MTLVertexDescriptor *vertdesc;
255 MTLRenderPipelineColorAttachmentDescriptor *rtdesc;
256 NSError *err = nil;
257 id<MTLRenderPipelineState> state;
258 METAL_PipelineState pipeline;
259 METAL_PipelineState *states;
260
261 id<MTLFunction> mtlvertfn = [data.mtllibrary newFunctionWithName:GetVertexFunctionName(cache->vertexFunction)];
262 id<MTLFunction> mtlfragfn = [data.mtllibrary newFunctionWithName:GetFragmentFunctionName(cache->fragmentFunction)];
263 SDL_assert(mtlvertfn != nil);
264 SDL_assert(mtlfragfn != nil);
265
266 mtlpipedesc = [[MTLRenderPipelineDescriptor alloc] init];
267 mtlpipedesc.vertexFunction = mtlvertfn;
268 mtlpipedesc.fragmentFunction = mtlfragfn;
269
270 vertdesc = [MTLVertexDescriptor vertexDescriptor];
271
272 switch (cache->vertexFunction) {
273 case SDL_METAL_VERTEX_SOLID:
274 // position (float2), color (float4)
275 vertdesc.layouts[0].stride = sizeof(float) * 2 + sizeof(float) * 4;
276 vertdesc.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
277
278 vertdesc.attributes[0].format = MTLVertexFormatFloat2;
279 vertdesc.attributes[0].offset = 0;
280 vertdesc.attributes[0].bufferIndex = 0;
281
282 vertdesc.attributes[1].format = MTLVertexFormatFloat4;
283 vertdesc.attributes[1].offset = sizeof(float) * 2;
284 vertdesc.attributes[1].bufferIndex = 0;
285
286 break;
287 case SDL_METAL_VERTEX_COPY:
288 // position (float2), color (float4), texcoord (float2)
289 vertdesc.layouts[0].stride = sizeof(float) * 2 + sizeof(float) * 4 + sizeof(float) * 2;
290 vertdesc.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
291
292 vertdesc.attributes[0].format = MTLVertexFormatFloat2;
293 vertdesc.attributes[0].offset = 0;
294 vertdesc.attributes[0].bufferIndex = 0;
295
296 vertdesc.attributes[1].format = MTLVertexFormatFloat4;
297 vertdesc.attributes[1].offset = sizeof(float) * 2;
298 vertdesc.attributes[1].bufferIndex = 0;
299
300 vertdesc.attributes[2].format = MTLVertexFormatFloat2;
301 vertdesc.attributes[2].offset = sizeof(float) * 2 + sizeof(float) * 4;
302 vertdesc.attributes[2].bufferIndex = 0;
303 break;
304 }
305
306 mtlpipedesc.vertexDescriptor = vertdesc;
307
308 rtdesc = mtlpipedesc.colorAttachments[0];
309 rtdesc.pixelFormat = cache->renderTargetFormat;
310
311 if (blendmode != SDL_BLENDMODE_NONE) {
312 rtdesc.blendingEnabled = YES;
313 rtdesc.sourceRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcColorFactor(blendmode));
314 rtdesc.destinationRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeDstColorFactor(blendmode));
315 rtdesc.rgbBlendOperation = GetBlendOperation(SDL_GetBlendModeColorOperation(blendmode));
316 rtdesc.sourceAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcAlphaFactor(blendmode));
317 rtdesc.destinationAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeDstAlphaFactor(blendmode));
318 rtdesc.alphaBlendOperation = GetBlendOperation(SDL_GetBlendModeAlphaOperation(blendmode));
319 } else {
320 rtdesc.blendingEnabled = NO;
321 }
322
323 mtlpipedesc.label = [@(cache->label) stringByAppendingString:blendlabel];
324
325 state = [data.mtldevice newRenderPipelineStateWithDescriptor:mtlpipedesc error:&err];
326 SDL_assert(err == nil);
327
328 pipeline.blendMode = blendmode;
329 pipeline.pipe = (void *)CFBridgingRetain(state);
330
331 states = SDL_realloc(cache->states, (cache->count + 1) * sizeof(pipeline));
332
333 if (states) {
334 states[cache->count++] = pipeline;
335 cache->states = states;
336 return (__bridge id<MTLRenderPipelineState>)pipeline.pipe;
337 } else {
338 CFBridgingRelease(pipeline.pipe);
339 return NULL;
340 }
341}
342
343static void MakePipelineCache(SDL3METAL_RenderData *data, METAL_PipelineCache *cache, const char *label,
344 MTLPixelFormat rtformat, SDL_MetalVertexFunction vertfn, SDL_MetalFragmentFunction fragfn)
345{
346 SDL_zerop(cache);
347
348 cache->vertexFunction = vertfn;
349 cache->fragmentFunction = fragfn;
350 cache->renderTargetFormat = rtformat;
351 cache->label = label;
352
353 /* Create pipeline states for the default blend modes. Custom blend modes
354 * will be added to the cache on-demand. */
355 MakePipelineState(data, cache, @" (blend=none)", SDL_BLENDMODE_NONE);
356 MakePipelineState(data, cache, @" (blend=blend)", SDL_BLENDMODE_BLEND);
357 MakePipelineState(data, cache, @" (blend=add)", SDL_BLENDMODE_ADD);
358 MakePipelineState(data, cache, @" (blend=mod)", SDL_BLENDMODE_MOD);
359 MakePipelineState(data, cache, @" (blend=mul)", SDL_BLENDMODE_MUL);
360}
361
362static void DestroyPipelineCache(METAL_PipelineCache *cache)
363{
364 if (cache != NULL) {
365 for (int i = 0; i < cache->count; i++) {
366 CFBridgingRelease(cache->states[i].pipe);
367 }
368
369 SDL_free(cache->states);
370 }
371}
372
373void MakeShaderPipelines(SDL3METAL_RenderData *data, METAL_ShaderPipelines *pipelines, MTLPixelFormat rtformat)
374{
375 SDL_zerop(pipelines);
376
377 pipelines->renderTargetFormat = rtformat;
378
379 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_SOLID], "SDL primitives pipeline", rtformat, SDL_METAL_VERTEX_SOLID, SDL_METAL_FRAGMENT_SOLID);
380 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_COPY], "SDL copy pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_COPY);
381 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_YUV], "SDL YUV pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_YUV);
382 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV12], "SDL NV12 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV12);
383}
384
385static METAL_ShaderPipelines *ChooseShaderPipelines(SDL3METAL_RenderData *data, MTLPixelFormat rtformat)
386{
387 METAL_ShaderPipelines *allpipelines = data.allpipelines;
388 int count = data.pipelinescount;
389
390 for (int i = 0; i < count; i++) {
391 if (allpipelines[i].renderTargetFormat == rtformat) {
392 return &allpipelines[i];
393 }
394 }
395
396 allpipelines = SDL_realloc(allpipelines, (count + 1) * sizeof(METAL_ShaderPipelines));
397
398 if (allpipelines == NULL) {
399 return NULL;
400 }
401
402 MakeShaderPipelines(data, &allpipelines[count], rtformat);
403
404 data.allpipelines = allpipelines;
405 data.pipelinescount = count + 1;
406
407 return &data.allpipelines[count];
408}
409
410static void DestroyAllPipelines(METAL_ShaderPipelines *allpipelines, int count)
411{
412 if (allpipelines != NULL) {
413 for (int i = 0; i < count; i++) {
414 for (int cache = 0; cache < SDL_METAL_FRAGMENT_COUNT; cache++) {
415 DestroyPipelineCache(&allpipelines[i].caches[cache]);
416 }
417 }
418
419 SDL_free(allpipelines);
420 }
421}
422
423static inline id<MTLRenderPipelineState> ChoosePipelineState(SDL3METAL_RenderData *data, METAL_ShaderPipelines *pipelines, SDL_MetalFragmentFunction fragfn, SDL_BlendMode blendmode)
424{
425 METAL_PipelineCache *cache = &pipelines->caches[fragfn];
426
427 for (int i = 0; i < cache->count; i++) {
428 if (cache->states[i].blendMode == blendmode) {
429 return (__bridge id<MTLRenderPipelineState>)cache->states[i].pipe;
430 }
431 }
432
433 return MakePipelineState(data, cache, [NSString stringWithFormat:@" (blend=custom 0x%x)", blendmode], blendmode);
434}
435
436static bool METAL_ActivateRenderCommandEncoder(SDL_Renderer *renderer, MTLLoadAction load, MTLClearColor *clear_color, id<MTLBuffer> vertex_buffer)
437{
438 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
439
440 /* Our SetRenderTarget just signals that the next render operation should
441 * set up a new render pass. This is where that work happens. */
442 if (data.mtlcmdencoder == nil) {
443 id<MTLTexture> mtltexture = nil;
444
445 if (renderer->target != NULL) {
446 SDL3METAL_TextureData *texdata = (__bridge SDL3METAL_TextureData *)renderer->target->internal;
447 mtltexture = texdata.mtltexture;
448 } else {
449 if (data.mtlbackbuffer == nil) {
450 /* The backbuffer's contents aren't guaranteed to persist after
451 * presenting, so we can leave it undefined when loading it. */
452 data.mtlbackbuffer = [data.mtllayer nextDrawable];
453 if (load == MTLLoadActionLoad) {
454 load = MTLLoadActionDontCare;
455 }
456 }
457 if (data.mtlbackbuffer != nil) {
458 mtltexture = data.mtlbackbuffer.texture;
459 }
460 }
461
462 /* mtltexture can be nil here if macOS refused to give us a drawable,
463 which apparently can happen for minimized windows, etc. */
464 if (mtltexture == nil) {
465 return false;
466 }
467
468 if (load == MTLLoadActionClear) {
469 SDL_assert(clear_color != NULL);
470 data.mtlpassdesc.colorAttachments[0].clearColor = *clear_color;
471 }
472
473 data.mtlpassdesc.colorAttachments[0].loadAction = load;
474 data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
475
476 data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
477 data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
478
479 if (data.mtlbackbuffer != nil && mtltexture == data.mtlbackbuffer.texture) {
480 data.mtlcmdencoder.label = @"SDL metal renderer backbuffer";
481 } else {
482 data.mtlcmdencoder.label = @"SDL metal renderer render target";
483 }
484
485 /* Set up buffer bindings for positions, texcoords, and color once here,
486 * the offsets are adjusted in the code that uses them. */
487 if (vertex_buffer != nil) {
488 [data.mtlcmdencoder setVertexBuffer:vertex_buffer offset:0 atIndex:0];
489 [data.mtlcmdencoder setFragmentBuffer:vertex_buffer offset:0 atIndex:0];
490 }
491
492 data.activepipelines = ChooseShaderPipelines(data, mtltexture.pixelFormat);
493
494 // make sure this has a definite place in the queue. This way it will
495 // execute reliably whether the app tries to make its own command buffers
496 // or whatever. This means we can _always_ batch rendering commands!
497 [data.mtlcmdbuffer enqueue];
498 }
499
500 return true;
501}
502
503static void METAL_WindowEvent(SDL_Renderer *renderer, const SDL_WindowEvent *event)
504{
505}
506
507static bool METAL_GetOutputSize(SDL_Renderer *renderer, int *w, int *h)
508{
509 @autoreleasepool {
510 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
511 if (w) {
512 *w = (int)data.mtllayer.drawableSize.width;
513 }
514 if (h) {
515 *h = (int)data.mtllayer.drawableSize.height;
516 }
517 return true;
518 }
519}
520
521static bool METAL_SupportsBlendMode(SDL_Renderer *renderer, SDL_BlendMode blendMode)
522{
523 SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode);
524 SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode);
525 SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode);
526 SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode);
527 SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode);
528 SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode);
529
530 if (GetBlendFactor(srcColorFactor) == invalidBlendFactor ||
531 GetBlendFactor(srcAlphaFactor) == invalidBlendFactor ||
532 GetBlendOperation(colorOperation) == invalidBlendOperation ||
533 GetBlendFactor(dstColorFactor) == invalidBlendFactor ||
534 GetBlendFactor(dstAlphaFactor) == invalidBlendFactor ||
535 GetBlendOperation(alphaOperation) == invalidBlendOperation) {
536 return false;
537 }
538 return true;
539}
540
541size_t GetBT601ConversionMatrix(SDL_Colorspace colorspace)
542{
543 switch (SDL_COLORSPACERANGE(colorspace)) {
544 case SDL_COLOR_RANGE_LIMITED:
545 case SDL_COLOR_RANGE_UNKNOWN:
546 return CONSTANTS_OFFSET_DECODE_BT601_LIMITED;
547 case SDL_COLOR_RANGE_FULL:
548 return CONSTANTS_OFFSET_DECODE_BT601_FULL;
549 default:
550 break;
551 }
552 return 0;
553}
554
555size_t GetBT709ConversionMatrix(SDL_Colorspace colorspace)
556{
557 switch (SDL_COLORSPACERANGE(colorspace)) {
558 case SDL_COLOR_RANGE_LIMITED:
559 case SDL_COLOR_RANGE_UNKNOWN:
560 return CONSTANTS_OFFSET_DECODE_BT709_LIMITED;
561 case SDL_COLOR_RANGE_FULL:
562 return CONSTANTS_OFFSET_DECODE_BT709_FULL;
563 default:
564 break;
565 }
566 return 0;
567}
568
569size_t GetBT2020ConversionMatrix(SDL_Colorspace colorspace)
570{
571 switch (SDL_COLORSPACERANGE(colorspace)) {
572 case SDL_COLOR_RANGE_LIMITED:
573 case SDL_COLOR_RANGE_UNKNOWN:
574 return CONSTANTS_OFFSET_DECODE_BT2020_LIMITED;
575 case SDL_COLOR_RANGE_FULL:
576 return CONSTANTS_OFFSET_DECODE_BT2020_FULL;
577 default:
578 break;
579 }
580 return 0;
581}
582
583size_t GetYCbCRtoRGBConversionMatrix(SDL_Colorspace colorspace, int w, int h, int bits_per_pixel)
584{
585 const int YUV_SD_THRESHOLD = 576;
586
587 switch (SDL_COLORSPACEMATRIX(colorspace)) {
588 case SDL_MATRIX_COEFFICIENTS_BT470BG:
589 case SDL_MATRIX_COEFFICIENTS_BT601:
590 return GetBT601ConversionMatrix(colorspace);
591
592 case SDL_MATRIX_COEFFICIENTS_BT709:
593 return GetBT709ConversionMatrix(colorspace);
594
595 case SDL_MATRIX_COEFFICIENTS_BT2020_NCL:
596 return GetBT2020ConversionMatrix(colorspace);
597
598 case SDL_MATRIX_COEFFICIENTS_UNSPECIFIED:
599 switch (bits_per_pixel) {
600 case 8:
601 if (h <= YUV_SD_THRESHOLD) {
602 return GetBT601ConversionMatrix(colorspace);
603 } else {
604 return GetBT709ConversionMatrix(colorspace);
605 }
606 case 10:
607 case 16:
608 return GetBT2020ConversionMatrix(colorspace);
609 default:
610 break;
611 }
612 break;
613 default:
614 break;
615 }
616 return 0;
617}
618
619static bool METAL_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_PropertiesID create_props)
620{
621 @autoreleasepool {
622 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
623 MTLPixelFormat pixfmt;
624 MTLTextureDescriptor *mtltexdesc;
625 id<MTLTexture> mtltexture = nil, mtltextureUv = nil;
626 SDL3METAL_TextureData *texturedata;
627 CVPixelBufferRef pixelbuffer = nil;
628 IOSurfaceRef surface = nil;
629
630 pixelbuffer = SDL_GetPointerProperty(create_props, SDL_PROP_TEXTURE_CREATE_METAL_PIXELBUFFER_POINTER, nil);
631 if (pixelbuffer) {
632 surface = CVPixelBufferGetIOSurface(pixelbuffer);
633 if (!surface) {
634 return SDL_SetError("CVPixelBufferGetIOSurface() failed");
635 }
636 }
637
638 switch (texture->format) {
639 case SDL_PIXELFORMAT_ABGR8888:
640 if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) {
641 pixfmt = MTLPixelFormatRGBA8Unorm_sRGB;
642 } else {
643 pixfmt = MTLPixelFormatRGBA8Unorm;
644 }
645 break;
646 case SDL_PIXELFORMAT_ARGB8888:
647 if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) {
648 pixfmt = MTLPixelFormatBGRA8Unorm_sRGB;
649 } else {
650 pixfmt = MTLPixelFormatBGRA8Unorm;
651 }
652 break;
653 case SDL_PIXELFORMAT_ABGR2101010:
654 pixfmt = MTLPixelFormatRGB10A2Unorm;
655 break;
656 case SDL_PIXELFORMAT_IYUV:
657 case SDL_PIXELFORMAT_YV12:
658 case SDL_PIXELFORMAT_NV12:
659 case SDL_PIXELFORMAT_NV21:
660 pixfmt = MTLPixelFormatR8Unorm;
661 break;
662 case SDL_PIXELFORMAT_P010:
663 pixfmt = MTLPixelFormatR16Unorm;
664 break;
665 case SDL_PIXELFORMAT_RGBA64_FLOAT:
666 pixfmt = MTLPixelFormatRGBA16Float;
667 break;
668 case SDL_PIXELFORMAT_RGBA128_FLOAT:
669 pixfmt = MTLPixelFormatRGBA32Float;
670 break;
671 default:
672 return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
673 }
674
675 mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixfmt
676 width:(NSUInteger)texture->w
677 height:(NSUInteger)texture->h
678 mipmapped:NO];
679
680 if (texture->access == SDL_TEXTUREACCESS_TARGET) {
681 mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
682 } else {
683 mtltexdesc.usage = MTLTextureUsageShaderRead;
684 }
685
686 if (surface) {
687 mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc iosurface:surface plane:0];
688 } else {
689 mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
690 }
691 if (mtltexture == nil) {
692 return SDL_SetError("Texture allocation failed");
693 }
694
695 mtltextureUv = nil;
696#ifdef SDL_HAVE_YUV
697 BOOL yuv = (texture->format == SDL_PIXELFORMAT_IYUV || texture->format == SDL_PIXELFORMAT_YV12);
698 BOOL nv12 = (texture->format == SDL_PIXELFORMAT_NV12 || texture->format == SDL_PIXELFORMAT_NV21 || texture->format == SDL_PIXELFORMAT_P010);
699
700 if (yuv) {
701 mtltexdesc.pixelFormat = MTLPixelFormatR8Unorm;
702 mtltexdesc.width = (texture->w + 1) / 2;
703 mtltexdesc.height = (texture->h + 1) / 2;
704 mtltexdesc.textureType = MTLTextureType2DArray;
705 mtltexdesc.arrayLength = 2;
706 } else if (texture->format == SDL_PIXELFORMAT_P010) {
707 mtltexdesc.pixelFormat = MTLPixelFormatRG16Unorm;
708 mtltexdesc.width = (texture->w + 1) / 2;
709 mtltexdesc.height = (texture->h + 1) / 2;
710 } else if (nv12) {
711 mtltexdesc.pixelFormat = MTLPixelFormatRG8Unorm;
712 mtltexdesc.width = (texture->w + 1) / 2;
713 mtltexdesc.height = (texture->h + 1) / 2;
714 }
715
716 if (yuv || nv12) {
717 if (surface) {
718 mtltextureUv = [data.mtldevice newTextureWithDescriptor:mtltexdesc iosurface:surface plane:1];
719 } else {
720 mtltextureUv = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
721 }
722 if (mtltextureUv == nil) {
723 return SDL_SetError("Texture allocation failed");
724 }
725 }
726#endif // SDL_HAVE_YUV
727 texturedata = [[SDL3METAL_TextureData alloc] init];
728#ifdef SDL_HAVE_YUV
729 if (yuv) {
730 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV;
731 } else if (nv12) {
732 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV12;
733 } else
734#endif
735 {
736 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY;
737 }
738 texturedata.mtltexture = mtltexture;
739 texturedata.mtltextureUv = mtltextureUv;
740#ifdef SDL_HAVE_YUV
741 texturedata.yuv = yuv;
742 texturedata.nv12 = nv12;
743 if (yuv || nv12) {
744 size_t offset = GetYCbCRtoRGBConversionMatrix(texture->colorspace, texture->w, texture->h, 8);
745 if (offset == 0) {
746 return SDL_SetError("Unsupported YUV colorspace");
747 }
748 texturedata.conversionBufferOffset = offset;
749 }
750#endif
751 texture->internal = (void *)CFBridgingRetain(texturedata);
752
753 return true;
754 }
755}
756
757static void METAL_UploadTextureData(id<MTLTexture> texture, SDL_Rect rect, int slice,
758 const void *pixels, int pitch)
759{
760 [texture replaceRegion:MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h)
761 mipmapLevel:0
762 slice:slice
763 withBytes:pixels
764 bytesPerRow:pitch
765 bytesPerImage:0];
766}
767
768static MTLStorageMode METAL_GetStorageMode(id<MTLResource> resource)
769{
770 return resource.storageMode;
771}
772
773static bool METAL_UpdateTextureInternal(SDL_Renderer *renderer, SDL3METAL_TextureData *texturedata,
774 id<MTLTexture> texture, SDL_Rect rect, int slice,
775 const void *pixels, int pitch)
776{
777 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
778 SDL_Rect stagingrect = { 0, 0, rect.w, rect.h };
779 MTLTextureDescriptor *desc;
780 id<MTLTexture> stagingtex;
781 id<MTLBlitCommandEncoder> blitcmd;
782
783 /* If the texture is managed or shared and this is the first upload, we can
784 * use replaceRegion to upload to it directly. Otherwise we upload the data
785 * to a staging texture and copy that over. */
786 if (!texturedata.hasdata && METAL_GetStorageMode(texture) != MTLStorageModePrivate) {
787 METAL_UploadTextureData(texture, rect, slice, pixels, pitch);
788 return true;
789 }
790
791 desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:texture.pixelFormat
792 width:rect.w
793 height:rect.h
794 mipmapped:NO];
795
796 if (desc == nil) {
797 return SDL_OutOfMemory();
798 }
799
800 /* TODO: We could have a pool of textures or a MTLHeap we allocate from,
801 * and release a staging texture back to the pool in the command buffer's
802 * completion handler. */
803 stagingtex = [data.mtldevice newTextureWithDescriptor:desc];
804 if (stagingtex == nil) {
805 return SDL_OutOfMemory();
806 }
807
808 METAL_UploadTextureData(stagingtex, stagingrect, 0, pixels, pitch);
809
810 if (data.mtlcmdencoder != nil) {
811 [data.mtlcmdencoder endEncoding];
812 data.mtlcmdencoder = nil;
813 }
814
815 if (data.mtlcmdbuffer == nil) {
816 data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
817 }
818
819 blitcmd = [data.mtlcmdbuffer blitCommandEncoder];
820
821 [blitcmd copyFromTexture:stagingtex
822 sourceSlice:0
823 sourceLevel:0
824 sourceOrigin:MTLOriginMake(0, 0, 0)
825 sourceSize:MTLSizeMake(rect.w, rect.h, 1)
826 toTexture:texture
827 destinationSlice:slice
828 destinationLevel:0
829 destinationOrigin:MTLOriginMake(rect.x, rect.y, 0)];
830
831 [blitcmd endEncoding];
832
833 /* TODO: This isn't very efficient for the YUV formats, which call
834 * UpdateTextureInternal multiple times in a row. */
835 [data.mtlcmdbuffer commit];
836 data.mtlcmdbuffer = nil;
837
838 return true;
839}
840
841static bool METAL_UpdateTexture(SDL_Renderer *renderer, SDL_Texture *texture,
842 const SDL_Rect *rect, const void *pixels, int pitch)
843{
844 @autoreleasepool {
845 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal;
846
847 if (!METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture, *rect, 0, pixels, pitch)) {
848 return false;
849 }
850#ifdef SDL_HAVE_YUV
851 if (texturedata.yuv) {
852 int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0;
853 int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1;
854 int UVpitch = (pitch + 1) / 2;
855 SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 };
856
857 // Skip to the correct offset into the next texture
858 pixels = (const void *)((const Uint8 *)pixels + rect->h * pitch);
859 if (!METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltextureUv, UVrect, Uslice, pixels, UVpitch)) {
860 return false;
861 }
862
863 // Skip to the correct offset into the next texture
864 pixels = (const void *)((const Uint8 *)pixels + UVrect.h * UVpitch);
865 if (!METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltextureUv, UVrect, Vslice, pixels, UVpitch)) {
866 return false;
867 }
868 }
869
870 if (texturedata.nv12) {
871 SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 };
872 int UVpitch = 2 * ((pitch + 1) / 2);
873
874 // Skip to the correct offset into the next texture
875 pixels = (const void *)((const Uint8 *)pixels + rect->h * pitch);
876 if (!METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltextureUv, UVrect, 0, pixels, UVpitch)) {
877 return false;
878 }
879 }
880#endif
881 texturedata.hasdata = YES;
882
883 return true;
884 }
885}
886
887#ifdef SDL_HAVE_YUV
888static bool METAL_UpdateTextureYUV(SDL_Renderer *renderer, SDL_Texture *texture,
889 const SDL_Rect *rect,
890 const Uint8 *Yplane, int Ypitch,
891 const Uint8 *Uplane, int Upitch,
892 const Uint8 *Vplane, int Vpitch)
893{
894 @autoreleasepool {
895 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal;
896 const int Uslice = 0;
897 const int Vslice = 1;
898 SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 };
899
900 // Bail out if we're supposed to update an empty rectangle
901 if (rect->w <= 0 || rect->h <= 0) {
902 return true;
903 }
904
905 if (!METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture, *rect, 0, Yplane, Ypitch)) {
906 return false;
907 }
908 if (!METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltextureUv, UVrect, Uslice, Uplane, Upitch)) {
909 return false;
910 }
911 if (!METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltextureUv, UVrect, Vslice, Vplane, Vpitch)) {
912 return false;
913 }
914
915 texturedata.hasdata = YES;
916
917 return true;
918 }
919}
920
921static bool METAL_UpdateTextureNV(SDL_Renderer *renderer, SDL_Texture *texture,
922 const SDL_Rect *rect,
923 const Uint8 *Yplane, int Ypitch,
924 const Uint8 *UVplane, int UVpitch)
925{
926 @autoreleasepool {
927 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal;
928 SDL_Rect UVrect = { rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2 };
929
930 // Bail out if we're supposed to update an empty rectangle
931 if (rect->w <= 0 || rect->h <= 0) {
932 return true;
933 }
934
935 if (!METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture, *rect, 0, Yplane, Ypitch)) {
936 return false;
937 }
938
939 if (!METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltextureUv, UVrect, 0, UVplane, UVpitch)) {
940 return false;
941 }
942
943 texturedata.hasdata = YES;
944
945 return true;
946 }
947}
948#endif
949
950static bool METAL_LockTexture(SDL_Renderer *renderer, SDL_Texture *texture,
951 const SDL_Rect *rect, void **pixels, int *pitch)
952{
953 @autoreleasepool {
954 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
955 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal;
956 int buffersize = 0;
957 id<MTLBuffer> lockedbuffer = nil;
958
959 if (rect->w <= 0 || rect->h <= 0) {
960 return SDL_SetError("Invalid rectangle dimensions for LockTexture.");
961 }
962
963 *pitch = SDL_BYTESPERPIXEL(texture->format) * rect->w;
964#ifdef SDL_HAVE_YUV
965 if (texturedata.yuv || texturedata.nv12) {
966 buffersize = ((*pitch) * rect->h) + (2 * (*pitch + 1) / 2) * ((rect->h + 1) / 2);
967 } else
968#endif
969 {
970 buffersize = (*pitch) * rect->h;
971 }
972
973 lockedbuffer = [data.mtldevice newBufferWithLength:buffersize options:MTLResourceStorageModeShared];
974 if (lockedbuffer == nil) {
975 return SDL_OutOfMemory();
976 }
977
978 texturedata.lockedrect = *rect;
979 texturedata.lockedbuffer = lockedbuffer;
980 *pixels = [lockedbuffer contents];
981
982 return true;
983 }
984}
985
986static void METAL_UnlockTexture(SDL_Renderer *renderer, SDL_Texture *texture)
987{
988 @autoreleasepool {
989 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
990 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal;
991 id<MTLBlitCommandEncoder> blitcmd;
992 SDL_Rect rect = texturedata.lockedrect;
993 int pitch = SDL_BYTESPERPIXEL(texture->format) * rect.w;
994#ifdef SDL_HAVE_YUV
995 SDL_Rect UVrect = { rect.x / 2, rect.y / 2, (rect.w + 1) / 2, (rect.h + 1) / 2 };
996#endif
997
998 if (texturedata.lockedbuffer == nil) {
999 return;
1000 }
1001
1002 if (data.mtlcmdencoder != nil) {
1003 [data.mtlcmdencoder endEncoding];
1004 data.mtlcmdencoder = nil;
1005 }
1006
1007 if (data.mtlcmdbuffer == nil) {
1008 data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
1009 }
1010
1011 blitcmd = [data.mtlcmdbuffer blitCommandEncoder];
1012
1013 [blitcmd copyFromBuffer:texturedata.lockedbuffer
1014 sourceOffset:0
1015 sourceBytesPerRow:pitch
1016 sourceBytesPerImage:0
1017 sourceSize:MTLSizeMake(rect.w, rect.h, 1)
1018 toTexture:texturedata.mtltexture
1019 destinationSlice:0
1020 destinationLevel:0
1021 destinationOrigin:MTLOriginMake(rect.x, rect.y, 0)];
1022#ifdef SDL_HAVE_YUV
1023 if (texturedata.yuv) {
1024 int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0;
1025 int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1;
1026 int UVpitch = (pitch + 1) / 2;
1027
1028 [blitcmd copyFromBuffer:texturedata.lockedbuffer
1029 sourceOffset:rect.h * pitch
1030 sourceBytesPerRow:UVpitch
1031 sourceBytesPerImage:UVpitch * UVrect.h
1032 sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1)
1033 toTexture:texturedata.mtltextureUv
1034 destinationSlice:Uslice
1035 destinationLevel:0
1036 destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)];
1037
1038 [blitcmd copyFromBuffer:texturedata.lockedbuffer
1039 sourceOffset:(rect.h * pitch) + UVrect.h * UVpitch
1040 sourceBytesPerRow:UVpitch
1041 sourceBytesPerImage:UVpitch * UVrect.h
1042 sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1)
1043 toTexture:texturedata.mtltextureUv
1044 destinationSlice:Vslice
1045 destinationLevel:0
1046 destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)];
1047 }
1048
1049 if (texturedata.nv12) {
1050 int UVpitch = 2 * ((pitch + 1) / 2);
1051
1052 [blitcmd copyFromBuffer:texturedata.lockedbuffer
1053 sourceOffset:rect.h * pitch
1054 sourceBytesPerRow:UVpitch
1055 sourceBytesPerImage:0
1056 sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1)
1057 toTexture:texturedata.mtltextureUv
1058 destinationSlice:0
1059 destinationLevel:0
1060 destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)];
1061 }
1062#endif
1063 [blitcmd endEncoding];
1064
1065 [data.mtlcmdbuffer commit];
1066 data.mtlcmdbuffer = nil;
1067
1068 texturedata.lockedbuffer = nil; // Retained property, so it calls release.
1069 texturedata.hasdata = YES;
1070 }
1071}
1072
1073static void METAL_SetTextureScaleMode(SDL_Renderer *renderer, SDL_Texture *texture, SDL_ScaleMode scaleMode)
1074{
1075}
1076
1077static bool METAL_SetRenderTarget(SDL_Renderer *renderer, SDL_Texture *texture)
1078{
1079 @autoreleasepool {
1080 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
1081
1082 if (data.mtlcmdencoder) {
1083 /* End encoding for the previous render target so we can set up a new
1084 * render pass for this one. */
1085 [data.mtlcmdencoder endEncoding];
1086 [data.mtlcmdbuffer commit];
1087
1088 data.mtlcmdencoder = nil;
1089 data.mtlcmdbuffer = nil;
1090 }
1091
1092 /* We don't begin a new render pass right away - we delay it until an actual
1093 * draw or clear happens. That way we can use hardware clears when possible,
1094 * which are only available when beginning a new render pass. */
1095 return true;
1096 }
1097}
1098
1099static bool METAL_QueueSetViewport(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
1100{
1101 float projection[4][4]; // Prepare an orthographic projection
1102 const int w = cmd->data.viewport.rect.w;
1103 const int h = cmd->data.viewport.rect.h;
1104 const size_t matrixlen = sizeof(projection);
1105 float *matrix = (float *)SDL_AllocateRenderVertices(renderer, matrixlen, CONSTANT_ALIGN(16), &cmd->data.viewport.first);
1106 if (!matrix) {
1107 return false;
1108 }
1109
1110 SDL_memset(projection, '\0', matrixlen);
1111 if (w && h) {
1112 projection[0][0] = 2.0f / w;
1113 projection[1][1] = -2.0f / h;
1114 projection[3][0] = -1.0f;
1115 projection[3][1] = 1.0f;
1116 projection[3][3] = 1.0f;
1117 }
1118 SDL_memcpy(matrix, projection, matrixlen);
1119
1120 return true;
1121}
1122
1123static bool METAL_QueueNoOp(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
1124{
1125 return true; // nothing to do in this backend.
1126}
1127
1128static bool METAL_QueueDrawPoints(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count)
1129{
1130 SDL_FColor color = cmd->data.draw.color;
1131 bool convert_color = SDL_RenderingLinearSpace(renderer);
1132
1133 const size_t vertlen = (2 * sizeof(float) + 4 * sizeof(float)) * count;
1134 float *verts = (float *)SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first);
1135 if (!verts) {
1136 return false;
1137 }
1138 cmd->data.draw.count = count;
1139
1140 if (convert_color) {
1141 SDL_ConvertToLinear(&color);
1142 }
1143
1144 for (int i = 0; i < count; i++, points++) {
1145 *(verts++) = points->x;
1146 *(verts++) = points->y;
1147 *(verts++) = color.r;
1148 *(verts++) = color.g;
1149 *(verts++) = color.b;
1150 *(verts++) = color.a;
1151 }
1152 return true;
1153}
1154
1155static bool METAL_QueueDrawLines(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count)
1156{
1157 SDL_FColor color = cmd->data.draw.color;
1158 bool convert_color = SDL_RenderingLinearSpace(renderer);
1159 size_t vertlen;
1160 float *verts;
1161
1162 SDL_assert(count >= 2); // should have been checked at the higher level.
1163
1164 vertlen = (2 * sizeof(float) + 4 * sizeof(float)) * count;
1165 verts = (float *)SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first);
1166 if (!verts) {
1167 return false;
1168 }
1169 cmd->data.draw.count = count;
1170
1171 if (convert_color) {
1172 SDL_ConvertToLinear(&color);
1173 }
1174
1175 for (int i = 0; i < count; i++, points++) {
1176 *(verts++) = points->x;
1177 *(verts++) = points->y;
1178 *(verts++) = color.r;
1179 *(verts++) = color.g;
1180 *(verts++) = color.b;
1181 *(verts++) = color.a;
1182 }
1183
1184 /* If the line segment is completely horizontal or vertical,
1185 make it one pixel longer, to satisfy the diamond-exit rule.
1186 We should probably do this for diagonal lines too, but we'd have to
1187 do some trigonometry to figure out the correct pixel and generally
1188 when we have problems with pixel perfection, it's for straight lines
1189 that are missing a pixel that frames something and not arbitrary
1190 angles. Maybe !!! FIXME for later, though. */
1191
1192 points -= 2; // update the last line.
1193 verts -= 2 + 1;
1194
1195 {
1196 const float xstart = points[0].x;
1197 const float ystart = points[0].y;
1198 const float xend = points[1].x;
1199 const float yend = points[1].y;
1200
1201 if (ystart == yend) { // horizontal line
1202 verts[0] += (xend > xstart) ? 1.0f : -1.0f;
1203 } else if (xstart == xend) { // vertical line
1204 verts[1] += (yend > ystart) ? 1.0f : -1.0f;
1205 }
1206 }
1207
1208 return true;
1209}
1210
1211static bool METAL_QueueGeometry(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture,
1212 const float *xy, int xy_stride, const SDL_FColor *color, int color_stride, const float *uv, int uv_stride,
1213 int num_vertices, const void *indices, int num_indices, int size_indices,
1214 float scale_x, float scale_y)
1215{
1216 bool convert_color = SDL_RenderingLinearSpace(renderer);
1217 int count = indices ? num_indices : num_vertices;
1218 const size_t vertlen = (2 * sizeof(float) + 4 * sizeof(float) + (texture ? 2 : 0) * sizeof(float)) * count;
1219 float *verts = (float *)SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first);
1220 if (!verts) {
1221 return false;
1222 }
1223
1224 cmd->data.draw.count = count;
1225 size_indices = indices ? size_indices : 0;
1226
1227 for (int i = 0; i < count; i++) {
1228 int j;
1229 float *xy_;
1230 SDL_FColor col_;
1231 if (size_indices == 4) {
1232 j = ((const Uint32 *)indices)[i];
1233 } else if (size_indices == 2) {
1234 j = ((const Uint16 *)indices)[i];
1235 } else if (size_indices == 1) {
1236 j = ((const Uint8 *)indices)[i];
1237 } else {
1238 j = i;
1239 }
1240
1241 xy_ = (float *)((char *)xy + j * xy_stride);
1242
1243 *(verts++) = xy_[0] * scale_x;
1244 *(verts++) = xy_[1] * scale_y;
1245
1246 col_ = *(SDL_FColor *)((char *)color + j * color_stride);
1247
1248 if (convert_color) {
1249 SDL_ConvertToLinear(&col_);
1250 }
1251
1252 *(verts++) = col_.r;
1253 *(verts++) = col_.g;
1254 *(verts++) = col_.b;
1255 *(verts++) = col_.a;
1256
1257 if (texture) {
1258 float *uv_ = (float *)((char *)uv + j * uv_stride);
1259 *(verts++) = uv_[0];
1260 *(verts++) = uv_[1];
1261 }
1262 }
1263
1264 return true;
1265}
1266
1267// These should mirror the definitions in SDL_shaders_metal.metal
1268//static const float TONEMAP_NONE = 0;
1269//static const float TONEMAP_LINEAR = 1;
1270static const float TONEMAP_CHROME = 2;
1271
1272//static const float TEXTURETYPE_NONE = 0;
1273static const float TEXTURETYPE_RGB = 1;
1274static const float TEXTURETYPE_NV12 = 2;
1275static const float TEXTURETYPE_NV21 = 3;
1276static const float TEXTURETYPE_YUV = 4;
1277
1278//static const float INPUTTYPE_UNSPECIFIED = 0;
1279static const float INPUTTYPE_SRGB = 1;
1280static const float INPUTTYPE_SCRGB = 2;
1281static const float INPUTTYPE_HDR10 = 3;
1282
1283typedef struct
1284{
1285 float scRGB_output;
1286 float texture_type;
1287 float input_type;
1288 float color_scale;
1289
1290 float tonemap_method;
1291 float tonemap_factor1;
1292 float tonemap_factor2;
1293 float sdr_white_point;
1294} PixelShaderConstants;
1295
1296typedef struct
1297{
1298 __unsafe_unretained id<MTLRenderPipelineState> pipeline;
1299 __unsafe_unretained id<MTLBuffer> vertex_buffer;
1300 size_t constants_offset;
1301 SDL_Texture *texture;
1302 bool cliprect_dirty;
1303 bool cliprect_enabled;
1304 SDL_Rect cliprect;
1305 bool viewport_dirty;
1306 SDL_Rect viewport;
1307 size_t projection_offset;
1308 bool shader_constants_dirty;
1309 PixelShaderConstants shader_constants;
1310} METAL_DrawStateCache;
1311
1312static void SetupShaderConstants(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const SDL_Texture *texture, PixelShaderConstants *constants)
1313{
1314 float output_headroom;
1315
1316 SDL_zerop(constants);
1317
1318 constants->scRGB_output = (float)SDL_RenderingLinearSpace(renderer);
1319 constants->color_scale = cmd->data.draw.color_scale;
1320
1321 if (texture) {
1322 switch (texture->format) {
1323 case SDL_PIXELFORMAT_YV12:
1324 case SDL_PIXELFORMAT_IYUV:
1325 constants->texture_type = TEXTURETYPE_YUV;
1326 break;
1327 case SDL_PIXELFORMAT_NV12:
1328 constants->texture_type = TEXTURETYPE_NV12;
1329 break;
1330 case SDL_PIXELFORMAT_NV21:
1331 constants->texture_type = TEXTURETYPE_NV21;
1332 break;
1333 case SDL_PIXELFORMAT_P010:
1334 constants->texture_type = TEXTURETYPE_NV12;
1335 break;
1336 default:
1337 constants->texture_type = TEXTURETYPE_RGB;
1338 }
1339
1340 switch (SDL_COLORSPACETRANSFER(texture->colorspace)) {
1341 case SDL_TRANSFER_CHARACTERISTICS_LINEAR:
1342 constants->input_type = INPUTTYPE_SCRGB;
1343 break;
1344 case SDL_TRANSFER_CHARACTERISTICS_PQ:
1345 constants->input_type = INPUTTYPE_HDR10;
1346 break;
1347 default:
1348 constants->input_type = INPUTTYPE_SRGB;
1349 break;
1350 }
1351
1352 constants->sdr_white_point = texture->SDR_white_point;
1353
1354 if (renderer->target) {
1355 output_headroom = renderer->target->HDR_headroom;
1356 } else {
1357 output_headroom = renderer->HDR_headroom;
1358 }
1359
1360 if (texture->HDR_headroom > output_headroom) {
1361 constants->tonemap_method = TONEMAP_CHROME;
1362 constants->tonemap_factor1 = (output_headroom / (texture->HDR_headroom * texture->HDR_headroom));
1363 constants->tonemap_factor2 = (1.0f / output_headroom);
1364 }
1365 }
1366}
1367
1368static bool SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const SDL_MetalFragmentFunction shader, PixelShaderConstants *shader_constants, const size_t constants_offset, id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache)
1369{
1370 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
1371 const SDL_BlendMode blend = cmd->data.draw.blend;
1372 size_t first = cmd->data.draw.first;
1373 id<MTLRenderPipelineState> newpipeline;
1374 PixelShaderConstants solid_constants;
1375
1376 if (!METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, statecache->vertex_buffer)) {
1377 return false;
1378 }
1379
1380 if (statecache->viewport_dirty) {
1381 MTLViewport viewport;
1382 viewport.originX = statecache->viewport.x;
1383 viewport.originY = statecache->viewport.y;
1384 viewport.width = statecache->viewport.w;
1385 viewport.height = statecache->viewport.h;
1386 viewport.znear = 0.0;
1387 viewport.zfar = 1.0;
1388 [data.mtlcmdencoder setViewport:viewport];
1389 [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:statecache->projection_offset atIndex:2]; // projection
1390 statecache->viewport_dirty = false;
1391 }
1392
1393 if (statecache->cliprect_dirty) {
1394 SDL_Rect output;
1395 SDL_Rect clip;
1396 if (statecache->cliprect_enabled) {
1397 clip = statecache->cliprect;
1398 clip.x += statecache->viewport.x;
1399 clip.y += statecache->viewport.y;
1400 } else {
1401 clip = statecache->viewport;
1402 }
1403
1404 // Set Scissor Rect Validation: w/h must be <= render pass
1405 SDL_zero(output);
1406
1407 if (renderer->target) {
1408 output.w = renderer->target->w;
1409 output.h = renderer->target->h;
1410 } else {
1411 METAL_GetOutputSize(renderer, &output.w, &output.h);
1412 }
1413
1414 if (SDL_GetRectIntersection(&output, &clip, &clip)) {
1415 MTLScissorRect mtlrect;
1416 mtlrect.x = clip.x;
1417 mtlrect.y = clip.y;
1418 mtlrect.width = clip.w;
1419 mtlrect.height = clip.h;
1420 [data.mtlcmdencoder setScissorRect:mtlrect];
1421 }
1422
1423 statecache->cliprect_dirty = false;
1424 }
1425
1426 newpipeline = ChoosePipelineState(data, data.activepipelines, shader, blend);
1427 if (newpipeline != statecache->pipeline) {
1428 [data.mtlcmdencoder setRenderPipelineState:newpipeline];
1429 statecache->pipeline = newpipeline;
1430 }
1431
1432 if (!shader_constants) {
1433 SetupShaderConstants(renderer, cmd, NULL, &solid_constants);
1434 shader_constants = &solid_constants;
1435 }
1436
1437 if (statecache->shader_constants_dirty ||
1438 SDL_memcmp(shader_constants, &statecache->shader_constants, sizeof(*shader_constants)) != 0) {
1439 id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:sizeof(*shader_constants) options:MTLResourceStorageModeShared];
1440 mtlbufconstants.label = @"SDL shader constants data";
1441 SDL_memcpy([mtlbufconstants contents], shader_constants, sizeof(*shader_constants));
1442 [data.mtlcmdencoder setFragmentBuffer:mtlbufconstants offset:0 atIndex:0];
1443
1444 SDL_memcpy(&statecache->shader_constants, shader_constants, sizeof(*shader_constants));
1445 statecache->shader_constants_dirty = false;
1446 }
1447
1448 if (constants_offset != statecache->constants_offset) {
1449 if (constants_offset != CONSTANTS_OFFSET_INVALID) {
1450 [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:constants_offset atIndex:3];
1451 }
1452 statecache->constants_offset = constants_offset;
1453 }
1454
1455 [data.mtlcmdencoder setVertexBufferOffset:first atIndex:0]; // position/texcoords
1456 return true;
1457}
1458
1459static bool SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const size_t constants_offset,
1460 id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache)
1461{
1462 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
1463 SDL_Texture *texture = cmd->data.draw.texture;
1464 SDL3METAL_TextureData *texturedata = (__bridge SDL3METAL_TextureData *)texture->internal;
1465 PixelShaderConstants constants;
1466
1467 SetupShaderConstants(renderer, cmd, texture, &constants);
1468
1469 if (!SetDrawState(renderer, cmd, texturedata.fragmentFunction, &constants, constants_offset, mtlbufvertex, statecache)) {
1470 return false;
1471 }
1472
1473 if (texture != statecache->texture) {
1474 id<MTLSamplerState> mtlsampler;
1475
1476 if (texture->scaleMode == SDL_SCALEMODE_NEAREST) {
1477 switch (cmd->data.draw.texture_address_mode) {
1478 case SDL_TEXTURE_ADDRESS_CLAMP:
1479 mtlsampler = data.mtlsamplers[SDL_METAL_SAMPLER_NEAREST_CLAMP];
1480 break;
1481 case SDL_TEXTURE_ADDRESS_WRAP:
1482 mtlsampler = data.mtlsamplers[SDL_METAL_SAMPLER_NEAREST_WRAP];
1483 break;
1484 default:
1485 return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode);
1486 }
1487 } else {
1488 switch (cmd->data.draw.texture_address_mode) {
1489 case SDL_TEXTURE_ADDRESS_CLAMP:
1490 mtlsampler = data.mtlsamplers[SDL_METAL_SAMPLER_LINEAR_CLAMP];
1491 break;
1492 case SDL_TEXTURE_ADDRESS_WRAP:
1493 mtlsampler = data.mtlsamplers[SDL_METAL_SAMPLER_LINEAR_WRAP];
1494 break;
1495 default:
1496 return SDL_SetError("Unknown texture address mode: %d", cmd->data.draw.texture_address_mode);
1497 }
1498 }
1499 [data.mtlcmdencoder setFragmentSamplerState:mtlsampler atIndex:0];
1500
1501 [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0];
1502#ifdef SDL_HAVE_YUV
1503 if (texturedata.yuv || texturedata.nv12) {
1504 [data.mtlcmdencoder setFragmentTexture:texturedata.mtltextureUv atIndex:1];
1505 [data.mtlcmdencoder setFragmentBuffer:data.mtlbufconstants offset:texturedata.conversionBufferOffset atIndex:1];
1506 }
1507#endif
1508 statecache->texture = texture;
1509 }
1510 return true;
1511}
1512
1513static void METAL_InvalidateCachedState(SDL_Renderer *renderer)
1514{
1515 // METAL_DrawStateCache only exists during a run of METAL_RunCommandQueue, so there's nothing to invalidate!
1516}
1517
1518static bool METAL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize)
1519{
1520 @autoreleasepool {
1521 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
1522 id<MTLBuffer> mtlbufvertex = nil;
1523 METAL_DrawStateCache statecache;
1524 SDL_zero(statecache);
1525
1526 statecache.pipeline = nil;
1527 statecache.vertex_buffer = nil;
1528 statecache.constants_offset = CONSTANTS_OFFSET_INVALID;
1529 statecache.texture = NULL;
1530 statecache.shader_constants_dirty = true;
1531 statecache.cliprect_dirty = true;
1532 statecache.viewport_dirty = true;
1533 statecache.projection_offset = 0;
1534
1535 // !!! FIXME: have a ring of pre-made MTLBuffers we cycle through? How expensive is creation?
1536 if (vertsize > 0) {
1537 /* We can memcpy to a shared buffer from the CPU and read it from the GPU
1538 * without any extra copying. It's a bit slower on macOS to read shared
1539 * data from the GPU than to read managed/private data, but we avoid the
1540 * cost of copying the data and the code's simpler. Apple's best
1541 * practices guide recommends this approach for streamed vertex data.
1542 */
1543 mtlbufvertex = [data.mtldevice newBufferWithLength:vertsize options:MTLResourceStorageModeShared];
1544 mtlbufvertex.label = @"SDL vertex data";
1545 SDL_memcpy([mtlbufvertex contents], vertices, vertsize);
1546
1547 statecache.vertex_buffer = mtlbufvertex;
1548 }
1549
1550 // If there's a command buffer here unexpectedly (app requested one?). Commit it so we can start fresh.
1551 [data.mtlcmdencoder endEncoding];
1552 [data.mtlcmdbuffer commit];
1553 data.mtlcmdencoder = nil;
1554 data.mtlcmdbuffer = nil;
1555
1556 while (cmd) {
1557 switch (cmd->command) {
1558 case SDL_RENDERCMD_SETVIEWPORT:
1559 {
1560 SDL_memcpy(&statecache.viewport, &cmd->data.viewport.rect, sizeof(statecache.viewport));
1561 statecache.projection_offset = cmd->data.viewport.first;
1562 statecache.viewport_dirty = true;
1563 statecache.cliprect_dirty = true;
1564 break;
1565 }
1566
1567 case SDL_RENDERCMD_SETCLIPRECT:
1568 {
1569 SDL_memcpy(&statecache.cliprect, &cmd->data.cliprect.rect, sizeof(statecache.cliprect));
1570 statecache.cliprect_enabled = cmd->data.cliprect.enabled;
1571 statecache.cliprect_dirty = true;
1572 break;
1573 }
1574
1575 case SDL_RENDERCMD_SETDRAWCOLOR:
1576 {
1577 break;
1578 }
1579
1580 case SDL_RENDERCMD_CLEAR:
1581 {
1582 /* If we're already encoding a command buffer, dump it without committing it. We'd just
1583 clear all its work anyhow, and starting a new encoder will let us use a hardware clear
1584 operation via MTLLoadActionClear. */
1585 if (data.mtlcmdencoder != nil) {
1586 [data.mtlcmdencoder endEncoding];
1587
1588 // !!! FIXME: have to commit, or an uncommitted but enqueued buffer will prevent the frame from finishing.
1589 [data.mtlcmdbuffer commit];
1590 data.mtlcmdencoder = nil;
1591 data.mtlcmdbuffer = nil;
1592 }
1593
1594 // force all this state to be reconfigured on next command buffer.
1595 statecache.pipeline = nil;
1596 statecache.constants_offset = CONSTANTS_OFFSET_INVALID;
1597 statecache.texture = NULL;
1598 statecache.shader_constants_dirty = true;
1599 statecache.cliprect_dirty = true;
1600 statecache.viewport_dirty = true;
1601
1602 {
1603 bool convert_color = SDL_RenderingLinearSpace(renderer);
1604 SDL_FColor color = cmd->data.color.color;
1605 if (convert_color) {
1606 SDL_ConvertToLinear(&color);
1607 }
1608 color.r *= cmd->data.color.color_scale;
1609 color.g *= cmd->data.color.color_scale;
1610 color.b *= cmd->data.color.color_scale;
1611 MTLClearColor mtlcolor = MTLClearColorMake(color.r, color.g, color.b, color.a);
1612
1613 // get new command encoder, set up with an initial clear operation.
1614 // (this might fail, and future draw operations will notice.)
1615 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear, &mtlcolor, mtlbufvertex);
1616 }
1617 break;
1618 }
1619
1620 case SDL_RENDERCMD_DRAW_POINTS:
1621 case SDL_RENDERCMD_DRAW_LINES:
1622 {
1623 const size_t count = cmd->data.draw.count;
1624 const MTLPrimitiveType primtype = (cmd->command == SDL_RENDERCMD_DRAW_POINTS) ? MTLPrimitiveTypePoint : MTLPrimitiveTypeLineStrip;
1625 if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache)) {
1626 [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
1627 }
1628 break;
1629 }
1630
1631 case SDL_RENDERCMD_FILL_RECTS: // unused
1632 break;
1633
1634 case SDL_RENDERCMD_COPY: // unused
1635 break;
1636
1637 case SDL_RENDERCMD_COPY_EX: // unused
1638 break;
1639
1640 case SDL_RENDERCMD_GEOMETRY:
1641 {
1642 const size_t count = cmd->data.draw.count;
1643 SDL_Texture *texture = cmd->data.draw.texture;
1644
1645 if (texture) {
1646 if (SetCopyState(renderer, cmd, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache)) {
1647 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:count];
1648 }
1649 } else {
1650 if (SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, NULL, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache)) {
1651 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:count];
1652 }
1653 }
1654 break;
1655 }
1656
1657 case SDL_RENDERCMD_NO_OP:
1658 break;
1659 }
1660 cmd = cmd->next;
1661 }
1662
1663 return true;
1664 }
1665}
1666
1667static SDL_Surface *METAL_RenderReadPixels(SDL_Renderer *renderer, const SDL_Rect *rect)
1668{
1669 @autoreleasepool {
1670 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
1671 id<MTLTexture> mtltexture;
1672 MTLRegion mtlregion;
1673 Uint32 format;
1674 SDL_Surface *surface;
1675
1676 if (!METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, nil)) {
1677 SDL_SetError("Failed to activate render command encoder (is your window in the background?");
1678 return NULL;
1679 }
1680
1681 [data.mtlcmdencoder endEncoding];
1682 mtltexture = data.mtlpassdesc.colorAttachments[0].texture;
1683
1684#ifdef SDL_PLATFORM_MACOS
1685 /* on macOS with managed-storage textures, we need to tell the driver to
1686 * update the CPU-side copy of the texture data.
1687 * NOTE: Currently all of our textures are managed on macOS. We'll need some
1688 * extra copying for any private textures. */
1689 if (METAL_GetStorageMode(mtltexture) == MTLStorageModeManaged) {
1690 id<MTLBlitCommandEncoder> blit = [data.mtlcmdbuffer blitCommandEncoder];
1691 [blit synchronizeResource:mtltexture];
1692 [blit endEncoding];
1693 }
1694#endif
1695
1696 /* Commit the current command buffer and wait until it's completed, to make
1697 * sure the GPU has finished rendering to it by the time we read it. */
1698 [data.mtlcmdbuffer commit];
1699 [data.mtlcmdbuffer waitUntilCompleted];
1700 data.mtlcmdencoder = nil;
1701 data.mtlcmdbuffer = nil;
1702
1703 mtlregion = MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h);
1704
1705 switch (mtltexture.pixelFormat) {
1706 case MTLPixelFormatBGRA8Unorm:
1707 case MTLPixelFormatBGRA8Unorm_sRGB:
1708 format = SDL_PIXELFORMAT_ARGB8888;
1709 break;
1710 case MTLPixelFormatRGBA8Unorm:
1711 case MTLPixelFormatRGBA8Unorm_sRGB:
1712 format = SDL_PIXELFORMAT_ABGR8888;
1713 break;
1714 case MTLPixelFormatRGB10A2Unorm:
1715 format = SDL_PIXELFORMAT_ABGR2101010;
1716 break;
1717 case MTLPixelFormatRGBA16Float:
1718 format = SDL_PIXELFORMAT_RGBA64_FLOAT;
1719 break;
1720 default:
1721 SDL_SetError("Unknown framebuffer pixel format");
1722 return NULL;
1723 }
1724 surface = SDL_CreateSurface(rect->w, rect->h, format);
1725 if (surface) {
1726 [mtltexture getBytes:surface->pixels bytesPerRow:surface->pitch fromRegion:mtlregion mipmapLevel:0];
1727 }
1728 return surface;
1729 }
1730}
1731
1732static bool METAL_RenderPresent(SDL_Renderer *renderer)
1733{
1734 @autoreleasepool {
1735 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
1736 bool ready = true;
1737
1738 // If we don't have a command buffer, we can't present, so activate to get one.
1739 if (data.mtlcmdencoder == nil) {
1740 // We haven't even gotten a backbuffer yet? Load and clear it. Otherwise, load the existing data.
1741 if (data.mtlbackbuffer == nil) {
1742 float alpha = (SDL_GetWindowFlags(renderer->window) & SDL_WINDOW_TRANSPARENT) ? 0.0f : 1.0f;
1743 MTLClearColor color = MTLClearColorMake(0.0f, 0.0f, 0.0f, alpha);
1744 ready = METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear, &color, nil);
1745 } else {
1746 ready = METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, nil);
1747 }
1748 }
1749
1750 [data.mtlcmdencoder endEncoding];
1751
1752 // If we don't have a drawable to present, don't try to present it.
1753 // But we'll still try to commit the command buffer in case it was already enqueued.
1754 if (ready) {
1755 SDL_assert(data.mtlbackbuffer != nil);
1756 [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
1757 }
1758
1759 [data.mtlcmdbuffer commit];
1760
1761 data.mtlcmdencoder = nil;
1762 data.mtlcmdbuffer = nil;
1763 data.mtlbackbuffer = nil;
1764
1765 if (renderer->hidden || !ready) {
1766 return false;
1767 }
1768 return true;
1769 }
1770}
1771
1772static void METAL_DestroyTexture(SDL_Renderer *renderer, SDL_Texture *texture)
1773{
1774 @autoreleasepool {
1775 CFBridgingRelease(texture->internal);
1776 texture->internal = NULL;
1777 }
1778}
1779
1780static void METAL_DestroyRenderer(SDL_Renderer *renderer)
1781{
1782 @autoreleasepool {
1783 if (renderer->internal) {
1784 SDL3METAL_RenderData *data = CFBridgingRelease(renderer->internal);
1785
1786 if (data.mtlcmdencoder != nil) {
1787 [data.mtlcmdencoder endEncoding];
1788 }
1789
1790 DestroyAllPipelines(data.allpipelines, data.pipelinescount);
1791
1792 /* Release the metal view instead of destroying it,
1793 in case we want to use it later (recreating the renderer)
1794 */
1795 // SDL_Metal_DestroyView(data.mtlview);
1796 CFBridgingRelease(data.mtlview);
1797 }
1798 }
1799}
1800
1801static void *METAL_GetMetalLayer(SDL_Renderer *renderer)
1802{
1803 @autoreleasepool {
1804 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
1805 return (__bridge void *)data.mtllayer;
1806 }
1807}
1808
1809static void *METAL_GetMetalCommandEncoder(SDL_Renderer *renderer)
1810{
1811 @autoreleasepool {
1812 // note that data.mtlcmdencoder can be nil if METAL_ActivateRenderCommandEncoder fails.
1813 // Before SDL 2.0.18, it might have returned a non-nil encoding that might not have been
1814 // usable for presentation. Check your return values!
1815 SDL3METAL_RenderData *data;
1816 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, nil);
1817 data = (__bridge SDL3METAL_RenderData *)renderer->internal;
1818 return (__bridge void *)data.mtlcmdencoder;
1819 }
1820}
1821
1822static bool METAL_SetVSync(SDL_Renderer *renderer, const int vsync)
1823{
1824#if defined(SDL_PLATFORM_MACOS) || TARGET_OS_MACCATALYST
1825 SDL3METAL_RenderData *data = (__bridge SDL3METAL_RenderData *)renderer->internal;
1826 switch (vsync) {
1827 case 0:
1828 data.mtllayer.displaySyncEnabled = NO;
1829 break;
1830 case 1:
1831 data.mtllayer.displaySyncEnabled = YES;
1832 break;
1833 default:
1834 return SDL_Unsupported();
1835 }
1836 return true;
1837#else
1838 switch (vsync) {
1839 case 1:
1840 return true;
1841 default:
1842 return SDL_Unsupported();
1843 }
1844#endif
1845}
1846
1847static SDL_MetalView GetWindowView(SDL_Window *window)
1848{
1849#ifdef SDL_VIDEO_DRIVER_COCOA
1850 NSWindow *nswindow = (__bridge NSWindow *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL);
1851 NSInteger tag = (NSInteger)SDL_GetNumberProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER, 0);
1852 if (nswindow && tag) {
1853 NSView *view = nswindow.contentView;
1854 if (view.subviews.count > 0) {
1855 view = view.subviews[0];
1856 if (view.tag == tag) {
1857 return (SDL_MetalView)CFBridgingRetain(view);
1858 }
1859 }
1860 }
1861#endif
1862
1863#ifdef SDL_VIDEO_DRIVER_UIKIT
1864 UIWindow *uiwindow = (__bridge UIWindow *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, NULL);
1865 NSInteger tag = (NSInteger)SDL_GetNumberProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_UIKIT_METAL_VIEW_TAG_NUMBER, 0);
1866 if (uiwindow && tag) {
1867 UIView *view = uiwindow.rootViewController.view;
1868 if (view.tag == tag) {
1869 return (SDL_MetalView)CFBridgingRetain(view);
1870 }
1871 }
1872#endif
1873
1874 return nil;
1875}
1876
1877static bool METAL_CreateRenderer(SDL_Renderer *renderer, SDL_Window *window, SDL_PropertiesID create_props)
1878{
1879 @autoreleasepool {
1880 SDL3METAL_RenderData *data = NULL;
1881 id<MTLDevice> mtldevice = nil;
1882 SDL_MetalView view = NULL;
1883 CAMetalLayer *layer = nil;
1884 NSError *err = nil;
1885 dispatch_data_t mtllibdata;
1886 char *constantdata;
1887 int maxtexsize, quadcount = UINT16_MAX / 4;
1888 UInt16 *indexdata;
1889 size_t indicessize = sizeof(UInt16) * quadcount * 6;
1890 MTLSamplerDescriptor *samplerdesc;
1891 id<MTLCommandQueue> mtlcmdqueue;
1892 id<MTLLibrary> mtllibrary;
1893 id<MTLBuffer> mtlbufconstantstaging, mtlbufquadindicesstaging, mtlbufconstants, mtlbufquadindices;
1894 id<MTLCommandBuffer> cmdbuffer;
1895 id<MTLBlitCommandEncoder> blitcmd;
1896 bool scRGB_supported = false;
1897
1898 // Note: matrices are column major.
1899 float identitytransform[16] = {
1900 1.0f,
1901 0.0f,
1902 0.0f,
1903 0.0f,
1904 0.0f,
1905 1.0f,
1906 0.0f,
1907 0.0f,
1908 0.0f,
1909 0.0f,
1910 1.0f,
1911 0.0f,
1912 0.0f,
1913 0.0f,
1914 0.0f,
1915 1.0f,
1916 };
1917
1918 float halfpixeltransform[16] = {
1919 1.0f,
1920 0.0f,
1921 0.0f,
1922 0.0f,
1923 0.0f,
1924 1.0f,
1925 0.0f,
1926 0.0f,
1927 0.0f,
1928 0.0f,
1929 1.0f,
1930 0.0f,
1931 0.5f,
1932 0.5f,
1933 0.0f,
1934 1.0f,
1935 };
1936
1937 const size_t YCbCr_shader_matrix_size = 4 * 4 * sizeof(float);
1938
1939 SDL_SetupRendererColorspace(renderer, create_props);
1940
1941#ifndef SDL_PLATFORM_TVOS
1942 if (@available(macos 10.11, iOS 16.0, *)) {
1943 scRGB_supported = true;
1944 }
1945#endif
1946 if (renderer->output_colorspace != SDL_COLORSPACE_SRGB) {
1947 if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR && scRGB_supported) {
1948 // This colorspace is supported
1949 } else {
1950 return SDL_SetError("Unsupported output colorspace");
1951 }
1952 }
1953
1954#ifdef SDL_PLATFORM_MACOS
1955 if (SDL_GetHintBoolean(SDL_HINT_RENDER_METAL_PREFER_LOW_POWER_DEVICE, true)) {
1956 NSArray<id<MTLDevice>> *devices = MTLCopyAllDevices();
1957
1958 for (id<MTLDevice> device in devices) {
1959 if (device.isLowPower) {
1960 mtldevice = device;
1961 break;
1962 }
1963 }
1964 }
1965#endif
1966
1967 if (mtldevice == nil) {
1968 mtldevice = MTLCreateSystemDefaultDevice();
1969 }
1970
1971 if (mtldevice == nil) {
1972 return SDL_SetError("Failed to obtain Metal device");
1973 }
1974
1975 view = GetWindowView(window);
1976 if (view == nil) {
1977 view = SDL_Metal_CreateView(window);
1978 }
1979
1980 if (view == NULL) {
1981 return false;
1982 }
1983
1984 // !!! FIXME: error checking on all of this.
1985 data = [[SDL3METAL_RenderData alloc] init];
1986
1987 if (data == nil) {
1988 /* Release the metal view instead of destroying it,
1989 in case we want to use it later (recreating the renderer)
1990 */
1991 // SDL_Metal_DestroyView(view);
1992 CFBridgingRelease(view);
1993 return SDL_SetError("SDL3METAL_RenderData alloc/init failed");
1994 }
1995
1996 renderer->internal = (void *)CFBridgingRetain(data);
1997 METAL_InvalidateCachedState(renderer);
1998 renderer->window = window;
1999
2000 data.mtlview = view;
2001
2002#ifdef SDL_PLATFORM_MACOS
2003 layer = (CAMetalLayer *)[(__bridge NSView *)view layer];
2004#else
2005 layer = (CAMetalLayer *)[(__bridge UIView *)view layer];
2006#endif
2007
2008#ifndef SDL_PLATFORM_TVOS
2009 if (renderer->output_colorspace == SDL_COLORSPACE_SRGB_LINEAR) {
2010 if (@available(macos 10.11, iOS 16.0, *)) {
2011 layer.wantsExtendedDynamicRangeContent = YES;
2012 } else {
2013 SDL_assert(!"Logic error, scRGB is not actually supported");
2014 }
2015 layer.pixelFormat = MTLPixelFormatRGBA16Float;
2016
2017 const CFStringRef name = kCGColorSpaceExtendedLinearSRGB;
2018 CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(name);
2019 layer.colorspace = colorspace;
2020 CGColorSpaceRelease(colorspace);
2021 }
2022#endif // !SDL_PLATFORM_TVOS
2023
2024 layer.device = mtldevice;
2025
2026 // Necessary for RenderReadPixels.
2027 layer.framebufferOnly = NO;
2028
2029 data.mtldevice = layer.device;
2030 data.mtllayer = layer;
2031 mtlcmdqueue = [data.mtldevice newCommandQueue];
2032 data.mtlcmdqueue = mtlcmdqueue;
2033 data.mtlcmdqueue.label = @"SDL Metal Renderer";
2034 data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
2035
2036 // The compiled .metallib is embedded in a static array in a header file
2037 // but the original shader source code is in SDL_shaders_metal.metal.
2038 mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{
2039 });
2040 mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
2041 data.mtllibrary = mtllibrary;
2042 SDL_assert(err == nil);
2043 data.mtllibrary.label = @"SDL Metal renderer shader library";
2044
2045 // Do some shader pipeline state loading up-front rather than on demand.
2046 data.pipelinescount = 0;
2047 data.allpipelines = NULL;
2048 ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm);
2049
2050 static struct
2051 {
2052 MTLSamplerMinMagFilter filter;
2053 MTLSamplerAddressMode address;
2054 } samplerParams[] = {
2055 { MTLSamplerMinMagFilterNearest, MTLSamplerAddressModeClampToEdge },
2056 { MTLSamplerMinMagFilterNearest, MTLSamplerAddressModeRepeat },
2057 { MTLSamplerMinMagFilterLinear, MTLSamplerAddressModeClampToEdge },
2058 { MTLSamplerMinMagFilterLinear, MTLSamplerAddressModeRepeat },
2059 };
2060 SDL_COMPILE_TIME_ASSERT(samplerParams_SIZE, SDL_arraysize(samplerParams) == SDL_NUM_METAL_SAMPLERS);
2061
2062 data.mtlsamplers = [[NSMutableArray<id<MTLSamplerState>> alloc] init];
2063 samplerdesc = [[MTLSamplerDescriptor alloc] init];
2064 for (int i = 0; i < SDL_arraysize(samplerParams); ++i) {
2065 samplerdesc.minFilter = samplerParams[i].filter;
2066 samplerdesc.magFilter = samplerParams[i].filter;
2067 samplerdesc.sAddressMode = samplerParams[i].address;
2068 samplerdesc.tAddressMode = samplerParams[i].address;
2069 [data.mtlsamplers addObject:[data.mtldevice newSamplerStateWithDescriptor:samplerdesc]];
2070 }
2071
2072 mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared];
2073
2074 constantdata = [mtlbufconstantstaging contents];
2075 SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform));
2076 SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform));
2077 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601_LIMITED, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT601_LIMITED, 0, 0, 8), YCbCr_shader_matrix_size);
2078 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601_FULL, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT601_FULL, 0, 0, 8), YCbCr_shader_matrix_size);
2079 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709_LIMITED, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT709_LIMITED, 0, 0, 8), YCbCr_shader_matrix_size);
2080 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709_FULL, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT709_FULL, 0, 0, 8), YCbCr_shader_matrix_size);
2081 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT2020_LIMITED, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT2020_LIMITED, 0, 0, 10), YCbCr_shader_matrix_size);
2082 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT2020_FULL, SDL_GetYCbCRtoRGBConversionMatrix(SDL_COLORSPACE_BT2020_FULL, 0, 0, 10), YCbCr_shader_matrix_size);
2083
2084 mtlbufquadindicesstaging = [data.mtldevice newBufferWithLength:indicessize options:MTLResourceStorageModeShared];
2085
2086 /* Quads in the following vertex order (matches the FillRects vertices):
2087 * 1---3
2088 * | \ |
2089 * 0---2
2090 */
2091 indexdata = [mtlbufquadindicesstaging contents];
2092 for (int i = 0; i < quadcount; i++) {
2093 indexdata[i * 6 + 0] = i * 4 + 0;
2094 indexdata[i * 6 + 1] = i * 4 + 1;
2095 indexdata[i * 6 + 2] = i * 4 + 2;
2096
2097 indexdata[i * 6 + 3] = i * 4 + 2;
2098 indexdata[i * 6 + 4] = i * 4 + 1;
2099 indexdata[i * 6 + 5] = i * 4 + 3;
2100 }
2101
2102 mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate];
2103 data.mtlbufconstants = mtlbufconstants;
2104 data.mtlbufconstants.label = @"SDL constant data";
2105
2106 mtlbufquadindices = [data.mtldevice newBufferWithLength:indicessize options:MTLResourceStorageModePrivate];
2107 data.mtlbufquadindices = mtlbufquadindices;
2108 data.mtlbufquadindices.label = @"SDL quad index buffer";
2109
2110 cmdbuffer = [data.mtlcmdqueue commandBuffer];
2111 blitcmd = [cmdbuffer blitCommandEncoder];
2112
2113 [blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH];
2114 [blitcmd copyFromBuffer:mtlbufquadindicesstaging sourceOffset:0 toBuffer:mtlbufquadindices destinationOffset:0 size:indicessize];
2115
2116 [blitcmd endEncoding];
2117 [cmdbuffer commit];
2118
2119 // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
2120
2121 renderer->WindowEvent = METAL_WindowEvent;
2122 renderer->GetOutputSize = METAL_GetOutputSize;
2123 renderer->SupportsBlendMode = METAL_SupportsBlendMode;
2124 renderer->CreateTexture = METAL_CreateTexture;
2125 renderer->UpdateTexture = METAL_UpdateTexture;
2126#ifdef SDL_HAVE_YUV
2127 renderer->UpdateTextureYUV = METAL_UpdateTextureYUV;
2128 renderer->UpdateTextureNV = METAL_UpdateTextureNV;
2129#endif
2130 renderer->LockTexture = METAL_LockTexture;
2131 renderer->UnlockTexture = METAL_UnlockTexture;
2132 renderer->SetTextureScaleMode = METAL_SetTextureScaleMode;
2133 renderer->SetRenderTarget = METAL_SetRenderTarget;
2134 renderer->QueueSetViewport = METAL_QueueSetViewport;
2135 renderer->QueueSetDrawColor = METAL_QueueNoOp;
2136 renderer->QueueDrawPoints = METAL_QueueDrawPoints;
2137 renderer->QueueDrawLines = METAL_QueueDrawLines;
2138 renderer->QueueGeometry = METAL_QueueGeometry;
2139 renderer->InvalidateCachedState = METAL_InvalidateCachedState;
2140 renderer->RunCommandQueue = METAL_RunCommandQueue;
2141 renderer->RenderReadPixels = METAL_RenderReadPixels;
2142 renderer->RenderPresent = METAL_RenderPresent;
2143 renderer->DestroyTexture = METAL_DestroyTexture;
2144 renderer->DestroyRenderer = METAL_DestroyRenderer;
2145 renderer->SetVSync = METAL_SetVSync;
2146 renderer->GetMetalLayer = METAL_GetMetalLayer;
2147 renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
2148
2149 renderer->name = METAL_RenderDriver.name;
2150 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ARGB8888);
2151 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR8888);
2152 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_ABGR2101010);
2153 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA64_FLOAT);
2154 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_RGBA128_FLOAT);
2155 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_YV12);
2156 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_IYUV);
2157 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV12);
2158 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_NV21);
2159 SDL_AddSupportedTextureFormat(renderer, SDL_PIXELFORMAT_P010);
2160
2161#if defined(SDL_PLATFORM_MACOS) || TARGET_OS_MACCATALYST
2162 data.mtllayer.displaySyncEnabled = NO;
2163#endif
2164
2165 // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
2166 maxtexsize = 4096;
2167#if defined(SDL_PLATFORM_MACOS) || TARGET_OS_MACCATALYST
2168 maxtexsize = 16384;
2169#elif defined(SDL_PLATFORM_TVOS)
2170 maxtexsize = 8192;
2171 if ([mtldevice supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) {
2172 maxtexsize = 16384;
2173 }
2174#else
2175 if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) {
2176 maxtexsize = 16384;
2177 } else if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) {
2178 maxtexsize = 16384;
2179 } else if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2] || [mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) {
2180 maxtexsize = 8192;
2181 } else {
2182 maxtexsize = 4096;
2183 }
2184#endif
2185
2186 SDL_SetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, maxtexsize);
2187
2188 return true;
2189 }
2190}
2191
2192SDL_RenderDriver METAL_RenderDriver = {
2193 METAL_CreateRenderer, "metal"
2194};
2195
2196#endif // SDL_VIDEO_RENDER_METAL