SDL 2.0
SDL_render_metal.m
Go to the documentation of this file.
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2019 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#if SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED
24
25#include "SDL_hints.h"
26#include "SDL_log.h"
27#include "SDL_assert.h"
28#include "SDL_syswm.h"
29#include "../SDL_sysrender.h"
30
31#ifdef __MACOSX__
32#include "../../video/cocoa/SDL_cocoametalview.h"
33#else
34#include "../../video/uikit/SDL_uikitmetalview.h"
35#endif
36#include <Availability.h>
37#import <Metal/Metal.h>
38#import <QuartzCore/CAMetalLayer.h>
39
40/* Regenerate these with build-metal-shaders.sh */
41#ifdef __MACOSX__
43#else
45#endif
46
47/* Apple Metal renderer implementation */
48
49/* macOS requires constants in a buffer to have a 256 byte alignment. */
50#ifdef __MACOSX__
51#define CONSTANT_ALIGN 256
52#else
53#define CONSTANT_ALIGN 4
54#endif
55
56#define ALIGN_CONSTANTS(size) ((size + CONSTANT_ALIGN - 1) & (~(CONSTANT_ALIGN - 1)))
57
58static const size_t CONSTANTS_OFFSET_INVALID = 0xFFFFFFFF;
59static const size_t CONSTANTS_OFFSET_IDENTITY = 0;
60static const size_t CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM = ALIGN_CONSTANTS(CONSTANTS_OFFSET_IDENTITY + sizeof(float) * 16);
61static const size_t CONSTANTS_OFFSET_DECODE_JPEG = ALIGN_CONSTANTS(CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM + sizeof(float) * 16);
62static const size_t CONSTANTS_OFFSET_DECODE_BT601 = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_JPEG + sizeof(float) * 4 * 4);
63static const size_t CONSTANTS_OFFSET_DECODE_BT709 = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_BT601 + sizeof(float) * 4 * 4);
64static const size_t CONSTANTS_LENGTH = CONSTANTS_OFFSET_DECODE_BT709 + sizeof(float) * 4 * 4;
65
66typedef enum SDL_MetalVertexFunction
67{
68 SDL_METAL_VERTEX_SOLID,
69 SDL_METAL_VERTEX_COPY,
70} SDL_MetalVertexFunction;
71
72typedef enum SDL_MetalFragmentFunction
73{
74 SDL_METAL_FRAGMENT_SOLID = 0,
75 SDL_METAL_FRAGMENT_COPY,
76 SDL_METAL_FRAGMENT_YUV,
77 SDL_METAL_FRAGMENT_NV12,
78 SDL_METAL_FRAGMENT_NV21,
79 SDL_METAL_FRAGMENT_COUNT,
80} SDL_MetalFragmentFunction;
81
82typedef struct METAL_PipelineState
83{
85 void *pipe;
86} METAL_PipelineState;
87
88typedef struct METAL_PipelineCache
89{
90 METAL_PipelineState *states;
91 int count;
92 SDL_MetalVertexFunction vertexFunction;
93 SDL_MetalFragmentFunction fragmentFunction;
94 MTLPixelFormat renderTargetFormat;
95 const char *label;
96} METAL_PipelineCache;
97
98/* Each shader combination used by drawing functions has a separate pipeline
99 * cache, and we have a separate list of caches for each render target pixel
100 * format. This is more efficient than iterating over a global cache to find
101 * the pipeline based on the specified shader combination and RT pixel format,
102 * since we know what the RT pixel format is when we set the render target, and
103 * we know what the shader combination is inside each drawing function's code. */
104typedef struct METAL_ShaderPipelines
105{
106 MTLPixelFormat renderTargetFormat;
107 METAL_PipelineCache caches[SDL_METAL_FRAGMENT_COUNT];
108} METAL_ShaderPipelines;
109
110@interface METAL_RenderData : NSObject
111 @property (nonatomic, retain) id<MTLDevice> mtldevice;
112 @property (nonatomic, retain) id<MTLCommandQueue> mtlcmdqueue;
113 @property (nonatomic, retain) id<MTLCommandBuffer> mtlcmdbuffer;
114 @property (nonatomic, retain) id<MTLRenderCommandEncoder> mtlcmdencoder;
115 @property (nonatomic, retain) id<MTLLibrary> mtllibrary;
116 @property (nonatomic, retain) id<CAMetalDrawable> mtlbackbuffer;
117 @property (nonatomic, retain) id<MTLSamplerState> mtlsamplernearest;
118 @property (nonatomic, retain) id<MTLSamplerState> mtlsamplerlinear;
119 @property (nonatomic, retain) id<MTLBuffer> mtlbufconstants;
120 @property (nonatomic, retain) id<MTLBuffer> mtlbufquadindices;
121 @property (nonatomic, retain) CAMetalLayer *mtllayer;
122 @property (nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
123 @property (nonatomic, assign) METAL_ShaderPipelines *activepipelines;
124 @property (nonatomic, assign) METAL_ShaderPipelines *allpipelines;
125 @property (nonatomic, assign) int pipelinescount;
126@end
127
128@implementation METAL_RenderData
129#if !__has_feature(objc_arc)
130- (void)dealloc
131{
132 [_mtldevice release];
133 [_mtlcmdqueue release];
134 [_mtlcmdbuffer release];
135 [_mtlcmdencoder release];
136 [_mtllibrary release];
137 [_mtlbackbuffer release];
138 [_mtlsamplernearest release];
139 [_mtlsamplerlinear release];
140 [_mtlbufconstants release];
141 [_mtlbufquadindices release];
142 [_mtllayer release];
143 [_mtlpassdesc release];
144 [super dealloc];
145}
146#endif
147@end
148
149@interface METAL_TextureData : NSObject
150 @property (nonatomic, retain) id<MTLTexture> mtltexture;
151 @property (nonatomic, retain) id<MTLTexture> mtltexture_uv;
152 @property (nonatomic, retain) id<MTLSamplerState> mtlsampler;
153 @property (nonatomic, assign) SDL_MetalFragmentFunction fragmentFunction;
154 @property (nonatomic, assign) BOOL yuv;
155 @property (nonatomic, assign) BOOL nv12;
156 @property (nonatomic, assign) size_t conversionBufferOffset;
157 @property (nonatomic, assign) BOOL hasdata;
158
159 @property (nonatomic, retain) id<MTLBuffer> lockedbuffer;
160 @property (nonatomic, assign) SDL_Rect lockedrect;
161@end
162
163@implementation METAL_TextureData
164#if !__has_feature(objc_arc)
165- (void)dealloc
166{
167 [_mtltexture release];
168 [_mtltexture_uv release];
169 [_mtlsampler release];
170 [super dealloc];
171}
172#endif
173@end
174
175static int
176IsMetalAvailable(const SDL_SysWMinfo *syswm)
177{
178 if (syswm->subsystem != SDL_SYSWM_COCOA && syswm->subsystem != SDL_SYSWM_UIKIT) {
179 return SDL_SetError("Metal render target only supports Cocoa and UIKit video targets at the moment.");
180 }
181
182 // this checks a weak symbol.
183#if (defined(__MACOSX__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101100))
184 if (MTLCreateSystemDefaultDevice == NULL) { // probably on 10.10 or lower.
185 return SDL_SetError("Metal framework not available on this system");
186 }
187#endif
188
189 return 0;
190}
191
192static const MTLBlendOperation invalidBlendOperation = (MTLBlendOperation)0xFFFFFFFF;
193static const MTLBlendFactor invalidBlendFactor = (MTLBlendFactor)0xFFFFFFFF;
194
195static MTLBlendOperation
196GetBlendOperation(SDL_BlendOperation operation)
197{
198 switch (operation) {
199 case SDL_BLENDOPERATION_ADD: return MTLBlendOperationAdd;
200 case SDL_BLENDOPERATION_SUBTRACT: return MTLBlendOperationSubtract;
201 case SDL_BLENDOPERATION_REV_SUBTRACT: return MTLBlendOperationReverseSubtract;
202 case SDL_BLENDOPERATION_MINIMUM: return MTLBlendOperationMin;
203 case SDL_BLENDOPERATION_MAXIMUM: return MTLBlendOperationMax;
204 default: return invalidBlendOperation;
205 }
206}
207
208static MTLBlendFactor
209GetBlendFactor(SDL_BlendFactor factor)
210{
211 switch (factor) {
212 case SDL_BLENDFACTOR_ZERO: return MTLBlendFactorZero;
213 case SDL_BLENDFACTOR_ONE: return MTLBlendFactorOne;
214 case SDL_BLENDFACTOR_SRC_COLOR: return MTLBlendFactorSourceColor;
215 case SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return MTLBlendFactorOneMinusSourceColor;
216 case SDL_BLENDFACTOR_SRC_ALPHA: return MTLBlendFactorSourceAlpha;
217 case SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return MTLBlendFactorOneMinusSourceAlpha;
218 case SDL_BLENDFACTOR_DST_COLOR: return MTLBlendFactorDestinationColor;
219 case SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR: return MTLBlendFactorOneMinusDestinationColor;
220 case SDL_BLENDFACTOR_DST_ALPHA: return MTLBlendFactorDestinationAlpha;
221 case SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return MTLBlendFactorOneMinusDestinationAlpha;
222 default: return invalidBlendFactor;
223 }
224}
225
226static NSString *
227GetVertexFunctionName(SDL_MetalVertexFunction function)
228{
229 switch (function) {
230 case SDL_METAL_VERTEX_SOLID: return @"SDL_Solid_vertex";
231 case SDL_METAL_VERTEX_COPY: return @"SDL_Copy_vertex";
232 default: return nil;
233 }
234}
235
236static NSString *
237GetFragmentFunctionName(SDL_MetalFragmentFunction function)
238{
239 switch (function) {
240 case SDL_METAL_FRAGMENT_SOLID: return @"SDL_Solid_fragment";
241 case SDL_METAL_FRAGMENT_COPY: return @"SDL_Copy_fragment";
242 case SDL_METAL_FRAGMENT_YUV: return @"SDL_YUV_fragment";
243 case SDL_METAL_FRAGMENT_NV12: return @"SDL_NV12_fragment";
244 case SDL_METAL_FRAGMENT_NV21: return @"SDL_NV21_fragment";
245 default: return nil;
246 }
247}
248
250MakePipelineState(METAL_RenderData *data, METAL_PipelineCache *cache,
251 NSString *blendlabel, SDL_BlendMode blendmode)
252{
253 id<MTLFunction> mtlvertfn = [data.mtllibrary newFunctionWithName:GetVertexFunctionName(cache->vertexFunction)];
254 id<MTLFunction> mtlfragfn = [data.mtllibrary newFunctionWithName:GetFragmentFunctionName(cache->fragmentFunction)];
255 SDL_assert(mtlvertfn != nil);
256 SDL_assert(mtlfragfn != nil);
257
258 MTLRenderPipelineDescriptor *mtlpipedesc = [[MTLRenderPipelineDescriptor alloc] init];
259 mtlpipedesc.vertexFunction = mtlvertfn;
260 mtlpipedesc.fragmentFunction = mtlfragfn;
261
262 MTLRenderPipelineColorAttachmentDescriptor *rtdesc = mtlpipedesc.colorAttachments[0];
263
264 rtdesc.pixelFormat = cache->renderTargetFormat;
265
266 if (blendmode != SDL_BLENDMODE_NONE) {
267 rtdesc.blendingEnabled = YES;
268 rtdesc.sourceRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcColorFactor(blendmode));
269 rtdesc.destinationRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeDstColorFactor(blendmode));
270 rtdesc.rgbBlendOperation = GetBlendOperation(SDL_GetBlendModeColorOperation(blendmode));
271 rtdesc.sourceAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcAlphaFactor(blendmode));
272 rtdesc.destinationAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeDstAlphaFactor(blendmode));
273 rtdesc.alphaBlendOperation = GetBlendOperation(SDL_GetBlendModeAlphaOperation(blendmode));
274 } else {
275 rtdesc.blendingEnabled = NO;
276 }
277
278 mtlpipedesc.label = [@(cache->label) stringByAppendingString:blendlabel];
279
280 NSError *err = nil;
281 id<MTLRenderPipelineState> state = [data.mtldevice newRenderPipelineStateWithDescriptor:mtlpipedesc error:&err];
282 SDL_assert(err == nil);
283
284 METAL_PipelineState pipeline;
285 pipeline.blendMode = blendmode;
286 pipeline.pipe = (void *)CFBridgingRetain(state);
287
288 METAL_PipelineState *states = SDL_realloc(cache->states, (cache->count + 1) * sizeof(pipeline));
289
290#if !__has_feature(objc_arc)
291 [mtlpipedesc release]; // !!! FIXME: can these be reused for each creation, or does the pipeline obtain it?
292 [mtlvertfn release];
293 [mtlfragfn release];
294 [state release];
295#endif
296
297 if (states) {
298 states[cache->count++] = pipeline;
299 cache->states = states;
300 return (__bridge id<MTLRenderPipelineState>)pipeline.pipe;
301 } else {
302 CFBridgingRelease(pipeline.pipe);
304 return NULL;
305 }
306}
307
308static void
309MakePipelineCache(METAL_RenderData *data, METAL_PipelineCache *cache, const char *label,
310 MTLPixelFormat rtformat, SDL_MetalVertexFunction vertfn, SDL_MetalFragmentFunction fragfn)
311{
312 SDL_zerop(cache);
313
314 cache->vertexFunction = vertfn;
315 cache->fragmentFunction = fragfn;
316 cache->renderTargetFormat = rtformat;
317 cache->label = label;
318
319 /* Create pipeline states for the default blend modes. Custom blend modes
320 * will be added to the cache on-demand. */
321 MakePipelineState(data, cache, @" (blend=none)", SDL_BLENDMODE_NONE);
322 MakePipelineState(data, cache, @" (blend=blend)", SDL_BLENDMODE_BLEND);
323 MakePipelineState(data, cache, @" (blend=add)", SDL_BLENDMODE_ADD);
324 MakePipelineState(data, cache, @" (blend=mod)", SDL_BLENDMODE_MOD);
325}
326
327static void
328DestroyPipelineCache(METAL_PipelineCache *cache)
329{
330 if (cache != NULL) {
331 for (int i = 0; i < cache->count; i++) {
332 CFBridgingRelease(cache->states[i].pipe);
333 }
334
335 SDL_free(cache->states);
336 }
337}
338
339void
340MakeShaderPipelines(METAL_RenderData *data, METAL_ShaderPipelines *pipelines, MTLPixelFormat rtformat)
341{
343
344 pipelines->renderTargetFormat = rtformat;
345
346 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_SOLID], "SDL primitives pipeline", rtformat, SDL_METAL_VERTEX_SOLID, SDL_METAL_FRAGMENT_SOLID);
347 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_COPY], "SDL copy pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_COPY);
348 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_YUV], "SDL YUV pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_YUV);
349 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV12], "SDL NV12 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV12);
350 MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV21], "SDL NV21 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV21);
351}
352
353static METAL_ShaderPipelines *
354ChooseShaderPipelines(METAL_RenderData *data, MTLPixelFormat rtformat)
355{
356 METAL_ShaderPipelines *allpipelines = data.allpipelines;
357 int count = data.pipelinescount;
358
359 for (int i = 0; i < count; i++) {
360 if (allpipelines[i].renderTargetFormat == rtformat) {
361 return &allpipelines[i];
362 }
363 }
364
365 allpipelines = SDL_realloc(allpipelines, (count + 1) * sizeof(METAL_ShaderPipelines));
366
367 if (allpipelines == NULL) {
369 return NULL;
370 }
371
372 MakeShaderPipelines(data, &allpipelines[count], rtformat);
373
374 data.allpipelines = allpipelines;
375 data.pipelinescount = count + 1;
376
377 return &data.allpipelines[count];
378}
379
380static void
381DestroyAllPipelines(METAL_ShaderPipelines *allpipelines, int count)
382{
383 if (allpipelines != NULL) {
384 for (int i = 0; i < count; i++) {
385 for (int cache = 0; cache < SDL_METAL_FRAGMENT_COUNT; cache++) {
386 DestroyPipelineCache(&allpipelines[i].caches[cache]);
387 }
388 }
389
390 SDL_free(allpipelines);
391 }
392}
393
394static inline id<MTLRenderPipelineState>
395ChoosePipelineState(METAL_RenderData *data, METAL_ShaderPipelines *pipelines, SDL_MetalFragmentFunction fragfn, SDL_BlendMode blendmode)
396{
397 METAL_PipelineCache *cache = &pipelines->caches[fragfn];
398
399 for (int i = 0; i < cache->count; i++) {
400 if (cache->states[i].blendMode == blendmode) {
401 return (__bridge id<MTLRenderPipelineState>)cache->states[i].pipe;
402 }
403 }
404
405 return MakePipelineState(data, cache, [NSString stringWithFormat:@" (blend=custom 0x%x)", blendmode], blendmode);
406}
407
408static void
409METAL_ActivateRenderCommandEncoder(SDL_Renderer * renderer, MTLLoadAction load, MTLClearColor *clear_color)
410{
411 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
412
413 /* Our SetRenderTarget just signals that the next render operation should
414 * set up a new render pass. This is where that work happens. */
415 if (data.mtlcmdencoder == nil) {
416 id<MTLTexture> mtltexture = nil;
417
418 if (renderer->target != NULL) {
419 METAL_TextureData *texdata = (__bridge METAL_TextureData *)renderer->target->driverdata;
420 mtltexture = texdata.mtltexture;
421 } else {
422 if (data.mtlbackbuffer == nil) {
423 /* The backbuffer's contents aren't guaranteed to persist after
424 * presenting, so we can leave it undefined when loading it. */
425 data.mtlbackbuffer = [data.mtllayer nextDrawable];
426 if (load == MTLLoadActionLoad) {
427 load = MTLLoadActionDontCare;
428 }
429 }
430 mtltexture = data.mtlbackbuffer.texture;
431 }
432
433 SDL_assert(mtltexture);
434
435 if (load == MTLLoadActionClear) {
436 SDL_assert(clear_color != NULL);
437 data.mtlpassdesc.colorAttachments[0].clearColor = *clear_color;
438 }
439
440 data.mtlpassdesc.colorAttachments[0].loadAction = load;
441 data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
442
443 data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
444 data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
445
446 if (data.mtlbackbuffer != nil && mtltexture == data.mtlbackbuffer.texture) {
447 data.mtlcmdencoder.label = @"SDL metal renderer backbuffer";
448 } else {
449 data.mtlcmdencoder.label = @"SDL metal renderer render target";
450 }
451
452 data.activepipelines = ChooseShaderPipelines(data, mtltexture.pixelFormat);
453
454 // make sure this has a definite place in the queue. This way it will
455 // execute reliably whether the app tries to make its own command buffers
456 // or whatever. This means we can _always_ batch rendering commands!
457 [data.mtlcmdbuffer enqueue];
458 }
459}
460
461static void
462METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
463{
464 if (event->event == SDL_WINDOWEVENT_SHOWN ||
465 event->event == SDL_WINDOWEVENT_HIDDEN) {
466 // !!! FIXME: write me
467 }
468}
469
470static int
471METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
472{ @autoreleasepool {
473 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
474 if (w) {
475 *w = (int)data.mtllayer.drawableSize.width;
476 }
477 if (h) {
478 *h = (int)data.mtllayer.drawableSize.height;
479 }
480 return 0;
481}}
482
483static SDL_bool
484METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode)
485{
492
493 if (GetBlendFactor(srcColorFactor) == invalidBlendFactor ||
494 GetBlendFactor(srcAlphaFactor) == invalidBlendFactor ||
495 GetBlendOperation(colorOperation) == invalidBlendOperation ||
496 GetBlendFactor(dstColorFactor) == invalidBlendFactor ||
497 GetBlendFactor(dstAlphaFactor) == invalidBlendFactor ||
498 GetBlendOperation(alphaOperation) == invalidBlendOperation) {
499 return SDL_FALSE;
500 }
501 return SDL_TRUE;
502}
503
504static int
505METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
506{ @autoreleasepool {
507 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
508 MTLPixelFormat pixfmt;
509
510 switch (texture->format) {
512 pixfmt = MTLPixelFormatRGBA8Unorm;
513 break;
515 pixfmt = MTLPixelFormatBGRA8Unorm;
516 break;
521 pixfmt = MTLPixelFormatR8Unorm;
522 break;
523 default:
524 return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
525 }
526
527 MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixfmt
528 width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO];
529
530 /* Not available in iOS 8. */
531 if ([mtltexdesc respondsToSelector:@selector(usage)]) {
532 if (texture->access == SDL_TEXTUREACCESS_TARGET) {
533 mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
534 } else {
535 mtltexdesc.usage = MTLTextureUsageShaderRead;
536 }
537 }
538
539 id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
540 if (mtltexture == nil) {
541 return SDL_SetError("Texture allocation failed");
542 }
543
544 id<MTLTexture> mtltexture_uv = nil;
545
546 BOOL yuv = (texture->format == SDL_PIXELFORMAT_IYUV) || (texture->format == SDL_PIXELFORMAT_YV12);
547 BOOL nv12 = (texture->format == SDL_PIXELFORMAT_NV12) || (texture->format == SDL_PIXELFORMAT_NV21);
548
549 if (yuv) {
550 mtltexdesc.pixelFormat = MTLPixelFormatR8Unorm;
551 mtltexdesc.width = (texture->w + 1) / 2;
552 mtltexdesc.height = (texture->h + 1) / 2;
553 mtltexdesc.textureType = MTLTextureType2DArray;
554 mtltexdesc.arrayLength = 2;
555 } else if (nv12) {
556 mtltexdesc.pixelFormat = MTLPixelFormatRG8Unorm;
557 mtltexdesc.width = (texture->w + 1) / 2;
558 mtltexdesc.height = (texture->h + 1) / 2;
559 }
560
561 if (yuv || nv12) {
562 mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
563 if (mtltexture_uv == nil) {
564#if !__has_feature(objc_arc)
565 [mtltexture release];
566#endif
567 return SDL_SetError("Texture allocation failed");
568 }
569 }
570
571 METAL_TextureData *texturedata = [[METAL_TextureData alloc] init];
572 if (texture->scaleMode == SDL_ScaleModeNearest) {
573 texturedata.mtlsampler = data.mtlsamplernearest;
574 } else {
575 texturedata.mtlsampler = data.mtlsamplerlinear;
576 }
577 texturedata.mtltexture = mtltexture;
578 texturedata.mtltexture_uv = mtltexture_uv;
579
580 texturedata.yuv = yuv;
581 texturedata.nv12 = nv12;
582
583 if (yuv) {
584 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV;
585 } else if (texture->format == SDL_PIXELFORMAT_NV12) {
586 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV12;
587 } else if (texture->format == SDL_PIXELFORMAT_NV21) {
588 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV21;
589 } else {
590 texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY;
591 }
592
593 if (yuv || nv12) {
594 size_t offset = 0;
596 switch (mode) {
597 case SDL_YUV_CONVERSION_JPEG: offset = CONSTANTS_OFFSET_DECODE_JPEG; break;
598 case SDL_YUV_CONVERSION_BT601: offset = CONSTANTS_OFFSET_DECODE_BT601; break;
599 case SDL_YUV_CONVERSION_BT709: offset = CONSTANTS_OFFSET_DECODE_BT709; break;
600 default: offset = 0; break;
601 }
602 texturedata.conversionBufferOffset = offset;
603 }
604
605 texture->driverdata = (void*)CFBridgingRetain(texturedata);
606
607#if !__has_feature(objc_arc)
608 [texturedata release];
609 [mtltexture release];
610 [mtltexture_uv release];
611#endif
612
613 return 0;
614}}
615
616static void
617METAL_UploadTextureData(id<MTLTexture> texture, SDL_Rect rect, int slice,
618 const void * pixels, int pitch)
619{
620 [texture replaceRegion:MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h)
621 mipmapLevel:0
622 slice:slice
623 withBytes:pixels
624 bytesPerRow:pitch
625 bytesPerImage:0];
626}
627
628static MTLStorageMode
629METAL_GetStorageMode(id<MTLResource> resource)
630{
631 /* iOS 8 does not have this method. */
632 if ([resource respondsToSelector:@selector(storageMode)]) {
633 return resource.storageMode;
634 }
635 return MTLStorageModeShared;
636}
637
638static int
639METAL_UpdateTextureInternal(SDL_Renderer * renderer, METAL_TextureData *texturedata,
641 const void * pixels, int pitch)
642{
643 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
644 SDL_Rect stagingrect = {0, 0, rect.w, rect.h};
645 MTLTextureDescriptor *desc;
646
647 /* If the texture is managed or shared and this is the first upload, we can
648 * use replaceRegion to upload to it directly. Otherwise we upload the data
649 * to a staging texture and copy that over. */
650 if (!texturedata.hasdata && METAL_GetStorageMode(texture) != MTLStorageModePrivate) {
651 METAL_UploadTextureData(texture, rect, slice, pixels, pitch);
652 return 0;
653 }
654
655 desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:texture.pixelFormat
656 width:rect.w
657 height:rect.h
658 mipmapped:NO];
659
660 if (desc == nil) {
661 return SDL_OutOfMemory();
662 }
663
664 /* TODO: We could have a pool of textures or a MTLHeap we allocate from,
665 * and release a staging texture back to the pool in the command buffer's
666 * completion handler. */
667 id<MTLTexture> stagingtex = [data.mtldevice newTextureWithDescriptor:desc];
668 if (stagingtex == nil) {
669 return SDL_OutOfMemory();
670 }
671
672#if !__has_feature(objc_arc)
673 [stagingtex autorelease];
674#endif
675
676 METAL_UploadTextureData(stagingtex, stagingrect, 0, pixels, pitch);
677
678 if (data.mtlcmdencoder != nil) {
679 [data.mtlcmdencoder endEncoding];
680 data.mtlcmdencoder = nil;
681 }
682
683 if (data.mtlcmdbuffer == nil) {
684 data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
685 }
686
687 id<MTLBlitCommandEncoder> blitcmd = [data.mtlcmdbuffer blitCommandEncoder];
688
689 [blitcmd copyFromTexture:stagingtex
690 sourceSlice:0
691 sourceLevel:0
692 sourceOrigin:MTLOriginMake(0, 0, 0)
693 sourceSize:MTLSizeMake(rect.w, rect.h, 1)
694 toTexture:texture
695 destinationSlice:slice
696 destinationLevel:0
697 destinationOrigin:MTLOriginMake(rect.x, rect.y, 0)];
698
699 [blitcmd endEncoding];
700
701 /* TODO: This isn't very efficient for the YUV formats, which call
702 * UpdateTextureInternal multiple times in a row. */
703 [data.mtlcmdbuffer commit];
704 data.mtlcmdbuffer = nil;
705
706 return 0;
707}
708
709static int
710METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
711 const SDL_Rect * rect, const void *pixels, int pitch)
712{ @autoreleasepool {
713 METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
714
715 if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture, *rect, 0, pixels, pitch) < 0) {
716 return -1;
717 }
718
719 if (texturedata.yuv) {
720 int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0;
721 int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1;
722 int UVpitch = (pitch + 1) / 2;
723 SDL_Rect UVrect = {rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2};
724
725 /* Skip to the correct offset into the next texture */
726 pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
727 if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, Uslice, pixels, UVpitch) < 0) {
728 return -1;
729 }
730
731 /* Skip to the correct offset into the next texture */
732 pixels = (const void*)((const Uint8*)pixels + UVrect.h * UVpitch);
733 if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, Vslice, pixels, UVpitch) < 0) {
734 return -1;
735 }
736 }
737
738 if (texturedata.nv12) {
739 SDL_Rect UVrect = {rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2};
740 int UVpitch = 2 * ((pitch + 1) / 2);
741
742 /* Skip to the correct offset into the next texture */
743 pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
744 if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, 0, pixels, UVpitch) < 0) {
745 return -1;
746 }
747 }
748
749 texturedata.hasdata = YES;
750
751 return 0;
752}}
753
754static int
755METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
756 const SDL_Rect * rect,
757 const Uint8 *Yplane, int Ypitch,
758 const Uint8 *Uplane, int Upitch,
759 const Uint8 *Vplane, int Vpitch)
760{ @autoreleasepool {
761 METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
762 const int Uslice = 0;
763 const int Vslice = 1;
764 SDL_Rect UVrect = {rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2};
765
766 /* Bail out if we're supposed to update an empty rectangle */
767 if (rect->w <= 0 || rect->h <= 0) {
768 return 0;
769 }
770
771 if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture, *rect, 0, Yplane, Ypitch) < 0) {
772 return -1;
773 }
774 if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, Uslice, Uplane, Upitch)) {
775 return -1;
776 }
777 if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, Vslice, Vplane, Vpitch)) {
778 return -1;
779 }
780
781 texturedata.hasdata = YES;
782
783 return 0;
784}}
785
786static int
787METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
788 const SDL_Rect * rect, void **pixels, int *pitch)
789{ @autoreleasepool {
790 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
791 METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
792 int buffersize = 0;
793
794 if (rect->w <= 0 || rect->h <= 0) {
795 return SDL_SetError("Invalid rectangle dimensions for LockTexture.");
796 }
797
798 *pitch = SDL_BYTESPERPIXEL(texture->format) * rect->w;
799
800 if (texturedata.yuv || texturedata.nv12) {
801 buffersize = ((*pitch) * rect->h) + (2 * (*pitch + 1) / 2) * ((rect->h + 1) / 2);
802 } else {
803 buffersize = (*pitch) * rect->h;
804 }
805
806 texturedata.lockedrect = *rect;
807 texturedata.lockedbuffer = [data.mtldevice newBufferWithLength:buffersize options:MTLResourceStorageModeShared];
808 if (texturedata.lockedbuffer == nil) {
809 return SDL_OutOfMemory();
810 }
811
812 *pixels = [texturedata.lockedbuffer contents];
813
814 return 0;
815}}
816
817static void
818METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture)
819{ @autoreleasepool {
820 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
821 METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
822 SDL_Rect rect = texturedata.lockedrect;
823 int pitch = SDL_BYTESPERPIXEL(texture->format) * rect.w;
824 SDL_Rect UVrect = {rect.x / 2, rect.y / 2, (rect.w + 1) / 2, (rect.h + 1) / 2};
825
826 if (texturedata.lockedbuffer == nil) {
827 return;
828 }
829
830 if (data.mtlcmdencoder != nil) {
831 [data.mtlcmdencoder endEncoding];
832 data.mtlcmdencoder = nil;
833 }
834
835 if (data.mtlcmdbuffer == nil) {
836 data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
837 }
838
839 id<MTLBlitCommandEncoder> blitcmd = [data.mtlcmdbuffer blitCommandEncoder];
840
841 [blitcmd copyFromBuffer:texturedata.lockedbuffer
842 sourceOffset:0
843 sourceBytesPerRow:pitch
844 sourceBytesPerImage:0
845 sourceSize:MTLSizeMake(rect.w, rect.h, 1)
846 toTexture:texturedata.mtltexture
847 destinationSlice:0
848 destinationLevel:0
849 destinationOrigin:MTLOriginMake(rect.x, rect.y, 0)];
850
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
856 [blitcmd copyFromBuffer:texturedata.lockedbuffer
857 sourceOffset:rect.h * pitch
858 sourceBytesPerRow:UVpitch
859 sourceBytesPerImage:UVpitch * UVrect.h
860 sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1)
861 toTexture:texturedata.mtltexture_uv
862 destinationSlice:Uslice
863 destinationLevel:0
864 destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)];
865
866 [blitcmd copyFromBuffer:texturedata.lockedbuffer
867 sourceOffset:(rect.h * pitch) + UVrect.h * UVpitch
868 sourceBytesPerRow:UVpitch
869 sourceBytesPerImage:UVpitch * UVrect.h
870 sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1)
871 toTexture:texturedata.mtltexture_uv
872 destinationSlice:Vslice
873 destinationLevel:0
874 destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)];
875 }
876
877 if (texturedata.nv12) {
878 int UVpitch = 2 * ((pitch + 1) / 2);
879
880 [blitcmd copyFromBuffer:texturedata.lockedbuffer
881 sourceOffset:rect.h * pitch
882 sourceBytesPerRow:UVpitch
883 sourceBytesPerImage:0
884 sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1)
885 toTexture:texturedata.mtltexture_uv
886 destinationSlice:0
887 destinationLevel:0
888 destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)];
889 }
890
891 [blitcmd endEncoding];
892
893 [data.mtlcmdbuffer commit];
894 data.mtlcmdbuffer = nil;
895
896#if !__has_feature(objc_arc)
897 [texturedata.lockedbuffer release];
898#endif
899
900 texturedata.lockedbuffer = nil;
901 texturedata.hasdata = YES;
902}}
903
904static int
905METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
906{ @autoreleasepool {
907 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
908
909 if (data.mtlcmdencoder) {
910 /* End encoding for the previous render target so we can set up a new
911 * render pass for this one. */
912 [data.mtlcmdencoder endEncoding];
913 [data.mtlcmdbuffer commit];
914
915 data.mtlcmdencoder = nil;
916 data.mtlcmdbuffer = nil;
917 }
918
919 /* We don't begin a new render pass right away - we delay it until an actual
920 * draw or clear happens. That way we can use hardware clears when possible,
921 * which are only available when beginning a new render pass. */
922 return 0;
923}}
924
925
926// normalize a value from 0.0f to len into 0.0f to 1.0f.
927static inline float
928normtex(const float _val, const float len)
929{
930 return _val / len;
931}
932
933static int
934METAL_QueueSetViewport(SDL_Renderer * renderer, SDL_RenderCommand *cmd)
935{
936 float projection[4][4]; /* Prepare an orthographic projection */
937 const int w = cmd->data.viewport.rect.w;
938 const int h = cmd->data.viewport.rect.h;
939 const size_t matrixlen = sizeof (projection);
940 float *matrix = (float *) SDL_AllocateRenderVertices(renderer, matrixlen, CONSTANT_ALIGN, &cmd->data.viewport.first);
941 if (!matrix) {
942 return -1;
943 }
944
945 SDL_memset(projection, '\0', matrixlen);
946 if (w && h) {
947 projection[0][0] = 2.0f / w;
948 projection[1][1] = -2.0f / h;
949 projection[3][0] = -1.0f;
950 projection[3][1] = 1.0f;
951 projection[3][3] = 1.0f;
952 }
953 SDL_memcpy(matrix, projection, matrixlen);
954
955 return 0;
956}
957
958static int
959METAL_QueueSetDrawColor(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
960{
961 const size_t vertlen = sizeof (float) * 4;
962 float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, CONSTANT_ALIGN, &cmd->data.color.first);
963 if (!verts) {
964 return -1;
965 }
966 *(verts++) = ((float)cmd->data.color.r) / 255.0f;
967 *(verts++) = ((float)cmd->data.color.g) / 255.0f;
968 *(verts++) = ((float)cmd->data.color.b) / 255.0f;
969 *(verts++) = ((float)cmd->data.color.a) / 255.0f;
970 return 0;
971}
972
973static int
974METAL_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count)
975{
976 const size_t vertlen = (sizeof (float) * 2) * count;
977 float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
978 if (!verts) {
979 return -1;
980 }
981 cmd->data.draw.count = count;
982 SDL_memcpy(verts, points, vertlen);
983 return 0;
984}
985
986static int
987METAL_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects, int count)
988{
989 const size_t vertlen = (sizeof (float) * 8) * count;
990 float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
991 if (!verts) {
992 return -1;
993 }
994
995 cmd->data.draw.count = count;
996
997 /* Quads in the following vertex order (matches the quad index buffer):
998 * 1---3
999 * | \ |
1000 * 0---2
1001 */
1002 for (int i = 0; i < count; i++, rects++) {
1003 if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) {
1004 cmd->data.draw.count--;
1005 } else {
1006 *(verts++) = rects->x;
1007 *(verts++) = rects->y + rects->h;
1008 *(verts++) = rects->x;
1009 *(verts++) = rects->y;
1010 *(verts++) = rects->x + rects->w;
1011 *(verts++) = rects->y + rects->h;
1012 *(verts++) = rects->x + rects->w;
1013 *(verts++) = rects->y;
1014 }
1015 }
1016
1017 if (cmd->data.draw.count == 0) {
1018 cmd->command = SDL_RENDERCMD_NO_OP; // nothing to do, just skip this one later.
1019 }
1020
1021 return 0;
1022}
1023
1024static int
1025METAL_QueueCopy(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
1026 const SDL_Rect * srcrect, const SDL_FRect * dstrect)
1027{
1028 const float texw = (float) texture->w;
1029 const float texh = (float) texture->h;
1030 // !!! FIXME: use an index buffer
1031 const size_t vertlen = (sizeof (float) * 16);
1032 float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
1033 if (!verts) {
1034 return -1;
1035 }
1036
1037 cmd->data.draw.count = 1;
1038
1039 *(verts++) = dstrect->x;
1040 *(verts++) = dstrect->y + dstrect->h;
1041 *(verts++) = dstrect->x;
1042 *(verts++) = dstrect->y;
1043 *(verts++) = dstrect->x + dstrect->w;
1044 *(verts++) = dstrect->y + dstrect->h;
1045 *(verts++) = dstrect->x + dstrect->w;
1046 *(verts++) = dstrect->y;
1047
1048 *(verts++) = normtex(srcrect->x, texw);
1049 *(verts++) = normtex(srcrect->y + srcrect->h, texh);
1050 *(verts++) = normtex(srcrect->x, texw);
1051 *(verts++) = normtex(srcrect->y, texh);
1052 *(verts++) = normtex(srcrect->x + srcrect->w, texw);
1053 *(verts++) = normtex(srcrect->y + srcrect->h, texh);
1054 *(verts++) = normtex(srcrect->x + srcrect->w, texw);
1055 *(verts++) = normtex(srcrect->y, texh);
1056
1057 return 0;
1058}
1059
1060static int
1061METAL_QueueCopyEx(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
1062 const SDL_Rect * srcquad, const SDL_FRect * dstrect,
1063 const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
1064{
1065 const float texw = (float) texture->w;
1066 const float texh = (float) texture->h;
1067 const float rads = (float)(M_PI * (float) angle / 180.0f);
1068 const float c = cosf(rads), s = sinf(rads);
1069 float minu, maxu, minv, maxv;
1070 const size_t vertlen = (sizeof (float) * 32);
1071 float *verts;
1072
1073 // cheat and store this offset in (count) because it needs to be aligned in ways other fields don't and we aren't using count otherwise.
1074 verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, CONSTANT_ALIGN, &cmd->data.draw.count);
1075 if (!verts) {
1076 return -1;
1077 }
1078
1079 // transform matrix
1080 SDL_memset(verts, '\0', sizeof (*verts) * 16);
1081 verts[10] = verts[15] = 1.0f;
1082 // rotation
1083 verts[0] = c;
1084 verts[1] = s;
1085 verts[4] = -s;
1086 verts[5] = c;
1087
1088 // translation
1089 verts[12] = dstrect->x + center->x;
1090 verts[13] = dstrect->y + center->y;
1091
1092 // rest of the vertices don't need the aggressive alignment. Pack them in.
1093 verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
1094 if (!verts) {
1095 return -1;
1096 }
1097
1098 minu = normtex(srcquad->x, texw);
1099 maxu = normtex(srcquad->x + srcquad->w, texw);
1100 minv = normtex(srcquad->y, texh);
1101 maxv = normtex(srcquad->y + srcquad->h, texh);
1102
1103 if (flip & SDL_FLIP_HORIZONTAL) {
1104 float tmp = maxu;
1105 maxu = minu;
1106 minu = tmp;
1107 }
1108 if (flip & SDL_FLIP_VERTICAL) {
1109 float tmp = maxv;
1110 maxv = minv;
1111 minv = tmp;
1112 }
1113
1114 // vertices
1115 *(verts++) = -center->x;
1116 *(verts++) = dstrect->h - center->y;
1117 *(verts++) = -center->x;
1118 *(verts++) = -center->y;
1119 *(verts++) = dstrect->w - center->x;
1120 *(verts++) = dstrect->h - center->y;
1121 *(verts++) = dstrect->w - center->x;
1122 *(verts++) = -center->y;
1123
1124 // texcoords
1125 *(verts++) = minu;
1126 *(verts++) = maxv;
1127 *(verts++) = minu;
1128 *(verts++) = minv;
1129 *(verts++) = maxu;
1130 *(verts++) = maxv;
1131 *(verts++) = maxu;
1132 *(verts++) = minv;
1133
1134 return 0;
1135}
1136
1137
1138typedef struct
1139{
1140 #if __has_feature(objc_arc)
1141 __unsafe_unretained id<MTLRenderPipelineState> pipeline;
1142 #else
1144 #endif
1145 size_t constants_offset;
1147 SDL_bool cliprect_dirty;
1148 SDL_bool cliprect_enabled;
1149 SDL_Rect cliprect;
1150 SDL_bool viewport_dirty;
1152 size_t projection_offset;
1153 SDL_bool color_dirty;
1154 size_t color_offset;
1155} METAL_DrawStateCache;
1156
1157static void
1158SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const SDL_MetalFragmentFunction shader,
1159 const size_t constants_offset, id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache)
1160{
1161 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
1162 const SDL_BlendMode blend = cmd->data.draw.blend;
1163 size_t first = cmd->data.draw.first;
1164 id<MTLRenderPipelineState> newpipeline;
1165
1166 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
1167
1168 if (statecache->viewport_dirty) {
1169 MTLViewport viewport;
1170 viewport.originX = statecache->viewport.x;
1171 viewport.originY = statecache->viewport.y;
1172 viewport.width = statecache->viewport.w;
1173 viewport.height = statecache->viewport.h;
1174 viewport.znear = 0.0;
1175 viewport.zfar = 1.0;
1176 [data.mtlcmdencoder setViewport:viewport];
1177 [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:statecache->projection_offset atIndex:2]; // projection
1178 statecache->viewport_dirty = SDL_FALSE;
1179 }
1180
1181 if (statecache->cliprect_dirty) {
1182 MTLScissorRect mtlrect;
1183 if (statecache->cliprect_enabled) {
1184 const SDL_Rect *rect = &statecache->cliprect;
1185 mtlrect.x = statecache->viewport.x + rect->x;
1186 mtlrect.y = statecache->viewport.y + rect->y;
1187 mtlrect.width = rect->w;
1188 mtlrect.height = rect->h;
1189 } else {
1190 mtlrect.x = statecache->viewport.x;
1191 mtlrect.y = statecache->viewport.y;
1192 mtlrect.width = statecache->viewport.w;
1193 mtlrect.height = statecache->viewport.h;
1194 }
1195 if (mtlrect.width > 0 && mtlrect.height > 0) {
1196 [data.mtlcmdencoder setScissorRect:mtlrect];
1197 }
1198 statecache->cliprect_dirty = SDL_FALSE;
1199 }
1200
1201 if (statecache->color_dirty) {
1202 [data.mtlcmdencoder setFragmentBuffer:mtlbufvertex offset:statecache->color_offset atIndex:0];
1203 statecache->color_dirty = SDL_FALSE;
1204 }
1205
1206 newpipeline = ChoosePipelineState(data, data.activepipelines, shader, blend);
1207 if (newpipeline != statecache->pipeline) {
1208 [data.mtlcmdencoder setRenderPipelineState:newpipeline];
1209 statecache->pipeline = newpipeline;
1210 }
1211
1212 if (constants_offset != statecache->constants_offset) {
1213 if (constants_offset != CONSTANTS_OFFSET_INVALID) {
1214 [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:constants_offset atIndex:3];
1215 }
1216 statecache->constants_offset = constants_offset;
1217 }
1218
1219 [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:first atIndex:0]; // position
1220}
1221
1222static void
1223SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const size_t constants_offset,
1224 id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache)
1225{
1226 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
1228 METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
1229
1230 SetDrawState(renderer, cmd, texturedata.fragmentFunction, constants_offset, mtlbufvertex, statecache);
1231
1232 [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:cmd->data.draw.first+(8*sizeof (float)) atIndex:1]; // texcoords
1233
1234 if (texture != statecache->texture) {
1235 METAL_TextureData *oldtexturedata = NULL;
1236 if (statecache->texture) {
1237 oldtexturedata = (__bridge METAL_TextureData *) statecache->texture->driverdata;
1238 }
1239 if (!oldtexturedata || (texturedata.mtlsampler != oldtexturedata.mtlsampler)) {
1240 [data.mtlcmdencoder setFragmentSamplerState:texturedata.mtlsampler atIndex:0];
1241 }
1242
1243 [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0];
1244 if (texturedata.yuv || texturedata.nv12) {
1245 [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture_uv atIndex:1];
1246 [data.mtlcmdencoder setFragmentBuffer:data.mtlbufconstants offset:texturedata.conversionBufferOffset atIndex:1];
1247 }
1248 statecache->texture = texture;
1249 }
1250}
1251
1252static int
1253METAL_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize)
1254{ @autoreleasepool {
1255 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
1256 METAL_DrawStateCache statecache;
1257 id<MTLBuffer> mtlbufvertex = nil;
1258
1259 statecache.pipeline = nil;
1260 statecache.constants_offset = CONSTANTS_OFFSET_INVALID;
1261 statecache.texture = NULL;
1262 statecache.color_dirty = SDL_TRUE;
1263 statecache.cliprect_dirty = SDL_TRUE;
1264 statecache.viewport_dirty = SDL_TRUE;
1265 statecache.projection_offset = 0;
1266 statecache.color_offset = 0;
1267
1268 // !!! FIXME: have a ring of pre-made MTLBuffers we cycle through? How expensive is creation?
1269 if (vertsize > 0) {
1270 /* We can memcpy to a shared buffer from the CPU and read it from the GPU
1271 * without any extra copying. It's a bit slower on macOS to read shared
1272 * data from the GPU than to read managed/private data, but we avoid the
1273 * cost of copying the data and the code's simpler. Apple's best
1274 * practices guide recommends this approach for streamed vertex data.
1275 * TODO: this buffer is also used for constants. Is performance still
1276 * good for those, or should we have a managed buffer for them? */
1277 mtlbufvertex = [data.mtldevice newBufferWithLength:vertsize options:MTLResourceStorageModeShared];
1278 #if !__has_feature(objc_arc)
1279 [mtlbufvertex autorelease];
1280 #endif
1281 mtlbufvertex.label = @"SDL vertex data";
1282 SDL_memcpy([mtlbufvertex contents], vertices, vertsize);
1283 }
1284
1285 // If there's a command buffer here unexpectedly (app requested one?). Commit it so we can start fresh.
1286 [data.mtlcmdencoder endEncoding];
1287 [data.mtlcmdbuffer commit];
1288 data.mtlcmdencoder = nil;
1289 data.mtlcmdbuffer = nil;
1290
1291 while (cmd) {
1292 switch (cmd->command) {
1294 SDL_memcpy(&statecache.viewport, &cmd->data.viewport.rect, sizeof (statecache.viewport));
1295 statecache.projection_offset = cmd->data.viewport.first;
1296 statecache.viewport_dirty = SDL_TRUE;
1297 break;
1298 }
1299
1301 SDL_memcpy(&statecache.cliprect, &cmd->data.cliprect.rect, sizeof (statecache.cliprect));
1302 statecache.cliprect_enabled = cmd->data.cliprect.enabled;
1303 statecache.cliprect_dirty = SDL_TRUE;
1304 break;
1305 }
1306
1308 statecache.color_offset = cmd->data.color.first;
1309 statecache.color_dirty = SDL_TRUE;
1310 break;
1311 }
1312
1313 case SDL_RENDERCMD_CLEAR: {
1314 /* If we're already encoding a command buffer, dump it without committing it. We'd just
1315 clear all its work anyhow, and starting a new encoder will let us use a hardware clear
1316 operation via MTLLoadActionClear. */
1317 if (data.mtlcmdencoder != nil) {
1318 [data.mtlcmdencoder endEncoding];
1319
1320 // !!! FIXME: have to commit, or an uncommitted but enqueued buffer will prevent the frame from finishing.
1321 [data.mtlcmdbuffer commit];
1322 data.mtlcmdencoder = nil;
1323 data.mtlcmdbuffer = nil;
1324 }
1325
1326 // force all this state to be reconfigured on next command buffer.
1327 statecache.pipeline = nil;
1328 statecache.constants_offset = CONSTANTS_OFFSET_INVALID;
1329 statecache.texture = NULL;
1330 statecache.color_dirty = SDL_TRUE;
1331 statecache.cliprect_dirty = SDL_TRUE;
1332 statecache.viewport_dirty = SDL_TRUE;
1333
1334 const Uint8 r = cmd->data.color.r;
1335 const Uint8 g = cmd->data.color.g;
1336 const Uint8 b = cmd->data.color.b;
1337 const Uint8 a = cmd->data.color.a;
1338 MTLClearColor color = MTLClearColorMake(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
1339
1340 // get new command encoder, set up with an initial clear operation.
1341 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear, &color);
1342 break;
1343 }
1344
1347 const size_t count = cmd->data.draw.count;
1348 const MTLPrimitiveType primtype = (cmd->command == SDL_RENDERCMD_DRAW_POINTS) ? MTLPrimitiveTypePoint : MTLPrimitiveTypeLineStrip;
1349 SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache);
1350 [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
1351 break;
1352 }
1353
1355 const size_t count = cmd->data.draw.count;
1356 const size_t maxcount = UINT16_MAX / 4;
1357 SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache);
1358 /* Our index buffer has 16 bit indices, so we can only draw 65k
1359 * vertices (16k rects) at a time. */
1360 for (size_t i = 0; i < count; i += maxcount) {
1361 /* Set the vertex buffer offset for our current positions.
1362 * The vertex buffer itself was bound in SetDrawState. */
1363 [data.mtlcmdencoder setVertexBufferOffset:cmd->data.draw.first + i*sizeof(float)*8 atIndex:0];
1364 [data.mtlcmdencoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
1365 indexCount:SDL_min(maxcount, count - i) * 6
1366 indexType:MTLIndexTypeUInt16
1367 indexBuffer:data.mtlbufquadindices
1368 indexBufferOffset:0];
1369 }
1370 break;
1371 }
1372
1373 case SDL_RENDERCMD_COPY: {
1374 SetCopyState(renderer, cmd, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache);
1375 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
1376 break;
1377 }
1378
1379 case SDL_RENDERCMD_COPY_EX: {
1380 SetCopyState(renderer, cmd, CONSTANTS_OFFSET_INVALID, mtlbufvertex, &statecache);
1381 [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:cmd->data.draw.count atIndex:3]; // transform
1382 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
1383 break;
1384 }
1385
1387 break;
1388 }
1389 cmd = cmd->next;
1390 }
1391
1392 return 0;
1393}}
1394
1395static int
1396METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
1397 Uint32 pixel_format, void * pixels, int pitch)
1398{ @autoreleasepool {
1399 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
1400 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
1401
1402 [data.mtlcmdencoder endEncoding];
1403 id<MTLTexture> mtltexture = data.mtlpassdesc.colorAttachments[0].texture;
1404
1405#ifdef __MACOSX__
1406 /* on macOS with managed-storage textures, we need to tell the driver to
1407 * update the CPU-side copy of the texture data.
1408 * NOTE: Currently all of our textures are managed on macOS. We'll need some
1409 * extra copying for any private textures. */
1410 if (METAL_GetStorageMode(mtltexture) == MTLStorageModeManaged) {
1411 id<MTLBlitCommandEncoder> blit = [data.mtlcmdbuffer blitCommandEncoder];
1412 [blit synchronizeResource:mtltexture];
1413 [blit endEncoding];
1414 }
1415#endif
1416
1417 /* Commit the current command buffer and wait until it's completed, to make
1418 * sure the GPU has finished rendering to it by the time we read it. */
1419 [data.mtlcmdbuffer commit];
1420 [data.mtlcmdbuffer waitUntilCompleted];
1421 data.mtlcmdencoder = nil;
1422 data.mtlcmdbuffer = nil;
1423
1424 MTLRegion mtlregion = MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h);
1425
1426 // we only do BGRA8 or RGBA8 at the moment, so 4 will do.
1427 const int temp_pitch = rect->w * 4;
1428 void *temp_pixels = SDL_malloc(temp_pitch * rect->h);
1429 if (!temp_pixels) {
1430 return SDL_OutOfMemory();
1431 }
1432
1433 [mtltexture getBytes:temp_pixels bytesPerRow:temp_pitch fromRegion:mtlregion mipmapLevel:0];
1434
1435 const Uint32 temp_format = (mtltexture.pixelFormat == MTLPixelFormatBGRA8Unorm) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_ABGR8888;
1436 const int status = SDL_ConvertPixels(rect->w, rect->h, temp_format, temp_pixels, temp_pitch, pixel_format, pixels, pitch);
1437 SDL_free(temp_pixels);
1438 return status;
1439}}
1440
1441static void
1442METAL_RenderPresent(SDL_Renderer * renderer)
1443{ @autoreleasepool {
1444 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
1445
1446 if (data.mtlcmdencoder != nil) {
1447 [data.mtlcmdencoder endEncoding];
1448 }
1449 if (data.mtlbackbuffer != nil) {
1450 [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
1451 }
1452 if (data.mtlcmdbuffer != nil) {
1453 [data.mtlcmdbuffer commit];
1454 }
1455 data.mtlcmdencoder = nil;
1456 data.mtlcmdbuffer = nil;
1457 data.mtlbackbuffer = nil;
1458}}
1459
1460static void
1461METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture)
1462{ @autoreleasepool {
1463 CFBridgingRelease(texture->driverdata);
1464 texture->driverdata = NULL;
1465}}
1466
1467static void
1468METAL_DestroyRenderer(SDL_Renderer * renderer)
1469{ @autoreleasepool {
1470 if (renderer->driverdata) {
1471 METAL_RenderData *data = CFBridgingRelease(renderer->driverdata);
1472
1473 if (data.mtlcmdencoder != nil) {
1474 [data.mtlcmdencoder endEncoding];
1475 }
1476
1477 DestroyAllPipelines(data.allpipelines, data.pipelinescount);
1478 }
1479
1481}}
1482
1483static void *
1484METAL_GetMetalLayer(SDL_Renderer * renderer)
1485{ @autoreleasepool {
1486 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
1487 return (__bridge void*)data.mtllayer;
1488}}
1489
1490static void *
1491METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
1492{ @autoreleasepool {
1493 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
1494 METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
1495 return (__bridge void*)data.mtlcmdencoder;
1496}}
1497
1498static SDL_Renderer *
1499METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
1500{ @autoreleasepool {
1502 METAL_RenderData *data = NULL;
1503 id<MTLDevice> mtldevice = nil;
1504 SDL_SysWMinfo syswm;
1505
1506 SDL_VERSION(&syswm.version);
1507 if (!SDL_GetWindowWMInfo(window, &syswm)) {
1508 return NULL;
1509 }
1510
1511 if (IsMetalAvailable(&syswm) == -1) {
1512 return NULL;
1513 }
1514
1515 renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer));
1516 if (!renderer) {
1518 return NULL;
1519 }
1520
1521 // !!! FIXME: MTLCopyAllDevices() can find other GPUs on macOS...
1522 mtldevice = MTLCreateSystemDefaultDevice();
1523
1524 if (mtldevice == nil) {
1526 SDL_SetError("Failed to obtain Metal device");
1527 return NULL;
1528 }
1529
1530 // !!! FIXME: error checking on all of this.
1531 data = [[METAL_RenderData alloc] init];
1532
1533 renderer->driverdata = (void*)CFBridgingRetain(data);
1535
1536#ifdef __MACOSX__
1537 NSView *view = Cocoa_Mtl_AddMetalView(window);
1538 CAMetalLayer *layer = (CAMetalLayer *)[view layer];
1539
1540 layer.device = mtldevice;
1541
1542 //layer.colorspace = nil;
1543
1544#else
1545 UIView *view = UIKit_Mtl_AddMetalView(window);
1546 CAMetalLayer *layer = (CAMetalLayer *)[view layer];
1547#endif
1548
1549 // Necessary for RenderReadPixels.
1550 layer.framebufferOnly = NO;
1551
1552 data.mtldevice = layer.device;
1553 data.mtllayer = layer;
1554 id<MTLCommandQueue> mtlcmdqueue = [data.mtldevice newCommandQueue];
1555 data.mtlcmdqueue = mtlcmdqueue;
1556 data.mtlcmdqueue.label = @"SDL Metal Renderer";
1557 data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
1558
1559 NSError *err = nil;
1560
1561 // The compiled .metallib is embedded in a static array in a header file
1562 // but the original shader source code is in SDL_shaders_metal.metal.
1563 dispatch_data_t mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{});
1564 id<MTLLibrary> mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
1565 data.mtllibrary = mtllibrary;
1566 SDL_assert(err == nil);
1567#if !__has_feature(objc_arc)
1568 dispatch_release(mtllibdata);
1569#endif
1570 data.mtllibrary.label = @"SDL Metal renderer shader library";
1571
1572 /* Do some shader pipeline state loading up-front rather than on demand. */
1573 data.pipelinescount = 0;
1574 data.allpipelines = NULL;
1575 ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm);
1576
1577 MTLSamplerDescriptor *samplerdesc = [[MTLSamplerDescriptor alloc] init];
1578
1579 samplerdesc.minFilter = MTLSamplerMinMagFilterNearest;
1580 samplerdesc.magFilter = MTLSamplerMinMagFilterNearest;
1581 id<MTLSamplerState> mtlsamplernearest = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
1582 data.mtlsamplernearest = mtlsamplernearest;
1583
1584 samplerdesc.minFilter = MTLSamplerMinMagFilterLinear;
1585 samplerdesc.magFilter = MTLSamplerMinMagFilterLinear;
1586 id<MTLSamplerState> mtlsamplerlinear = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
1587 data.mtlsamplerlinear = mtlsamplerlinear;
1588
1589 /* Note: matrices are column major. */
1590 float identitytransform[16] = {
1591 1.0f, 0.0f, 0.0f, 0.0f,
1592 0.0f, 1.0f, 0.0f, 0.0f,
1593 0.0f, 0.0f, 1.0f, 0.0f,
1594 0.0f, 0.0f, 0.0f, 1.0f,
1595 };
1596
1597 float halfpixeltransform[16] = {
1598 1.0f, 0.0f, 0.0f, 0.0f,
1599 0.0f, 1.0f, 0.0f, 0.0f,
1600 0.0f, 0.0f, 1.0f, 0.0f,
1601 0.5f, 0.5f, 0.0f, 1.0f,
1602 };
1603
1604 /* Metal pads float3s to 16 bytes. */
1605 float decodetransformJPEG[4*4] = {
1606 0.0, -0.501960814, -0.501960814, 0.0, /* offset */
1607 1.0000, 0.0000, 1.4020, 0.0, /* Rcoeff */
1608 1.0000, -0.3441, -0.7141, 0.0, /* Gcoeff */
1609 1.0000, 1.7720, 0.0000, 0.0, /* Bcoeff */
1610 };
1611
1612 float decodetransformBT601[4*4] = {
1613 -0.0627451017, -0.501960814, -0.501960814, 0.0, /* offset */
1614 1.1644, 0.0000, 1.5960, 0.0, /* Rcoeff */
1615 1.1644, -0.3918, -0.8130, 0.0, /* Gcoeff */
1616 1.1644, 2.0172, 0.0000, 0.0, /* Bcoeff */
1617 };
1618
1619 float decodetransformBT709[4*4] = {
1620 0.0, -0.501960814, -0.501960814, 0.0, /* offset */
1621 1.0000, 0.0000, 1.4020, 0.0, /* Rcoeff */
1622 1.0000, -0.3441, -0.7141, 0.0, /* Gcoeff */
1623 1.0000, 1.7720, 0.0000, 0.0, /* Bcoeff */
1624 };
1625
1626 id<MTLBuffer> mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared];
1627 #if !__has_feature(objc_arc)
1628 [mtlbufconstantstaging autorelease];
1629 #endif
1630
1631 char *constantdata = [mtlbufconstantstaging contents];
1632 SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform));
1633 SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform));
1634 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_JPEG, decodetransformJPEG, sizeof(decodetransformJPEG));
1635 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601, decodetransformBT601, sizeof(decodetransformBT601));
1636 SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709, decodetransformBT709, sizeof(decodetransformBT709));
1637
1638 int quadcount = UINT16_MAX / 4;
1639 size_t indicessize = sizeof(UInt16) * quadcount * 6;
1640 id<MTLBuffer> mtlbufquadindicesstaging = [data.mtldevice newBufferWithLength:indicessize options:MTLResourceStorageModeShared];
1641#if !__has_feature(objc_arc)
1642 [mtlbufquadindicesstaging autorelease];
1643#endif
1644
1645 /* Quads in the following vertex order (matches the FillRects vertices):
1646 * 1---3
1647 * | \ |
1648 * 0---2
1649 */
1650 UInt16 *indexdata = [mtlbufquadindicesstaging contents];
1651 for (int i = 0; i < quadcount; i++) {
1652 indexdata[i * 6 + 0] = i * 4 + 0;
1653 indexdata[i * 6 + 1] = i * 4 + 1;
1654 indexdata[i * 6 + 2] = i * 4 + 2;
1655
1656 indexdata[i * 6 + 3] = i * 4 + 2;
1657 indexdata[i * 6 + 4] = i * 4 + 1;
1658 indexdata[i * 6 + 5] = i * 4 + 3;
1659 }
1660
1661 id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate];
1662 data.mtlbufconstants = mtlbufconstants;
1663 data.mtlbufconstants.label = @"SDL constant data";
1664
1665 id<MTLBuffer> mtlbufquadindices = [data.mtldevice newBufferWithLength:indicessize options:MTLResourceStorageModePrivate];
1666 data.mtlbufquadindices = mtlbufquadindices;
1667 data.mtlbufquadindices.label = @"SDL quad index buffer";
1668
1669 id<MTLCommandBuffer> cmdbuffer = [data.mtlcmdqueue commandBuffer];
1670 id<MTLBlitCommandEncoder> blitcmd = [cmdbuffer blitCommandEncoder];
1671
1672 [blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH];
1673 [blitcmd copyFromBuffer:mtlbufquadindicesstaging sourceOffset:0 toBuffer:mtlbufquadindices destinationOffset:0 size:indicessize];
1674
1675 [blitcmd endEncoding];
1676 [cmdbuffer commit];
1677
1678 // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
1679
1680 renderer->WindowEvent = METAL_WindowEvent;
1681 renderer->GetOutputSize = METAL_GetOutputSize;
1682 renderer->SupportsBlendMode = METAL_SupportsBlendMode;
1683 renderer->CreateTexture = METAL_CreateTexture;
1684 renderer->UpdateTexture = METAL_UpdateTexture;
1685 renderer->UpdateTextureYUV = METAL_UpdateTextureYUV;
1686 renderer->LockTexture = METAL_LockTexture;
1687 renderer->UnlockTexture = METAL_UnlockTexture;
1688 renderer->SetRenderTarget = METAL_SetRenderTarget;
1689 renderer->QueueSetViewport = METAL_QueueSetViewport;
1690 renderer->QueueSetDrawColor = METAL_QueueSetDrawColor;
1691 renderer->QueueDrawPoints = METAL_QueueDrawPoints;
1692 renderer->QueueDrawLines = METAL_QueueDrawPoints; // lines and points queue the same way.
1693 renderer->QueueFillRects = METAL_QueueFillRects;
1694 renderer->QueueCopy = METAL_QueueCopy;
1695 renderer->QueueCopyEx = METAL_QueueCopyEx;
1696 renderer->RunCommandQueue = METAL_RunCommandQueue;
1697 renderer->RenderReadPixels = METAL_RenderReadPixels;
1698 renderer->RenderPresent = METAL_RenderPresent;
1699 renderer->DestroyTexture = METAL_DestroyTexture;
1700 renderer->DestroyRenderer = METAL_DestroyRenderer;
1701 renderer->GetMetalLayer = METAL_GetMetalLayer;
1702 renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
1703
1706
1708
1709#if defined(__MACOSX__) && defined(MAC_OS_X_VERSION_10_13)
1710 if (@available(macOS 10.13, *)) {
1711 data.mtllayer.displaySyncEnabled = (flags & SDL_RENDERER_PRESENTVSYNC) != 0;
1712 if (data.mtllayer.displaySyncEnabled) {
1714 }
1715 } else
1716#endif
1717 {
1719 }
1720
1721 /* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
1722 int maxtexsize = 4096;
1723#if defined(__MACOSX__)
1724 maxtexsize = 16384;
1725#elif defined(__TVOS__)
1726 maxtexsize = 8192;
1727#ifdef __TVOS_11_0
1728 if (@available(tvOS 11.0, *)) {
1729 if ([mtldevice supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) {
1730 maxtexsize = 16384;
1731 }
1732 }
1733#endif
1734#else
1735#ifdef __IPHONE_11_0
1736#pragma clang diagnostic push
1737#pragma clang diagnostic ignored "-Wunguarded-availability-new"
1738 if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) {
1739 maxtexsize = 16384;
1740 } else
1741#pragma clang diagnostic pop
1742#endif
1743#ifdef __IPHONE_10_0
1744 if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) {
1745 maxtexsize = 16384;
1746 } else
1747#endif
1748 if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2] || [mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) {
1749 maxtexsize = 8192;
1750 } else {
1751 maxtexsize = 4096;
1752 }
1753#endif
1754
1755 renderer->info.max_texture_width = maxtexsize;
1756 renderer->info.max_texture_height = maxtexsize;
1757
1758#if !__has_feature(objc_arc)
1759 [mtlcmdqueue release];
1760 [mtllibrary release];
1761 [samplerdesc release];
1762 [mtlsamplernearest release];
1763 [mtlsamplerlinear release];
1764 [mtlbufconstants release];
1765 [mtlbufquadindices release];
1766 [view release];
1767 [data release];
1768 [mtldevice release];
1769#endif
1770
1771 return renderer;
1772}}
1773
1775 METAL_CreateRenderer,
1776 {
1777 "metal",
1779 6,
1780 {
1787 },
1788 0, 0,
1789 }
1790};
1791
1792#endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */
1793
1794/* vi: set ts=4 sw=4 expandtab: */
#define SDL_assert(condition)
Definition: SDL_assert.h:169
SDL_BlendOperation
The blend operation used when combining source and destination pixel components.
Definition: SDL_blendmode.h:63
@ SDL_BLENDOPERATION_MAXIMUM
Definition: SDL_blendmode.h:68
@ SDL_BLENDOPERATION_MINIMUM
Definition: SDL_blendmode.h:67
@ SDL_BLENDOPERATION_REV_SUBTRACT
Definition: SDL_blendmode.h:66
@ SDL_BLENDOPERATION_ADD
Definition: SDL_blendmode.h:64
@ SDL_BLENDOPERATION_SUBTRACT
Definition: SDL_blendmode.h:65
SDL_BlendFactor
The normalized factor used to multiply pixel components.
Definition: SDL_blendmode.h:76
@ SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR
Definition: SDL_blendmode.h:80
@ SDL_BLENDFACTOR_ZERO
Definition: SDL_blendmode.h:77
@ SDL_BLENDFACTOR_SRC_COLOR
Definition: SDL_blendmode.h:79
@ SDL_BLENDFACTOR_SRC_ALPHA
Definition: SDL_blendmode.h:81
@ SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR
Definition: SDL_blendmode.h:84
@ SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA
Definition: SDL_blendmode.h:86
@ SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA
Definition: SDL_blendmode.h:82
@ SDL_BLENDFACTOR_DST_ALPHA
Definition: SDL_blendmode.h:85
@ SDL_BLENDFACTOR_DST_COLOR
Definition: SDL_blendmode.h:83
@ SDL_BLENDFACTOR_ONE
Definition: SDL_blendmode.h:78
SDL_BlendMode
The blend mode used in SDL_RenderCopy() and drawing operations.
Definition: SDL_blendmode.h:41
@ SDL_BLENDMODE_NONE
Definition: SDL_blendmode.h:42
@ SDL_BLENDMODE_ADD
Definition: SDL_blendmode.h:47
@ SDL_BLENDMODE_BLEND
Definition: SDL_blendmode.h:44
@ SDL_BLENDMODE_MOD
Definition: SDL_blendmode.h:50
#define SDL_SetError
#define SDL_memset
#define SDL_GetYUVConversionModeForResolution
#define SDL_GetPixelFormatName
#define SDL_malloc
#define SDL_realloc
#define SDL_free
#define SDL_memcpy
#define SDL_calloc
#define SDL_ConvertPixels
#define SDL_GetWindowWMInfo
SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char const char SDL_SCANF_FORMAT_STRING const char return SDL_ThreadFunction const char void return Uint32 return Uint32 void
#define SDL_OutOfMemory()
Definition: SDL_error.h:52
GLint GLint GLint GLint GLint GLint y
Definition: SDL_opengl.h:1574
GLuint GLuint GLsizei count
Definition: SDL_opengl.h:1571
GLint GLint GLsizei GLsizei GLsizei GLint GLenum GLenum const GLvoid * pixels
Definition: SDL_opengl.h:1572
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
GLdouble GLdouble GLdouble r
Definition: SDL_opengl.h:2079
GLint GLint GLint GLint GLint x
Definition: SDL_opengl.h:1574
GLdouble s
Definition: SDL_opengl.h:2063
GLboolean GLboolean GLboolean b
struct _cl_event * event
GLfixed GLfixed GLint GLint GLfixed points
GLfloat f
GLenum mode
const GLuint * pipelines
GLuint color
GLintptr offset
GLuint shader
GLuint id
GLenum GLsizei len
GLboolean GLboolean GLboolean GLboolean a
const GLubyte * c
GLuint GLenum matrix
GLuint GLsizei const GLchar * label
GLboolean GLboolean g
GLfloat angle
GLbitfield flags
GLenum GLenum GLuint texture
GLenum GLuint GLint GLint layer
const GLint * first
GLfloat GLfloat GLfloat GLfloat h
GLubyte GLubyte GLubyte GLubyte w
GLsizeiptr const void GLenum usage
#define SDL_BYTESPERPIXEL(X)
Definition: SDL_pixels.h:128
@ SDL_PIXELFORMAT_YV12
Definition: SDL_pixels.h:277
@ SDL_PIXELFORMAT_ABGR8888
Definition: SDL_pixels.h:254
@ SDL_PIXELFORMAT_NV12
Definition: SDL_pixels.h:287
@ SDL_PIXELFORMAT_NV21
Definition: SDL_pixels.h:289
@ SDL_PIXELFORMAT_IYUV
Definition: SDL_pixels.h:279
@ SDL_PIXELFORMAT_ARGB8888
Definition: SDL_pixels.h:248
SDL_BlendFactor SDL_GetBlendModeDstColorFactor(SDL_BlendMode blendMode)
Definition: SDL_render.c:3349
SDL_BlendFactor SDL_GetBlendModeSrcColorFactor(SDL_BlendMode blendMode)
Definition: SDL_render.c:3342
SDL_BlendFactor SDL_GetBlendModeDstAlphaFactor(SDL_BlendMode blendMode)
Definition: SDL_render.c:3370
SDL_BlendFactor SDL_GetBlendModeSrcAlphaFactor(SDL_BlendMode blendMode)
Definition: SDL_render.c:3363
SDL_BlendOperation SDL_GetBlendModeAlphaOperation(SDL_BlendMode blendMode)
Definition: SDL_render.c:3377
SDL_BlendOperation SDL_GetBlendModeColorOperation(SDL_BlendMode blendMode)
Definition: SDL_render.c:3356
void * SDL_AllocateRenderVertices(SDL_Renderer *renderer, const size_t numbytes, const size_t alignment, size_t *offset)
Definition: SDL_render.c:284
@ SDL_RENDERER_ACCELERATED
Definition: SDL_render.h:67
@ SDL_RENDERER_PRESENTVSYNC
Definition: SDL_render.h:69
@ SDL_RENDERER_TARGETTEXTURE
Definition: SDL_render.h:71
SDL_RendererFlip
Flip constants for SDL_RenderCopyEx.
Definition: SDL_render.h:112
@ SDL_FLIP_VERTICAL
Definition: SDL_render.h:115
@ SDL_FLIP_HORIZONTAL
Definition: SDL_render.h:114
@ SDL_TEXTUREACCESS_TARGET
Definition: SDL_render.h:95
static void SetDrawState(SDL_Surface *surface, SW_DrawStateCache *drawstate)
const unsigned int sdl_metallib_len
const unsigned char sdl_metallib[]
SDL_bool
Definition: SDL_stdinc.h:162
@ SDL_TRUE
Definition: SDL_stdinc.h:164
@ SDL_FALSE
Definition: SDL_stdinc.h:163
uint32_t Uint32
Definition: SDL_stdinc.h:203
#define SDL_zerop(x)
Definition: SDL_stdinc.h:417
uint8_t Uint8
Definition: SDL_stdinc.h:179
SDL_YUV_CONVERSION_MODE
The formula used for converting between YUV and RGB.
Definition: SDL_surface.h:105
@ SDL_YUV_CONVERSION_BT601
Definition: SDL_surface.h:107
@ SDL_YUV_CONVERSION_JPEG
Definition: SDL_surface.h:106
@ SDL_YUV_CONVERSION_BT709
Definition: SDL_surface.h:108
SDL_RenderDriver METAL_RenderDriver
@ SDL_ScaleModeNearest
Definition: SDL_sysrender.h:37
@ SDL_RENDERCMD_SETCLIPRECT
Definition: SDL_sysrender.h:76
@ SDL_RENDERCMD_DRAW_LINES
Definition: SDL_sysrender.h:80
@ SDL_RENDERCMD_SETVIEWPORT
Definition: SDL_sysrender.h:75
@ SDL_RENDERCMD_DRAW_POINTS
Definition: SDL_sysrender.h:79
@ SDL_RENDERCMD_NO_OP
Definition: SDL_sysrender.h:74
@ SDL_RENDERCMD_FILL_RECTS
Definition: SDL_sysrender.h:81
@ SDL_RENDERCMD_COPY
Definition: SDL_sysrender.h:82
@ SDL_RENDERCMD_CLEAR
Definition: SDL_sysrender.h:78
@ SDL_RENDERCMD_SETDRAWCOLOR
Definition: SDL_sysrender.h:77
@ SDL_RENDERCMD_COPY_EX
Definition: SDL_sysrender.h:83
@ SDL_SYSWM_UIKIT
Definition: SDL_syswm.h:126
@ SDL_SYSWM_COCOA
Definition: SDL_syswm.h:125
#define SDL_VERSION(x)
Macro to determine SDL version program was compiled against.
Definition: SDL_version.h:79
@ SDL_WINDOWEVENT_HIDDEN
Definition: SDL_video.h:150
@ SDL_WINDOWEVENT_SHOWN
Definition: SDL_video.h:149
struct xkb_state * state
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
Definition: SDL_x11sym.h:50
#define NULL
Definition: begin_code.h:167
EGLSurface EGLNativeWindowType * window
Definition: eglext.h:1025
EGLSurface EGLint * rects
Definition: eglext.h:282
The structure that defines a point (floating point)
Definition: SDL_rect.h:61
A rectangle, with the origin at the upper left (floating point).
Definition: SDL_rect.h:88
float x
Definition: SDL_rect.h:89
float y
Definition: SDL_rect.h:90
A rectangle, with the origin at the upper left (integer).
Definition: SDL_rect.h:78
int h
Definition: SDL_rect.h:80
int w
Definition: SDL_rect.h:80
int y
Definition: SDL_rect.h:79
int x
Definition: SDL_rect.h:79
union SDL_RenderCommand::@30 data
struct SDL_RenderCommand * next
struct SDL_RenderCommand::@30::@33 draw
struct SDL_RenderCommand::@30::@31 viewport
SDL_BlendMode blend
SDL_RenderCommandType command
Definition: SDL_sysrender.h:88
struct SDL_RenderCommand::@30::@32 cliprect
SDL_Texture * texture
struct SDL_RenderCommand::@30::@34 color
SDL_RendererInfo info
int(* QueueCopy)(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, const SDL_Rect *srcrect, const SDL_FRect *dstrect)
int(* LockTexture)(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, void **pixels, int *pitch)
SDL_Window * window
int(* UpdateTextureYUV)(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, const Uint8 *Yplane, int Ypitch, const Uint8 *Uplane, int Upitch, const Uint8 *Vplane, int Vpitch)
int(* QueueDrawLines)(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count)
void(* UnlockTexture)(SDL_Renderer *renderer, SDL_Texture *texture)
SDL_bool always_batch
int(* UpdateTexture)(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rect *rect, const void *pixels, int pitch)
int(* SetRenderTarget)(SDL_Renderer *renderer, SDL_Texture *texture)
void(* DestroyRenderer)(SDL_Renderer *renderer)
void(* DestroyTexture)(SDL_Renderer *renderer, SDL_Texture *texture)
int(* GetOutputSize)(SDL_Renderer *renderer, int *w, int *h)
SDL_bool(* SupportsBlendMode)(SDL_Renderer *renderer, SDL_BlendMode blendMode)
void *(* GetMetalLayer)(SDL_Renderer *renderer)
void *(* GetMetalCommandEncoder)(SDL_Renderer *renderer)
void(* RenderPresent)(SDL_Renderer *renderer)
int(* RenderReadPixels)(SDL_Renderer *renderer, const SDL_Rect *rect, Uint32 format, void *pixels, int pitch)
int(* QueueDrawPoints)(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FPoint *points, int count)
int(* QueueFillRects)(SDL_Renderer *renderer, SDL_RenderCommand *cmd, const SDL_FRect *rects, int count)
void(* WindowEvent)(SDL_Renderer *renderer, const SDL_WindowEvent *event)
int(* RunCommandQueue)(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize)
int(* QueueCopyEx)(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture, const SDL_Rect *srcquad, const SDL_FRect *dstrect, const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
int(* CreateTexture)(SDL_Renderer *renderer, SDL_Texture *texture)
int(* QueueSetViewport)(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
SDL_RendererInfo info
void * driverdata
SDL_Texture * target
int(* QueueSetDrawColor)(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
int max_texture_height
Definition: SDL_render.h:85
SDL_SYSWM_TYPE subsystem
Definition: SDL_syswm.h:200
SDL_version version
Definition: SDL_syswm.h:199
void * driverdata
Definition: SDL_sysrender.h:66
Window state change event data (event.window.*)
Definition: SDL_events.h:196
The type used to identify a window.
Definition: SDL_sysvideo.h:74
static SDL_Renderer * renderer
static SDL_BlendMode blendMode
Definition: testdraw2.c:34
SDL_Rect rect
Definition: testrelative.c:27
SDL_Rect viewport
Definition: testviewport.c:28
static int available()
Definition: video.c:356