src/render/metal/SDL_render_metal.m
author Alex Szpakowski <slime73@gmail.com>
Thu, 01 Nov 2018 19:49:01 -0300
changeset 12384 b1f5162fd621
parent 12382 03d0bddca61b
child 12385 45038f8422c9
permissions -rw-r--r--
metal: remove an obsolete section of a constant buffer.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2018 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__
    42 #include "SDL_shaders_metal_osx.h"
    43 #else
    44 #include "SDL_shaders_metal_ios.h"
    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 
    58 static const size_t CONSTANTS_OFFSET_INVALID = 0xFFFFFFFF;
    59 static const size_t CONSTANTS_OFFSET_IDENTITY = 0;
    60 static const size_t CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM = ALIGN_CONSTANTS(CONSTANTS_OFFSET_IDENTITY + sizeof(float) * 16);
    61 static const size_t CONSTANTS_OFFSET_DECODE_JPEG = ALIGN_CONSTANTS(CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM + sizeof(float) * 16);
    62 static const size_t CONSTANTS_OFFSET_DECODE_BT601 = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_JPEG + sizeof(float) * 4 * 4);
    63 static const size_t CONSTANTS_OFFSET_DECODE_BT709 = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_BT601 + sizeof(float) * 4 * 4);
    64 static const size_t CONSTANTS_LENGTH = CONSTANTS_OFFSET_DECODE_BT709 + sizeof(float) * 6;
    65 
    66 typedef enum SDL_MetalVertexFunction
    67 {
    68     SDL_METAL_VERTEX_SOLID,
    69     SDL_METAL_VERTEX_COPY,
    70 } SDL_MetalVertexFunction;
    71 
    72 typedef 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 
    82 typedef struct METAL_PipelineState
    83 {
    84     SDL_BlendMode blendMode;
    85     void *pipe;
    86 } METAL_PipelineState;
    87 
    88 typedef 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. */
   104 typedef 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) CAMetalLayer *mtllayer;
   121     @property (nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
   122     @property (nonatomic, assign) METAL_ShaderPipelines *activepipelines;
   123     @property (nonatomic, assign) METAL_ShaderPipelines *allpipelines;
   124     @property (nonatomic, assign) int pipelinescount;
   125 @end
   126 
   127 @implementation METAL_RenderData
   128 #if !__has_feature(objc_arc)
   129 - (void)dealloc
   130 {
   131     [_mtldevice release];
   132     [_mtlcmdqueue release];
   133     [_mtlcmdbuffer release];
   134     [_mtlcmdencoder release];
   135     [_mtllibrary release];
   136     [_mtlbackbuffer release];
   137     [_mtlsamplernearest release];
   138     [_mtlsamplerlinear release];
   139     [_mtlbufconstants release];
   140     [_mtllayer release];
   141     [_mtlpassdesc release];
   142     [super dealloc];
   143 }
   144 #endif
   145 @end
   146 
   147 @interface METAL_TextureData : NSObject
   148     @property (nonatomic, retain) id<MTLTexture> mtltexture;
   149     @property (nonatomic, retain) id<MTLTexture> mtltexture_uv;
   150     @property (nonatomic, retain) id<MTLSamplerState> mtlsampler;
   151     @property (nonatomic, assign) SDL_MetalFragmentFunction fragmentFunction;
   152     @property (nonatomic, assign) BOOL yuv;
   153     @property (nonatomic, assign) BOOL nv12;
   154     @property (nonatomic, assign) size_t conversionBufferOffset;
   155 @end
   156 
   157 @implementation METAL_TextureData
   158 #if !__has_feature(objc_arc)
   159 - (void)dealloc
   160 {
   161     [_mtltexture release];
   162     [_mtltexture_uv release];
   163     [_mtlsampler release];
   164     [super dealloc];
   165 }
   166 #endif
   167 @end
   168 
   169 static int
   170 IsMetalAvailable(const SDL_SysWMinfo *syswm)
   171 {
   172     if (syswm->subsystem != SDL_SYSWM_COCOA && syswm->subsystem != SDL_SYSWM_UIKIT) {
   173         return SDL_SetError("Metal render target only supports Cocoa and UIKit video targets at the moment.");
   174     }
   175 
   176     // this checks a weak symbol.
   177 #if (defined(__MACOSX__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101100))
   178     if (MTLCreateSystemDefaultDevice == NULL) {  // probably on 10.10 or lower.
   179         return SDL_SetError("Metal framework not available on this system");
   180     }
   181 #endif
   182 
   183     return 0;
   184 }
   185 
   186 static const MTLBlendOperation invalidBlendOperation = (MTLBlendOperation)0xFFFFFFFF;
   187 static const MTLBlendFactor invalidBlendFactor = (MTLBlendFactor)0xFFFFFFFF;
   188 
   189 static MTLBlendOperation
   190 GetBlendOperation(SDL_BlendOperation operation)
   191 {
   192     switch (operation) {
   193         case SDL_BLENDOPERATION_ADD: return MTLBlendOperationAdd;
   194         case SDL_BLENDOPERATION_SUBTRACT: return MTLBlendOperationSubtract;
   195         case SDL_BLENDOPERATION_REV_SUBTRACT: return MTLBlendOperationReverseSubtract;
   196         case SDL_BLENDOPERATION_MINIMUM: return MTLBlendOperationMin;
   197         case SDL_BLENDOPERATION_MAXIMUM: return MTLBlendOperationMax;
   198         default: return invalidBlendOperation;
   199     }
   200 }
   201 
   202 static MTLBlendFactor
   203 GetBlendFactor(SDL_BlendFactor factor)
   204 {
   205     switch (factor) {
   206         case SDL_BLENDFACTOR_ZERO: return MTLBlendFactorZero;
   207         case SDL_BLENDFACTOR_ONE: return MTLBlendFactorOne;
   208         case SDL_BLENDFACTOR_SRC_COLOR: return MTLBlendFactorSourceColor;
   209         case SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return MTLBlendFactorOneMinusSourceColor;
   210         case SDL_BLENDFACTOR_SRC_ALPHA: return MTLBlendFactorSourceAlpha;
   211         case SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return MTLBlendFactorOneMinusSourceAlpha;
   212         case SDL_BLENDFACTOR_DST_COLOR: return MTLBlendFactorDestinationColor;
   213         case SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR: return MTLBlendFactorOneMinusDestinationColor;
   214         case SDL_BLENDFACTOR_DST_ALPHA: return MTLBlendFactorDestinationAlpha;
   215         case SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return MTLBlendFactorOneMinusDestinationAlpha;
   216         default: return invalidBlendFactor;
   217     }
   218 }
   219 
   220 static NSString *
   221 GetVertexFunctionName(SDL_MetalVertexFunction function)
   222 {
   223     switch (function) {
   224         case SDL_METAL_VERTEX_SOLID: return @"SDL_Solid_vertex";
   225         case SDL_METAL_VERTEX_COPY: return @"SDL_Copy_vertex";
   226         default: return nil;
   227     }
   228 }
   229 
   230 static NSString *
   231 GetFragmentFunctionName(SDL_MetalFragmentFunction function)
   232 {
   233     switch (function) {
   234         case SDL_METAL_FRAGMENT_SOLID: return @"SDL_Solid_fragment";
   235         case SDL_METAL_FRAGMENT_COPY: return @"SDL_Copy_fragment";
   236         case SDL_METAL_FRAGMENT_YUV: return @"SDL_YUV_fragment";
   237         case SDL_METAL_FRAGMENT_NV12: return @"SDL_NV12_fragment";
   238         case SDL_METAL_FRAGMENT_NV21: return @"SDL_NV21_fragment";
   239         default: return nil;
   240     }
   241 }
   242 
   243 static id<MTLRenderPipelineState>
   244 MakePipelineState(METAL_RenderData *data, METAL_PipelineCache *cache,
   245                   NSString *blendlabel, SDL_BlendMode blendmode)
   246 {
   247     id<MTLFunction> mtlvertfn = [data.mtllibrary newFunctionWithName:GetVertexFunctionName(cache->vertexFunction)];
   248     id<MTLFunction> mtlfragfn = [data.mtllibrary newFunctionWithName:GetFragmentFunctionName(cache->fragmentFunction)];
   249     SDL_assert(mtlvertfn != nil);
   250     SDL_assert(mtlfragfn != nil);
   251 
   252     MTLRenderPipelineDescriptor *mtlpipedesc = [[MTLRenderPipelineDescriptor alloc] init];
   253     mtlpipedesc.vertexFunction = mtlvertfn;
   254     mtlpipedesc.fragmentFunction = mtlfragfn;
   255 
   256     MTLRenderPipelineColorAttachmentDescriptor *rtdesc = mtlpipedesc.colorAttachments[0];
   257 
   258     rtdesc.pixelFormat = cache->renderTargetFormat;
   259 
   260     if (blendmode != SDL_BLENDMODE_NONE) {
   261         rtdesc.blendingEnabled = YES;
   262         rtdesc.sourceRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcColorFactor(blendmode));
   263         rtdesc.destinationRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeDstColorFactor(blendmode));
   264         rtdesc.rgbBlendOperation = GetBlendOperation(SDL_GetBlendModeColorOperation(blendmode));
   265         rtdesc.sourceAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcAlphaFactor(blendmode));
   266         rtdesc.destinationAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeDstAlphaFactor(blendmode));
   267         rtdesc.alphaBlendOperation = GetBlendOperation(SDL_GetBlendModeAlphaOperation(blendmode));
   268     } else {
   269         rtdesc.blendingEnabled = NO;
   270     }
   271 
   272     mtlpipedesc.label = [@(cache->label) stringByAppendingString:blendlabel];
   273 
   274     NSError *err = nil;
   275     id<MTLRenderPipelineState> state = [data.mtldevice newRenderPipelineStateWithDescriptor:mtlpipedesc error:&err];
   276     SDL_assert(err == nil);
   277 
   278     METAL_PipelineState pipeline;
   279     pipeline.blendMode = blendmode;
   280     pipeline.pipe = (void *)CFBridgingRetain(state);
   281 
   282     METAL_PipelineState *states = SDL_realloc(cache->states, (cache->count + 1) * sizeof(pipeline));
   283 
   284 #if !__has_feature(objc_arc)
   285     [mtlpipedesc release];  // !!! FIXME: can these be reused for each creation, or does the pipeline obtain it?
   286     [mtlvertfn release];
   287     [mtlfragfn release];
   288     [state release];
   289 #endif
   290 
   291     if (states) {
   292         states[cache->count++] = pipeline;
   293         cache->states = states;
   294         return (__bridge id<MTLRenderPipelineState>)pipeline.pipe;
   295     } else {
   296         CFBridgingRelease(pipeline.pipe);
   297         SDL_OutOfMemory();
   298         return NULL;
   299     }
   300 }
   301 
   302 static void
   303 MakePipelineCache(METAL_RenderData *data, METAL_PipelineCache *cache, const char *label,
   304                   MTLPixelFormat rtformat, SDL_MetalVertexFunction vertfn, SDL_MetalFragmentFunction fragfn)
   305 {
   306     SDL_zerop(cache);
   307 
   308     cache->vertexFunction = vertfn;
   309     cache->fragmentFunction = fragfn;
   310     cache->renderTargetFormat = rtformat;
   311     cache->label = label;
   312 
   313     /* Create pipeline states for the default blend modes. Custom blend modes
   314      * will be added to the cache on-demand. */
   315     MakePipelineState(data, cache, @" (blend=none)", SDL_BLENDMODE_NONE);
   316     MakePipelineState(data, cache, @" (blend=blend)", SDL_BLENDMODE_BLEND);
   317     MakePipelineState(data, cache, @" (blend=add)", SDL_BLENDMODE_ADD);
   318     MakePipelineState(data, cache, @" (blend=mod)", SDL_BLENDMODE_MOD);
   319 }
   320 
   321 static void
   322 DestroyPipelineCache(METAL_PipelineCache *cache)
   323 {
   324     if (cache != NULL) {
   325         for (int i = 0; i < cache->count; i++) {
   326             CFBridgingRelease(cache->states[i].pipe);
   327         }
   328 
   329         SDL_free(cache->states);
   330     }
   331 }
   332 
   333 void
   334 MakeShaderPipelines(METAL_RenderData *data, METAL_ShaderPipelines *pipelines, MTLPixelFormat rtformat)
   335 {
   336     SDL_zerop(pipelines);
   337 
   338     pipelines->renderTargetFormat = rtformat;
   339 
   340     MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_SOLID], "SDL primitives pipeline", rtformat, SDL_METAL_VERTEX_SOLID, SDL_METAL_FRAGMENT_SOLID);
   341     MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_COPY], "SDL copy pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_COPY);
   342     MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_YUV], "SDL YUV pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_YUV);
   343     MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV12], "SDL NV12 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV12);
   344     MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV21], "SDL NV21 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV21);
   345 }
   346 
   347 static METAL_ShaderPipelines *
   348 ChooseShaderPipelines(METAL_RenderData *data, MTLPixelFormat rtformat)
   349 {
   350     METAL_ShaderPipelines *allpipelines = data.allpipelines;
   351     int count = data.pipelinescount;
   352 
   353     for (int i = 0; i < count; i++) {
   354         if (allpipelines[i].renderTargetFormat == rtformat) {
   355             return &allpipelines[i];
   356         }
   357     }
   358 
   359     allpipelines = SDL_realloc(allpipelines, (count + 1) * sizeof(METAL_ShaderPipelines));
   360 
   361     if (allpipelines == NULL) {
   362         SDL_OutOfMemory();
   363         return NULL;
   364     }
   365 
   366     MakeShaderPipelines(data, &allpipelines[count], rtformat);
   367 
   368     data.allpipelines = allpipelines;
   369     data.pipelinescount = count + 1;
   370 
   371     return &data.allpipelines[count];
   372 }
   373 
   374 static void
   375 DestroyAllPipelines(METAL_ShaderPipelines *allpipelines, int count)
   376 {
   377     if (allpipelines != NULL) {
   378         for (int i = 0; i < count; i++) {
   379             for (int cache = 0; cache < SDL_METAL_FRAGMENT_COUNT; cache++) {
   380                 DestroyPipelineCache(&allpipelines[i].caches[cache]);
   381             }
   382         }
   383 
   384         SDL_free(allpipelines);
   385     }
   386 }
   387 
   388 static inline id<MTLRenderPipelineState>
   389 ChoosePipelineState(METAL_RenderData *data, METAL_ShaderPipelines *pipelines, SDL_MetalFragmentFunction fragfn, SDL_BlendMode blendmode)
   390 {
   391     METAL_PipelineCache *cache = &pipelines->caches[fragfn];
   392 
   393     for (int i = 0; i < cache->count; i++) {
   394         if (cache->states[i].blendMode == blendmode) {
   395             return (__bridge id<MTLRenderPipelineState>)cache->states[i].pipe;
   396         }
   397     }
   398 
   399     return MakePipelineState(data, cache, [NSString stringWithFormat:@" (blend=custom 0x%x)", blendmode], blendmode);
   400 }
   401 
   402 static void
   403 METAL_ActivateRenderCommandEncoder(SDL_Renderer * renderer, MTLLoadAction load, MTLClearColor *clear_color)
   404 {
   405     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   406 
   407     /* Our SetRenderTarget just signals that the next render operation should
   408      * set up a new render pass. This is where that work happens. */
   409     if (data.mtlcmdencoder == nil) {
   410         id<MTLTexture> mtltexture = nil;
   411 
   412         if (renderer->target != NULL) {
   413             METAL_TextureData *texdata = (__bridge METAL_TextureData *)renderer->target->driverdata;
   414             mtltexture = texdata.mtltexture;
   415         } else {
   416             if (data.mtlbackbuffer == nil) {
   417                 /* The backbuffer's contents aren't guaranteed to persist after
   418                  * presenting, so we can leave it undefined when loading it. */
   419                 data.mtlbackbuffer = [data.mtllayer nextDrawable];
   420                 if (load == MTLLoadActionLoad) {
   421                     load = MTLLoadActionDontCare;
   422                 }
   423             }
   424             mtltexture = data.mtlbackbuffer.texture;
   425         }
   426 
   427         SDL_assert(mtltexture);
   428 
   429         if (load == MTLLoadActionClear) {
   430             SDL_assert(clear_color != NULL);
   431             data.mtlpassdesc.colorAttachments[0].clearColor = *clear_color;
   432         }
   433 
   434         data.mtlpassdesc.colorAttachments[0].loadAction = load;
   435         data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
   436 
   437         data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
   438         data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
   439 
   440         if (data.mtlbackbuffer != nil && mtltexture == data.mtlbackbuffer.texture) {
   441             data.mtlcmdencoder.label = @"SDL metal renderer backbuffer";
   442         } else {
   443             data.mtlcmdencoder.label = @"SDL metal renderer render target";
   444         }
   445 
   446         data.activepipelines = ChooseShaderPipelines(data, mtltexture.pixelFormat);
   447 
   448         // make sure this has a definite place in the queue. This way it will
   449         //  execute reliably whether the app tries to make its own command buffers
   450         //  or whatever. This means we can _always_ batch rendering commands!
   451         [data.mtlcmdbuffer enqueue];
   452     }
   453 }
   454 
   455 static void
   456 METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
   457 {
   458     if (event->event == SDL_WINDOWEVENT_SHOWN ||
   459         event->event == SDL_WINDOWEVENT_HIDDEN) {
   460         // !!! FIXME: write me
   461     }
   462 }
   463 
   464 static int
   465 METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
   466 { @autoreleasepool {
   467     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   468     if (w) {
   469         *w = (int)data.mtllayer.drawableSize.width;
   470     }
   471     if (h) {
   472         *h = (int)data.mtllayer.drawableSize.height;
   473     }
   474     return 0;
   475 }}
   476 
   477 static SDL_bool
   478 METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode)
   479 {
   480     SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode);
   481     SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode);
   482     SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode);
   483     SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode);
   484     SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode);
   485     SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode);
   486 
   487     if (GetBlendFactor(srcColorFactor) == invalidBlendFactor ||
   488         GetBlendFactor(srcAlphaFactor) == invalidBlendFactor ||
   489         GetBlendOperation(colorOperation) == invalidBlendOperation ||
   490         GetBlendFactor(dstColorFactor) == invalidBlendFactor ||
   491         GetBlendFactor(dstAlphaFactor) == invalidBlendFactor ||
   492         GetBlendOperation(alphaOperation) == invalidBlendOperation) {
   493         return SDL_FALSE;
   494     }
   495     return SDL_TRUE;
   496 }
   497 
   498 static int
   499 METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
   500 { @autoreleasepool {
   501     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   502     MTLPixelFormat pixfmt;
   503 
   504     switch (texture->format) {
   505         case SDL_PIXELFORMAT_ABGR8888:
   506             pixfmt = MTLPixelFormatRGBA8Unorm;
   507             break;
   508         case SDL_PIXELFORMAT_ARGB8888:
   509             pixfmt = MTLPixelFormatBGRA8Unorm;
   510             break;
   511         case SDL_PIXELFORMAT_IYUV:
   512         case SDL_PIXELFORMAT_YV12:
   513         case SDL_PIXELFORMAT_NV12:
   514         case SDL_PIXELFORMAT_NV21:
   515             pixfmt = MTLPixelFormatR8Unorm;
   516             break;
   517         default:
   518             return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
   519     }
   520 
   521     MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixfmt
   522                                             width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO];
   523 
   524     /* Not available in iOS 8. */
   525     if ([mtltexdesc respondsToSelector:@selector(usage)]) {
   526         if (texture->access == SDL_TEXTUREACCESS_TARGET) {
   527             mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
   528         } else {
   529             mtltexdesc.usage = MTLTextureUsageShaderRead;
   530         }
   531     }
   532     
   533     id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
   534     if (mtltexture == nil) {
   535         return SDL_SetError("Texture allocation failed");
   536     }
   537 
   538     id<MTLTexture> mtltexture_uv = nil;
   539 
   540     BOOL yuv = (texture->format == SDL_PIXELFORMAT_IYUV) || (texture->format == SDL_PIXELFORMAT_YV12);
   541     BOOL nv12 = (texture->format == SDL_PIXELFORMAT_NV12) || (texture->format == SDL_PIXELFORMAT_NV21);
   542 
   543     if (yuv) {
   544         mtltexdesc.pixelFormat = MTLPixelFormatR8Unorm;
   545         mtltexdesc.width = (texture->w + 1) / 2;
   546         mtltexdesc.height = (texture->h + 1) / 2;
   547         mtltexdesc.textureType = MTLTextureType2DArray;
   548         mtltexdesc.arrayLength = 2;
   549     } else if (nv12) {
   550         mtltexdesc.pixelFormat = MTLPixelFormatRG8Unorm;
   551         mtltexdesc.width = (texture->w + 1) / 2;
   552         mtltexdesc.height = (texture->h + 1) / 2;
   553     }
   554 
   555     if (yuv || nv12) {
   556         mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
   557         if (mtltexture_uv == nil) {
   558 #if !__has_feature(objc_arc)
   559             [mtltexture release];
   560 #endif
   561             return SDL_SetError("Texture allocation failed");
   562         }
   563     }
   564 
   565     METAL_TextureData *texturedata = [[METAL_TextureData alloc] init];
   566     if (texture->scaleMode == SDL_ScaleModeNearest) {
   567         texturedata.mtlsampler = data.mtlsamplernearest;
   568     } else {
   569         texturedata.mtlsampler = data.mtlsamplerlinear;
   570     }
   571     texturedata.mtltexture = mtltexture;
   572     texturedata.mtltexture_uv = mtltexture_uv;
   573 
   574     texturedata.yuv = yuv;
   575     texturedata.nv12 = nv12;
   576 
   577     if (yuv) {
   578         texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV;
   579     } else if (texture->format == SDL_PIXELFORMAT_NV12) {
   580         texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV12;
   581     } else if (texture->format == SDL_PIXELFORMAT_NV21) {
   582         texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV21;
   583     } else {
   584         texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY;
   585     }
   586 
   587     if (yuv || nv12) {
   588         size_t offset = 0;
   589         SDL_YUV_CONVERSION_MODE mode = SDL_GetYUVConversionModeForResolution(texture->w, texture->h);
   590         switch (mode) {
   591             case SDL_YUV_CONVERSION_JPEG: offset = CONSTANTS_OFFSET_DECODE_JPEG; break;
   592             case SDL_YUV_CONVERSION_BT601: offset = CONSTANTS_OFFSET_DECODE_BT601; break;
   593             case SDL_YUV_CONVERSION_BT709: offset = CONSTANTS_OFFSET_DECODE_BT709; break;
   594             default: offset = 0; break;
   595         }
   596         texturedata.conversionBufferOffset = offset;
   597     }
   598 
   599     texture->driverdata = (void*)CFBridgingRetain(texturedata);
   600 
   601 #if !__has_feature(objc_arc)
   602     [texturedata release];
   603     [mtltexture release];
   604     [mtltexture_uv release];
   605 #endif
   606 
   607     return 0;
   608 }}
   609 
   610 static int
   611 METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
   612                  const SDL_Rect * rect, const void *pixels, int pitch)
   613 { @autoreleasepool {
   614     METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
   615 
   616     /* !!! FIXME: replaceRegion does not do any synchronization, so it might
   617      * !!! FIXME: stomp on a previous frame's data that's currently being read
   618      * !!! FIXME: by the GPU. */
   619     [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
   620                               mipmapLevel:0
   621                                 withBytes:pixels
   622                               bytesPerRow:pitch];
   623 
   624     if (texturedata.yuv) {
   625         int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0;
   626         int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1;
   627 
   628         /* Skip to the correct offset into the next texture */
   629         pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
   630         [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
   631                                      mipmapLevel:0
   632                                            slice:Uslice
   633                                        withBytes:pixels
   634                                      bytesPerRow:(pitch + 1) / 2
   635                                    bytesPerImage:0];
   636 
   637         /* Skip to the correct offset into the next texture */
   638         pixels = (const void*)((const Uint8*)pixels + ((rect->h + 1) / 2) * ((pitch + 1)/2));
   639         [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
   640                                      mipmapLevel:0
   641                                            slice:Vslice
   642                                        withBytes:pixels
   643                                      bytesPerRow:(pitch + 1) / 2
   644                                    bytesPerImage:0];
   645     }
   646 
   647     if (texturedata.nv12) {
   648         /* Skip to the correct offset into the next texture */
   649         pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
   650         [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
   651                                      mipmapLevel:0
   652                                            slice:0
   653                                        withBytes:pixels
   654                                      bytesPerRow:2 * ((pitch + 1) / 2)
   655                                    bytesPerImage:0];
   656     }
   657 
   658     return 0;
   659 }}
   660 
   661 static int
   662 METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
   663                     const SDL_Rect * rect,
   664                     const Uint8 *Yplane, int Ypitch,
   665                     const Uint8 *Uplane, int Upitch,
   666                     const Uint8 *Vplane, int Vpitch)
   667 { @autoreleasepool {
   668     METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
   669     const int Uslice = 0;
   670     const int Vslice = 1;
   671 
   672     /* Bail out if we're supposed to update an empty rectangle */
   673     if (rect->w <= 0 || rect->h <= 0) {
   674         return 0;
   675     }
   676 
   677     [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
   678                               mipmapLevel:0
   679                                 withBytes:Yplane
   680                               bytesPerRow:Ypitch];
   681 
   682     [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
   683                                  mipmapLevel:0
   684                                        slice:Uslice
   685                                    withBytes:Uplane
   686                                  bytesPerRow:Upitch
   687                                bytesPerImage:0];
   688 
   689     [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
   690                                  mipmapLevel:0
   691                                        slice:Vslice
   692                                    withBytes:Vplane
   693                                  bytesPerRow:Vpitch
   694                                bytesPerImage:0];
   695 
   696     return 0;
   697 }}
   698 
   699 static int
   700 METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
   701                const SDL_Rect * rect, void **pixels, int *pitch)
   702 {
   703     return SDL_Unsupported();   // !!! FIXME: write me
   704 }
   705 
   706 static void
   707 METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture)
   708 {
   709     // !!! FIXME: write me
   710 }
   711 
   712 static int
   713 METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
   714 { @autoreleasepool {
   715     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   716 
   717     if (data.mtlcmdencoder) {
   718         /* End encoding for the previous render target so we can set up a new
   719          * render pass for this one. */
   720         [data.mtlcmdencoder endEncoding];
   721         [data.mtlcmdbuffer commit];
   722 
   723         data.mtlcmdencoder = nil;
   724         data.mtlcmdbuffer = nil;
   725     }
   726 
   727     /* We don't begin a new render pass right away - we delay it until an actual
   728      * draw or clear happens. That way we can use hardware clears when possible,
   729      * which are only available when beginning a new render pass. */
   730     return 0;
   731 }}
   732 
   733 
   734 // normalize a value from 0.0f to len into 0.0f to 1.0f.
   735 static inline float
   736 normtex(const float _val, const float len)
   737 {
   738     return _val / len;
   739 }
   740 
   741 static int
   742 METAL_QueueSetViewport(SDL_Renderer * renderer, SDL_RenderCommand *cmd)
   743 {
   744     float projection[4][4];    /* Prepare an orthographic projection */
   745     const int w = cmd->data.viewport.rect.w;
   746     const int h = cmd->data.viewport.rect.h;
   747     const size_t matrixlen = sizeof (projection);
   748     float *matrix = (float *) SDL_AllocateRenderVertices(renderer, matrixlen, CONSTANT_ALIGN, &cmd->data.viewport.first);
   749     if (!matrix) {
   750         return -1;
   751     }
   752 
   753     SDL_memset(projection, '\0', matrixlen);
   754     if (w && h) {
   755         projection[0][0] = 2.0f / w;
   756         projection[1][1] = -2.0f / h;
   757         projection[3][0] = -1.0f;
   758         projection[3][1] = 1.0f;
   759         projection[3][3] = 1.0f;
   760     }
   761     SDL_memcpy(matrix, projection, matrixlen);
   762 
   763     return 0;
   764 }
   765 
   766 static int
   767 METAL_QueueSetDrawColor(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
   768 {
   769     const size_t vertlen = sizeof (float) * 4;
   770     float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, CONSTANT_ALIGN, &cmd->data.color.first);
   771     if (!verts) {
   772         return -1;
   773     }
   774     *(verts++) = ((float)cmd->data.color.r) / 255.0f;
   775     *(verts++) = ((float)cmd->data.color.g) / 255.0f;
   776     *(verts++) = ((float)cmd->data.color.b) / 255.0f;
   777     *(verts++) = ((float)cmd->data.color.a) / 255.0f;
   778     return 0;
   779 }
   780 
   781 static int
   782 METAL_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count)
   783 {
   784     const size_t vertlen = (sizeof (float) * 2) * count;
   785     float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
   786     if (!verts) {
   787         return -1;
   788     }
   789     cmd->data.draw.count = count;
   790     SDL_memcpy(verts, points, vertlen);
   791     return 0;
   792 }
   793 
   794 static int
   795 METAL_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects, int count)
   796 {
   797     // !!! FIXME: use an index buffer
   798     const size_t vertlen = (sizeof (float) * 8) * count;
   799     float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
   800     if (!verts) {
   801         return -1;
   802     }
   803 
   804     cmd->data.draw.count = count;
   805 
   806     for (int i = 0; i < count; i++, rects++) {
   807         if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) {
   808             cmd->data.draw.count--;
   809         } else {
   810             *(verts++) = rects->x;
   811             *(verts++) = rects->y + rects->h;
   812             *(verts++) = rects->x;
   813             *(verts++) = rects->y;
   814             *(verts++) = rects->x + rects->w;
   815             *(verts++) = rects->y + rects->h;
   816             *(verts++) = rects->x + rects->w;
   817             *(verts++) = rects->y;
   818         }
   819     }
   820 
   821     if (cmd->data.draw.count == 0) {
   822         cmd->command = SDL_RENDERCMD_NO_OP;  // nothing to do, just skip this one later.
   823     }
   824 
   825     return 0;
   826 }
   827 
   828 static int
   829 METAL_QueueCopy(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
   830                 const SDL_Rect * srcrect, const SDL_FRect * dstrect)
   831 {
   832     METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
   833     const float texw = (float) texturedata.mtltexture.width;
   834     const float texh = (float) texturedata.mtltexture.height;
   835     // !!! FIXME: use an index buffer
   836     const size_t vertlen = (sizeof (float) * 16);
   837     float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
   838     if (!verts) {
   839         return -1;
   840     }
   841 
   842     cmd->data.draw.count = 1;
   843 
   844     *(verts++) = dstrect->x;
   845     *(verts++) = dstrect->y + dstrect->h;
   846     *(verts++) = dstrect->x;
   847     *(verts++) = dstrect->y;
   848     *(verts++) = dstrect->x + dstrect->w;
   849     *(verts++) = dstrect->y + dstrect->h;
   850     *(verts++) = dstrect->x + dstrect->w;
   851     *(verts++) = dstrect->y;
   852 
   853     *(verts++) = normtex(srcrect->x, texw);
   854     *(verts++) = normtex(srcrect->y + srcrect->h, texh);
   855     *(verts++) = normtex(srcrect->x, texw);
   856     *(verts++) = normtex(srcrect->y, texh);
   857     *(verts++) = normtex(srcrect->x + srcrect->w, texw);
   858     *(verts++) = normtex(srcrect->y + srcrect->h, texh);
   859     *(verts++) = normtex(srcrect->x + srcrect->w, texw);
   860     *(verts++) = normtex(srcrect->y, texh);
   861 
   862     return 0;
   863 }
   864 
   865 static int
   866 METAL_QueueCopyEx(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
   867                   const SDL_Rect * srcquad, const SDL_FRect * dstrect,
   868                   const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
   869 {
   870     METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
   871     const float texw = (float) texturedata.mtltexture.width;
   872     const float texh = (float) texturedata.mtltexture.height;
   873     const float rads = (float)(M_PI * (float) angle / 180.0f);
   874     const float c = cosf(rads), s = sinf(rads);
   875     float minu, maxu, minv, maxv;
   876     const size_t vertlen = (sizeof (float) * 32);
   877     float *verts;
   878 
   879     // 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.
   880     verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, CONSTANT_ALIGN, &cmd->data.draw.count);
   881     if (!verts) {
   882         return -1;
   883     }
   884 
   885     // transform matrix
   886     SDL_memset(verts, '\0', sizeof (*verts) * 16);
   887     verts[10] = verts[15] = 1.0f;
   888     // rotation
   889     verts[0] = c;
   890     verts[1] = s;
   891     verts[4] = -s;
   892     verts[5] = c;
   893 
   894     // translation
   895     verts[12] = dstrect->x + center->x;
   896     verts[13] = dstrect->y + center->y;
   897 
   898     // rest of the vertices don't need the aggressive alignment. Pack them in.
   899     verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
   900     if (!verts) {
   901         return -1;
   902     }
   903 
   904     minu = normtex(srcquad->x, texw);
   905     maxu = normtex(srcquad->x + srcquad->w, texw);
   906     minv = normtex(srcquad->y, texh);
   907     maxv = normtex(srcquad->y + srcquad->h, texh);
   908 
   909     if (flip & SDL_FLIP_HORIZONTAL) {
   910         float tmp = maxu;
   911         maxu = minu;
   912         minu = tmp;
   913     }
   914     if (flip & SDL_FLIP_VERTICAL) {
   915         float tmp = maxv;
   916         maxv = minv;
   917         minv = tmp;
   918     }
   919 
   920     // vertices
   921     *(verts++) = -center->x;
   922     *(verts++) = dstrect->h - center->y;
   923     *(verts++) = -center->x;
   924     *(verts++) = -center->y;
   925     *(verts++) = dstrect->w - center->x;
   926     *(verts++) = dstrect->h - center->y;
   927     *(verts++) = dstrect->w - center->x;
   928     *(verts++) = -center->y;
   929 
   930     // texcoords
   931     *(verts++) = minu;
   932     *(verts++) = maxv;
   933     *(verts++) = minu;
   934     *(verts++) = minv;
   935     *(verts++) = maxu;
   936     *(verts++) = maxv;
   937     *(verts++) = maxu;
   938     *(verts++) = minv;
   939 
   940     return 0;
   941 }
   942 
   943 
   944 typedef struct
   945 {
   946     #if __has_feature(objc_arc)
   947     __unsafe_unretained id<MTLRenderPipelineState> pipeline;
   948     #else
   949     id<MTLRenderPipelineState> pipeline;
   950     #endif
   951     size_t constants_offset;
   952     SDL_Texture *texture;
   953     SDL_bool cliprect_dirty;
   954     SDL_bool cliprect_enabled;
   955     SDL_Rect cliprect;
   956     SDL_bool viewport_dirty;
   957     SDL_Rect viewport;
   958     size_t projection_offset;
   959     SDL_bool color_dirty;
   960     size_t color_offset;
   961 } METAL_DrawStateCache;
   962 
   963 static void
   964 SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const SDL_MetalFragmentFunction shader,
   965              const size_t constants_offset, id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache)
   966 {
   967     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   968     const SDL_BlendMode blend = cmd->data.draw.blend;
   969     size_t first = cmd->data.draw.first;
   970     id<MTLRenderPipelineState> newpipeline;
   971 
   972     METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
   973 
   974     if (statecache->viewport_dirty) {
   975         MTLViewport viewport;
   976         viewport.originX = statecache->viewport.x;
   977         viewport.originY = statecache->viewport.y;
   978         viewport.width = statecache->viewport.w;
   979         viewport.height = statecache->viewport.h;
   980         viewport.znear = 0.0;
   981         viewport.zfar = 1.0;
   982         [data.mtlcmdencoder setViewport:viewport];
   983         [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:statecache->projection_offset atIndex:2];  // projection
   984         statecache->viewport_dirty = SDL_FALSE;
   985     }
   986 
   987     if (statecache->cliprect_dirty) {
   988         MTLScissorRect mtlrect;
   989         if (statecache->cliprect_enabled) {
   990             const SDL_Rect *rect = &statecache->cliprect;
   991             mtlrect.x = statecache->viewport.x + rect->x;
   992             mtlrect.y = statecache->viewport.y + rect->y;
   993             mtlrect.width = rect->w;
   994             mtlrect.height = rect->h;
   995         } else {
   996             mtlrect.x = statecache->viewport.x;
   997             mtlrect.y = statecache->viewport.y;
   998             mtlrect.width = statecache->viewport.w;
   999             mtlrect.height = statecache->viewport.h;
  1000         }
  1001         if (mtlrect.width > 0 && mtlrect.height > 0) {
  1002             [data.mtlcmdencoder setScissorRect:mtlrect];
  1003         }
  1004         statecache->cliprect_dirty = SDL_FALSE;
  1005     }
  1006 
  1007     if (statecache->color_dirty) {
  1008         [data.mtlcmdencoder setFragmentBuffer:mtlbufvertex offset:statecache->color_offset atIndex:0];
  1009         statecache->color_dirty = SDL_FALSE;
  1010     }
  1011 
  1012     newpipeline = ChoosePipelineState(data, data.activepipelines, shader, blend);
  1013     if (newpipeline != statecache->pipeline) {
  1014         [data.mtlcmdencoder setRenderPipelineState:newpipeline];
  1015         statecache->pipeline = newpipeline;
  1016     }
  1017 
  1018     if (constants_offset != statecache->constants_offset) {
  1019         if (constants_offset != CONSTANTS_OFFSET_INVALID) {
  1020             [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:constants_offset atIndex:3];
  1021         }
  1022         statecache->constants_offset = constants_offset;
  1023     }
  1024 
  1025     [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:first atIndex:0];  // position
  1026 }
  1027 
  1028 static void
  1029 SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const size_t constants_offset,
  1030              id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache)
  1031 {
  1032     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1033     SDL_Texture *texture = cmd->data.draw.texture;
  1034     METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
  1035 
  1036     SetDrawState(renderer, cmd, texturedata.fragmentFunction, constants_offset, mtlbufvertex, statecache);
  1037 
  1038     [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:cmd->data.draw.first+(8*sizeof (float)) atIndex:1];  // texcoords
  1039 
  1040     if (texture != statecache->texture) {
  1041         METAL_TextureData *oldtexturedata = NULL;
  1042         if (statecache->texture) {
  1043             oldtexturedata = (__bridge METAL_TextureData *) statecache->texture->driverdata;
  1044         }
  1045         if (!oldtexturedata || (texturedata.mtlsampler != oldtexturedata.mtlsampler)) {
  1046             [data.mtlcmdencoder setFragmentSamplerState:texturedata.mtlsampler atIndex:0];
  1047         }
  1048 
  1049         [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0];
  1050         if (texturedata.yuv || texturedata.nv12) {
  1051             [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture_uv atIndex:1];
  1052             [data.mtlcmdencoder setFragmentBuffer:data.mtlbufconstants offset:texturedata.conversionBufferOffset atIndex:1];
  1053         }
  1054         statecache->texture = texture;
  1055     }
  1056 }
  1057 
  1058 static int
  1059 METAL_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize)
  1060 { @autoreleasepool {
  1061     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1062     METAL_DrawStateCache statecache;
  1063     id<MTLBuffer> mtlbufvertex = nil;
  1064 
  1065     statecache.pipeline = nil;
  1066     statecache.constants_offset = CONSTANTS_OFFSET_INVALID;
  1067     statecache.texture = NULL;
  1068     statecache.color_dirty = SDL_TRUE;
  1069     statecache.cliprect_dirty = SDL_TRUE;
  1070     statecache.viewport_dirty = SDL_TRUE;
  1071     statecache.projection_offset = 0;
  1072     statecache.color_offset = 0;
  1073 
  1074     // !!! FIXME: have a ring of pre-made MTLBuffers we cycle through? How expensive is creation?
  1075     if (vertsize > 0) {
  1076         id<MTLBuffer> mtlbufvertexstaging = [data.mtldevice newBufferWithLength:vertsize options:MTLResourceStorageModeShared];
  1077         #if !__has_feature(objc_arc)
  1078         [mtlbufvertexstaging autorelease];
  1079         #endif
  1080         mtlbufvertexstaging.label = @"SDL vertex staging data";
  1081         SDL_memcpy([mtlbufvertexstaging contents], vertices, vertsize);
  1082 
  1083         // Move our new vertex buffer from system RAM to GPU memory so any draw calls can use it.
  1084         mtlbufvertex = [data.mtldevice newBufferWithLength:vertsize options:MTLResourceStorageModePrivate];
  1085         #if !__has_feature(objc_arc)
  1086         [mtlbufvertex autorelease];
  1087         #endif
  1088         mtlbufvertex.label = @"SDL vertex data";
  1089         id<MTLCommandBuffer> cmdbuffer = [data.mtlcmdqueue commandBuffer];
  1090         id<MTLBlitCommandEncoder> blitcmd = [cmdbuffer blitCommandEncoder];
  1091         [blitcmd copyFromBuffer:mtlbufvertexstaging sourceOffset:0 toBuffer:mtlbufvertex destinationOffset:0 size:vertsize];
  1092         [blitcmd endEncoding];
  1093         [cmdbuffer commit];
  1094     }
  1095 
  1096     // If there's a command buffer here unexpectedly (app requested one?). Commit it so we can start fresh.
  1097     [data.mtlcmdencoder endEncoding];
  1098     [data.mtlcmdbuffer commit];
  1099     data.mtlcmdencoder = nil;
  1100     data.mtlcmdbuffer = nil;
  1101 
  1102     while (cmd) {
  1103         switch (cmd->command) {
  1104             case SDL_RENDERCMD_SETVIEWPORT: {
  1105                 SDL_memcpy(&statecache.viewport, &cmd->data.viewport.rect, sizeof (statecache.viewport));
  1106                 statecache.projection_offset = cmd->data.viewport.first;
  1107                 statecache.viewport_dirty = SDL_TRUE;
  1108                 break;
  1109             }
  1110 
  1111             case SDL_RENDERCMD_SETCLIPRECT: {
  1112                 SDL_memcpy(&statecache.cliprect, &cmd->data.cliprect.rect, sizeof (statecache.cliprect));
  1113                 statecache.cliprect_enabled = cmd->data.cliprect.enabled;
  1114                 statecache.cliprect_dirty = SDL_TRUE;
  1115                 break;
  1116             }
  1117 
  1118             case SDL_RENDERCMD_SETDRAWCOLOR: {
  1119                 statecache.color_offset = cmd->data.color.first;
  1120                 statecache.color_dirty = SDL_TRUE;
  1121                 break;
  1122             }
  1123 
  1124             case SDL_RENDERCMD_CLEAR: {
  1125                 /* If we're already encoding a command buffer, dump it without committing it. We'd just
  1126                     clear all its work anyhow, and starting a new encoder will let us use a hardware clear
  1127                     operation via MTLLoadActionClear. */
  1128                 if (data.mtlcmdencoder != nil) {
  1129                     [data.mtlcmdencoder endEncoding];
  1130 
  1131                     // !!! FIXME: have to commit, or an uncommitted but enqueued buffer will prevent the frame from finishing.
  1132                     [data.mtlcmdbuffer commit];
  1133                     data.mtlcmdencoder = nil;
  1134                     data.mtlcmdbuffer = nil;
  1135                 }
  1136 
  1137                 // force all this state to be reconfigured on next command buffer.
  1138                 statecache.pipeline = nil;
  1139                 statecache.constants_offset = CONSTANTS_OFFSET_INVALID;
  1140                 statecache.texture = NULL;
  1141                 statecache.color_dirty = SDL_TRUE;
  1142                 statecache.cliprect_dirty = SDL_TRUE;
  1143                 statecache.viewport_dirty = SDL_TRUE;
  1144 
  1145                 const Uint8 r = cmd->data.color.r;
  1146                 const Uint8 g = cmd->data.color.g;
  1147                 const Uint8 b = cmd->data.color.b;
  1148                 const Uint8 a = cmd->data.color.a;
  1149                 MTLClearColor color = MTLClearColorMake(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
  1150 
  1151                 // get new command encoder, set up with an initial clear operation.
  1152                 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear, &color);
  1153                 break;
  1154             }
  1155 
  1156             case SDL_RENDERCMD_DRAW_POINTS:
  1157             case SDL_RENDERCMD_DRAW_LINES: {
  1158                 const size_t count = cmd->data.draw.count;
  1159                 const MTLPrimitiveType primtype = (cmd->command == SDL_RENDERCMD_DRAW_POINTS) ? MTLPrimitiveTypePoint : MTLPrimitiveTypeLineStrip;
  1160                 SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache);
  1161                 [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
  1162                 break;
  1163             }
  1164 
  1165             case SDL_RENDERCMD_FILL_RECTS: {
  1166                 const size_t count = cmd->data.draw.count;
  1167                 size_t start = 0;
  1168                 SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache);
  1169                 for (size_t i = 0; i < count; i++, start += 4) {   // !!! FIXME: can we do all of these this with a single draw call, using MTLPrimitiveTypeTriangle and an index buffer?
  1170                     [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:start vertexCount:4];
  1171                 }
  1172                 break;
  1173             }
  1174 
  1175             case SDL_RENDERCMD_COPY: {
  1176                 SetCopyState(renderer, cmd, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache);
  1177                 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
  1178                 break;
  1179             }
  1180 
  1181             case SDL_RENDERCMD_COPY_EX: {
  1182                 SetCopyState(renderer, cmd, CONSTANTS_OFFSET_INVALID, mtlbufvertex, &statecache);
  1183                 [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:cmd->data.draw.count atIndex:3];  // transform
  1184                 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
  1185                 break;
  1186             }
  1187 
  1188             case SDL_RENDERCMD_NO_OP:
  1189                 break;
  1190         }
  1191         cmd = cmd->next;
  1192     }
  1193 
  1194     return 0;
  1195 }}
  1196 
  1197 static int
  1198 METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
  1199                     Uint32 pixel_format, void * pixels, int pitch)
  1200 { @autoreleasepool {
  1201     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1202     METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
  1203 
  1204     // Commit any current command buffer, and waitUntilCompleted, so any output is ready to be read.
  1205     [data.mtlcmdencoder endEncoding];
  1206     [data.mtlcmdbuffer commit];
  1207     [data.mtlcmdbuffer waitUntilCompleted];
  1208     data.mtlcmdencoder = nil;
  1209     data.mtlcmdbuffer = nil;
  1210 
  1211     id<MTLTexture> mtltexture = data.mtlpassdesc.colorAttachments[0].texture;
  1212     MTLRegion mtlregion = MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h);
  1213 
  1214     // we only do BGRA8 or RGBA8 at the moment, so 4 will do.
  1215     const int temp_pitch = rect->w * 4;
  1216     void *temp_pixels = SDL_malloc(temp_pitch * rect->h);
  1217     if (!temp_pixels) {
  1218         return SDL_OutOfMemory();
  1219     }
  1220 
  1221     [mtltexture getBytes:temp_pixels bytesPerRow:temp_pitch fromRegion:mtlregion mipmapLevel:0];
  1222 
  1223     const Uint32 temp_format = (mtltexture.pixelFormat == MTLPixelFormatBGRA8Unorm) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_ABGR8888;
  1224     const int status = SDL_ConvertPixels(rect->w, rect->h, temp_format, temp_pixels, temp_pitch, pixel_format, pixels, pitch);
  1225     SDL_free(temp_pixels);
  1226     return status;
  1227 }}
  1228 
  1229 static void
  1230 METAL_RenderPresent(SDL_Renderer * renderer)
  1231 { @autoreleasepool {
  1232     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1233 
  1234     if (data.mtlcmdencoder != nil) {
  1235         [data.mtlcmdencoder endEncoding];
  1236     }
  1237     if (data.mtlbackbuffer != nil) {
  1238         [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
  1239     }
  1240     if (data.mtlcmdbuffer != nil) {
  1241         [data.mtlcmdbuffer commit];
  1242     }
  1243     data.mtlcmdencoder = nil;
  1244     data.mtlcmdbuffer = nil;
  1245     data.mtlbackbuffer = nil;
  1246 }}
  1247 
  1248 static void
  1249 METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture)
  1250 { @autoreleasepool {
  1251     CFBridgingRelease(texture->driverdata);
  1252     texture->driverdata = NULL;
  1253 }}
  1254 
  1255 static void
  1256 METAL_DestroyRenderer(SDL_Renderer * renderer)
  1257 { @autoreleasepool {
  1258     if (renderer->driverdata) {
  1259         METAL_RenderData *data = CFBridgingRelease(renderer->driverdata);
  1260 
  1261         if (data.mtlcmdencoder != nil) {
  1262             [data.mtlcmdencoder endEncoding];
  1263         }
  1264 
  1265         DestroyAllPipelines(data.allpipelines, data.pipelinescount);
  1266     }
  1267 
  1268     SDL_free(renderer);
  1269 }}
  1270 
  1271 static void *
  1272 METAL_GetMetalLayer(SDL_Renderer * renderer)
  1273 { @autoreleasepool {
  1274     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1275     return (__bridge void*)data.mtllayer;
  1276 }}
  1277 
  1278 static void *
  1279 METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
  1280 { @autoreleasepool {
  1281     METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
  1282     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1283     return (__bridge void*)data.mtlcmdencoder;
  1284 }}
  1285 
  1286 static SDL_Renderer *
  1287 METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
  1288 { @autoreleasepool {
  1289     SDL_Renderer *renderer = NULL;
  1290     METAL_RenderData *data = NULL;
  1291     id<MTLDevice> mtldevice = nil;
  1292     SDL_SysWMinfo syswm;
  1293 
  1294     SDL_VERSION(&syswm.version);
  1295     if (!SDL_GetWindowWMInfo(window, &syswm)) {
  1296         return NULL;
  1297     }
  1298 
  1299     if (IsMetalAvailable(&syswm) == -1) {
  1300         return NULL;
  1301     }
  1302 
  1303     renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer));
  1304     if (!renderer) {
  1305         SDL_OutOfMemory();
  1306         return NULL;
  1307     }
  1308 
  1309     // !!! FIXME: MTLCopyAllDevices() can find other GPUs on macOS...
  1310     mtldevice = MTLCreateSystemDefaultDevice();
  1311 
  1312     if (mtldevice == nil) {
  1313         SDL_free(renderer);
  1314         SDL_SetError("Failed to obtain Metal device");
  1315         return NULL;
  1316     }
  1317 
  1318     // !!! FIXME: error checking on all of this.
  1319     data = [[METAL_RenderData alloc] init];
  1320 
  1321     renderer->driverdata = (void*)CFBridgingRetain(data);
  1322     renderer->window = window;
  1323 
  1324 #ifdef __MACOSX__
  1325     NSView *view = Cocoa_Mtl_AddMetalView(window);
  1326     CAMetalLayer *layer = (CAMetalLayer *)[view layer];
  1327 
  1328     layer.device = mtldevice;
  1329 
  1330     //layer.colorspace = nil;
  1331 
  1332 #else
  1333     UIView *view = UIKit_Mtl_AddMetalView(window);
  1334     CAMetalLayer *layer = (CAMetalLayer *)[view layer];
  1335 #endif
  1336 
  1337     // Necessary for RenderReadPixels.
  1338     layer.framebufferOnly = NO;
  1339 
  1340     data.mtldevice = layer.device;
  1341     data.mtllayer = layer;
  1342     id<MTLCommandQueue> mtlcmdqueue = [data.mtldevice newCommandQueue];
  1343     data.mtlcmdqueue = mtlcmdqueue;
  1344     data.mtlcmdqueue.label = @"SDL Metal Renderer";
  1345     data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
  1346 
  1347     NSError *err = nil;
  1348 
  1349     // The compiled .metallib is embedded in a static array in a header file
  1350     // but the original shader source code is in SDL_shaders_metal.metal.
  1351     dispatch_data_t mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{});
  1352     id<MTLLibrary> mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
  1353     data.mtllibrary = mtllibrary;
  1354     SDL_assert(err == nil);
  1355 #if !__has_feature(objc_arc)
  1356     dispatch_release(mtllibdata);
  1357 #endif
  1358     data.mtllibrary.label = @"SDL Metal renderer shader library";
  1359 
  1360     /* Do some shader pipeline state loading up-front rather than on demand. */
  1361     data.pipelinescount = 0;
  1362     data.allpipelines = NULL;
  1363     ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm);
  1364 
  1365     MTLSamplerDescriptor *samplerdesc = [[MTLSamplerDescriptor alloc] init];
  1366 
  1367     samplerdesc.minFilter = MTLSamplerMinMagFilterNearest;
  1368     samplerdesc.magFilter = MTLSamplerMinMagFilterNearest;
  1369     id<MTLSamplerState> mtlsamplernearest = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
  1370     data.mtlsamplernearest = mtlsamplernearest;
  1371 
  1372     samplerdesc.minFilter = MTLSamplerMinMagFilterLinear;
  1373     samplerdesc.magFilter = MTLSamplerMinMagFilterLinear;
  1374     id<MTLSamplerState> mtlsamplerlinear = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
  1375     data.mtlsamplerlinear = mtlsamplerlinear;
  1376 
  1377     /* Note: matrices are column major. */
  1378     float identitytransform[16] = {
  1379         1.0f, 0.0f, 0.0f, 0.0f,
  1380         0.0f, 1.0f, 0.0f, 0.0f,
  1381         0.0f, 0.0f, 1.0f, 0.0f,
  1382         0.0f, 0.0f, 0.0f, 1.0f,
  1383     };
  1384 
  1385     float halfpixeltransform[16] = {
  1386         1.0f, 0.0f, 0.0f, 0.0f,
  1387         0.0f, 1.0f, 0.0f, 0.0f,
  1388         0.0f, 0.0f, 1.0f, 0.0f,
  1389         0.5f, 0.5f, 0.0f, 1.0f,
  1390     };
  1391 
  1392     /* Metal pads float3s to 16 bytes. */
  1393     float decodetransformJPEG[4*4] = {
  1394         0.0, -0.501960814, -0.501960814, 0.0, /* offset */
  1395         1.0000,  0.0000,  1.4020, 0.0,        /* Rcoeff */
  1396         1.0000, -0.3441, -0.7141, 0.0,        /* Gcoeff */
  1397         1.0000,  1.7720,  0.0000, 0.0,        /* Bcoeff */
  1398     };
  1399 
  1400     float decodetransformBT601[4*4] = {
  1401         -0.0627451017, -0.501960814, -0.501960814, 0.0, /* offset */
  1402         1.1644,  0.0000,  1.5960, 0.0,                  /* Rcoeff */
  1403         1.1644, -0.3918, -0.8130, 0.0,                  /* Gcoeff */
  1404         1.1644,  2.0172,  0.0000, 0.0,                  /* Bcoeff */
  1405     };
  1406 
  1407     float decodetransformBT709[4*4] = {
  1408         0.0, -0.501960814, -0.501960814, 0.0, /* offset */
  1409         1.0000,  0.0000,  1.4020, 0.0,        /* Rcoeff */
  1410         1.0000, -0.3441, -0.7141, 0.0,        /* Gcoeff */
  1411         1.0000,  1.7720,  0.0000, 0.0,        /* Bcoeff */
  1412     };
  1413 
  1414     id<MTLBuffer> mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared];
  1415     #if !__has_feature(objc_arc)
  1416     [mtlbufconstantstaging autorelease];
  1417     #endif
  1418     mtlbufconstantstaging.label = @"SDL constant staging data";
  1419 
  1420     id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate];
  1421     data.mtlbufconstants = mtlbufconstants;
  1422     data.mtlbufconstants.label = @"SDL constant data";
  1423 
  1424     char *constantdata = [mtlbufconstantstaging contents];
  1425     SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform));
  1426     SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform));
  1427     SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_JPEG, decodetransformJPEG, sizeof(decodetransformJPEG));
  1428     SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601, decodetransformBT601, sizeof(decodetransformBT601));
  1429     SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709, decodetransformBT709, sizeof(decodetransformBT709));
  1430 
  1431     id<MTLCommandBuffer> cmdbuffer = [data.mtlcmdqueue commandBuffer];
  1432     id<MTLBlitCommandEncoder> blitcmd = [cmdbuffer blitCommandEncoder];
  1433 
  1434     [blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:data.mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH];
  1435 
  1436     [blitcmd endEncoding];
  1437     [cmdbuffer commit];
  1438 
  1439     // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
  1440 
  1441     renderer->WindowEvent = METAL_WindowEvent;
  1442     renderer->GetOutputSize = METAL_GetOutputSize;
  1443     renderer->SupportsBlendMode = METAL_SupportsBlendMode;
  1444     renderer->CreateTexture = METAL_CreateTexture;
  1445     renderer->UpdateTexture = METAL_UpdateTexture;
  1446     renderer->UpdateTextureYUV = METAL_UpdateTextureYUV;
  1447     renderer->LockTexture = METAL_LockTexture;
  1448     renderer->UnlockTexture = METAL_UnlockTexture;
  1449     renderer->SetRenderTarget = METAL_SetRenderTarget;
  1450     renderer->QueueSetViewport = METAL_QueueSetViewport;
  1451     renderer->QueueSetDrawColor = METAL_QueueSetDrawColor;
  1452     renderer->QueueDrawPoints = METAL_QueueDrawPoints;
  1453     renderer->QueueDrawLines = METAL_QueueDrawPoints;  // lines and points queue the same way.
  1454     renderer->QueueFillRects = METAL_QueueFillRects;
  1455     renderer->QueueCopy = METAL_QueueCopy;
  1456     renderer->QueueCopyEx = METAL_QueueCopyEx;
  1457     renderer->RunCommandQueue = METAL_RunCommandQueue;
  1458     renderer->RenderReadPixels = METAL_RenderReadPixels;
  1459     renderer->RenderPresent = METAL_RenderPresent;
  1460     renderer->DestroyTexture = METAL_DestroyTexture;
  1461     renderer->DestroyRenderer = METAL_DestroyRenderer;
  1462     renderer->GetMetalLayer = METAL_GetMetalLayer;
  1463     renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
  1464 
  1465     renderer->info = METAL_RenderDriver.info;
  1466     renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
  1467 
  1468     renderer->always_batch = SDL_TRUE;
  1469 
  1470 #if defined(__MACOSX__) && defined(MAC_OS_X_VERSION_10_13)
  1471     if (@available(macOS 10.13, *)) {
  1472         data.mtllayer.displaySyncEnabled = (flags & SDL_RENDERER_PRESENTVSYNC) != 0;
  1473     } else
  1474 #endif
  1475     {
  1476         renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
  1477     }
  1478 
  1479     /* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
  1480     int maxtexsize = 4096;
  1481 #if defined(__MACOSX__)
  1482     maxtexsize = 16384;
  1483 #elif defined(__TVOS__)
  1484     maxtexsize = 8192;
  1485 #ifdef __TVOS_11_0
  1486     if (@available(tvOS 11.0, *)) {
  1487         if ([mtldevice supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) {
  1488             maxtexsize = 16384;
  1489         }
  1490     }
  1491 #endif
  1492 #else
  1493 #ifdef __IPHONE_11_0
  1494     if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) {
  1495         maxtexsize = 16384;
  1496     } else
  1497 #endif
  1498 #ifdef __IPHONE_10_0
  1499     if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) {
  1500         maxtexsize = 16384;
  1501     } else
  1502 #endif
  1503     if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2] || [mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) {
  1504         maxtexsize = 8192;
  1505     } else {
  1506         maxtexsize = 4096;
  1507     }
  1508 #endif
  1509 
  1510     renderer->info.max_texture_width = maxtexsize;
  1511     renderer->info.max_texture_height = maxtexsize;
  1512 
  1513 #if !__has_feature(objc_arc)
  1514     [mtlcmdqueue release];
  1515     [mtllibrary release];
  1516     [samplerdesc release];
  1517     [mtlsamplernearest release];
  1518     [mtlsamplerlinear release];
  1519     [mtlbufconstants release];
  1520     [view release];
  1521     [data release];
  1522     [mtldevice release];
  1523 #endif
  1524 
  1525     return renderer;
  1526 }}
  1527 
  1528 SDL_RenderDriver METAL_RenderDriver = {
  1529     METAL_CreateRenderer,
  1530     {
  1531         "metal",
  1532         (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
  1533         6,
  1534         {
  1535             SDL_PIXELFORMAT_ARGB8888,
  1536             SDL_PIXELFORMAT_ABGR8888,
  1537             SDL_PIXELFORMAT_YV12,
  1538             SDL_PIXELFORMAT_IYUV,
  1539             SDL_PIXELFORMAT_NV12,
  1540             SDL_PIXELFORMAT_NV21
  1541         },
  1542     0, 0,
  1543     }
  1544 };
  1545 
  1546 #endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */
  1547 
  1548 /* vi: set ts=4 sw=4 expandtab: */