src/render/metal/SDL_render_metal.m
author Alex Szpakowski <slime73@gmail.com>
Thu, 01 Nov 2018 20:24:21 -0300
changeset 12385 45038f8422c9
parent 12384 b1f5162fd621
child 12392 046dcbdbaa74
permissions -rw-r--r--
metal: avoid an extra buffer allocation and GPU data copy in RunCommandQueue, it's not needed. Improves overall performance.
     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         /* We can memcpy to a shared buffer from the CPU and read it from the GPU
  1077          * without any extra copying. It's a bit slower on macOS to read shared
  1078          * data from the GPU than to read managed/private data, but we avoid the
  1079          * cost of copying the data and the code's simpler. Apple's best
  1080          * practices guide recommends this approach for streamed vertex data.
  1081          * TODO: this buffer is also used for constants. Is performance still
  1082          * good for those, or should we have a managed buffer for them? */
  1083         mtlbufvertex = [data.mtldevice newBufferWithLength:vertsize options:MTLResourceStorageModeShared];
  1084         #if !__has_feature(objc_arc)
  1085         [mtlbufvertex autorelease];
  1086         #endif
  1087         mtlbufvertex.label = @"SDL vertex data";
  1088         SDL_memcpy([mtlbufvertex contents], vertices, vertsize);
  1089     }
  1090 
  1091     // If there's a command buffer here unexpectedly (app requested one?). Commit it so we can start fresh.
  1092     [data.mtlcmdencoder endEncoding];
  1093     [data.mtlcmdbuffer commit];
  1094     data.mtlcmdencoder = nil;
  1095     data.mtlcmdbuffer = nil;
  1096 
  1097     while (cmd) {
  1098         switch (cmd->command) {
  1099             case SDL_RENDERCMD_SETVIEWPORT: {
  1100                 SDL_memcpy(&statecache.viewport, &cmd->data.viewport.rect, sizeof (statecache.viewport));
  1101                 statecache.projection_offset = cmd->data.viewport.first;
  1102                 statecache.viewport_dirty = SDL_TRUE;
  1103                 break;
  1104             }
  1105 
  1106             case SDL_RENDERCMD_SETCLIPRECT: {
  1107                 SDL_memcpy(&statecache.cliprect, &cmd->data.cliprect.rect, sizeof (statecache.cliprect));
  1108                 statecache.cliprect_enabled = cmd->data.cliprect.enabled;
  1109                 statecache.cliprect_dirty = SDL_TRUE;
  1110                 break;
  1111             }
  1112 
  1113             case SDL_RENDERCMD_SETDRAWCOLOR: {
  1114                 statecache.color_offset = cmd->data.color.first;
  1115                 statecache.color_dirty = SDL_TRUE;
  1116                 break;
  1117             }
  1118 
  1119             case SDL_RENDERCMD_CLEAR: {
  1120                 /* If we're already encoding a command buffer, dump it without committing it. We'd just
  1121                     clear all its work anyhow, and starting a new encoder will let us use a hardware clear
  1122                     operation via MTLLoadActionClear. */
  1123                 if (data.mtlcmdencoder != nil) {
  1124                     [data.mtlcmdencoder endEncoding];
  1125 
  1126                     // !!! FIXME: have to commit, or an uncommitted but enqueued buffer will prevent the frame from finishing.
  1127                     [data.mtlcmdbuffer commit];
  1128                     data.mtlcmdencoder = nil;
  1129                     data.mtlcmdbuffer = nil;
  1130                 }
  1131 
  1132                 // force all this state to be reconfigured on next command buffer.
  1133                 statecache.pipeline = nil;
  1134                 statecache.constants_offset = CONSTANTS_OFFSET_INVALID;
  1135                 statecache.texture = NULL;
  1136                 statecache.color_dirty = SDL_TRUE;
  1137                 statecache.cliprect_dirty = SDL_TRUE;
  1138                 statecache.viewport_dirty = SDL_TRUE;
  1139 
  1140                 const Uint8 r = cmd->data.color.r;
  1141                 const Uint8 g = cmd->data.color.g;
  1142                 const Uint8 b = cmd->data.color.b;
  1143                 const Uint8 a = cmd->data.color.a;
  1144                 MTLClearColor color = MTLClearColorMake(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
  1145 
  1146                 // get new command encoder, set up with an initial clear operation.
  1147                 METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear, &color);
  1148                 break;
  1149             }
  1150 
  1151             case SDL_RENDERCMD_DRAW_POINTS:
  1152             case SDL_RENDERCMD_DRAW_LINES: {
  1153                 const size_t count = cmd->data.draw.count;
  1154                 const MTLPrimitiveType primtype = (cmd->command == SDL_RENDERCMD_DRAW_POINTS) ? MTLPrimitiveTypePoint : MTLPrimitiveTypeLineStrip;
  1155                 SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache);
  1156                 [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
  1157                 break;
  1158             }
  1159 
  1160             case SDL_RENDERCMD_FILL_RECTS: {
  1161                 const size_t count = cmd->data.draw.count;
  1162                 size_t start = 0;
  1163                 SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache);
  1164                 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?
  1165                     [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:start vertexCount:4];
  1166                 }
  1167                 break;
  1168             }
  1169 
  1170             case SDL_RENDERCMD_COPY: {
  1171                 SetCopyState(renderer, cmd, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache);
  1172                 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
  1173                 break;
  1174             }
  1175 
  1176             case SDL_RENDERCMD_COPY_EX: {
  1177                 SetCopyState(renderer, cmd, CONSTANTS_OFFSET_INVALID, mtlbufvertex, &statecache);
  1178                 [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:cmd->data.draw.count atIndex:3];  // transform
  1179                 [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
  1180                 break;
  1181             }
  1182 
  1183             case SDL_RENDERCMD_NO_OP:
  1184                 break;
  1185         }
  1186         cmd = cmd->next;
  1187     }
  1188 
  1189     return 0;
  1190 }}
  1191 
  1192 static int
  1193 METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
  1194                     Uint32 pixel_format, void * pixels, int pitch)
  1195 { @autoreleasepool {
  1196     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1197     METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
  1198 
  1199     // Commit any current command buffer, and waitUntilCompleted, so any output is ready to be read.
  1200     [data.mtlcmdencoder endEncoding];
  1201     [data.mtlcmdbuffer commit];
  1202     [data.mtlcmdbuffer waitUntilCompleted];
  1203     data.mtlcmdencoder = nil;
  1204     data.mtlcmdbuffer = nil;
  1205 
  1206     id<MTLTexture> mtltexture = data.mtlpassdesc.colorAttachments[0].texture;
  1207     MTLRegion mtlregion = MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h);
  1208 
  1209     // we only do BGRA8 or RGBA8 at the moment, so 4 will do.
  1210     const int temp_pitch = rect->w * 4;
  1211     void *temp_pixels = SDL_malloc(temp_pitch * rect->h);
  1212     if (!temp_pixels) {
  1213         return SDL_OutOfMemory();
  1214     }
  1215 
  1216     [mtltexture getBytes:temp_pixels bytesPerRow:temp_pitch fromRegion:mtlregion mipmapLevel:0];
  1217 
  1218     const Uint32 temp_format = (mtltexture.pixelFormat == MTLPixelFormatBGRA8Unorm) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_ABGR8888;
  1219     const int status = SDL_ConvertPixels(rect->w, rect->h, temp_format, temp_pixels, temp_pitch, pixel_format, pixels, pitch);
  1220     SDL_free(temp_pixels);
  1221     return status;
  1222 }}
  1223 
  1224 static void
  1225 METAL_RenderPresent(SDL_Renderer * renderer)
  1226 { @autoreleasepool {
  1227     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1228 
  1229     if (data.mtlcmdencoder != nil) {
  1230         [data.mtlcmdencoder endEncoding];
  1231     }
  1232     if (data.mtlbackbuffer != nil) {
  1233         [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
  1234     }
  1235     if (data.mtlcmdbuffer != nil) {
  1236         [data.mtlcmdbuffer commit];
  1237     }
  1238     data.mtlcmdencoder = nil;
  1239     data.mtlcmdbuffer = nil;
  1240     data.mtlbackbuffer = nil;
  1241 }}
  1242 
  1243 static void
  1244 METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture)
  1245 { @autoreleasepool {
  1246     CFBridgingRelease(texture->driverdata);
  1247     texture->driverdata = NULL;
  1248 }}
  1249 
  1250 static void
  1251 METAL_DestroyRenderer(SDL_Renderer * renderer)
  1252 { @autoreleasepool {
  1253     if (renderer->driverdata) {
  1254         METAL_RenderData *data = CFBridgingRelease(renderer->driverdata);
  1255 
  1256         if (data.mtlcmdencoder != nil) {
  1257             [data.mtlcmdencoder endEncoding];
  1258         }
  1259 
  1260         DestroyAllPipelines(data.allpipelines, data.pipelinescount);
  1261     }
  1262 
  1263     SDL_free(renderer);
  1264 }}
  1265 
  1266 static void *
  1267 METAL_GetMetalLayer(SDL_Renderer * renderer)
  1268 { @autoreleasepool {
  1269     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1270     return (__bridge void*)data.mtllayer;
  1271 }}
  1272 
  1273 static void *
  1274 METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
  1275 { @autoreleasepool {
  1276     METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
  1277     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1278     return (__bridge void*)data.mtlcmdencoder;
  1279 }}
  1280 
  1281 static SDL_Renderer *
  1282 METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
  1283 { @autoreleasepool {
  1284     SDL_Renderer *renderer = NULL;
  1285     METAL_RenderData *data = NULL;
  1286     id<MTLDevice> mtldevice = nil;
  1287     SDL_SysWMinfo syswm;
  1288 
  1289     SDL_VERSION(&syswm.version);
  1290     if (!SDL_GetWindowWMInfo(window, &syswm)) {
  1291         return NULL;
  1292     }
  1293 
  1294     if (IsMetalAvailable(&syswm) == -1) {
  1295         return NULL;
  1296     }
  1297 
  1298     renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer));
  1299     if (!renderer) {
  1300         SDL_OutOfMemory();
  1301         return NULL;
  1302     }
  1303 
  1304     // !!! FIXME: MTLCopyAllDevices() can find other GPUs on macOS...
  1305     mtldevice = MTLCreateSystemDefaultDevice();
  1306 
  1307     if (mtldevice == nil) {
  1308         SDL_free(renderer);
  1309         SDL_SetError("Failed to obtain Metal device");
  1310         return NULL;
  1311     }
  1312 
  1313     // !!! FIXME: error checking on all of this.
  1314     data = [[METAL_RenderData alloc] init];
  1315 
  1316     renderer->driverdata = (void*)CFBridgingRetain(data);
  1317     renderer->window = window;
  1318 
  1319 #ifdef __MACOSX__
  1320     NSView *view = Cocoa_Mtl_AddMetalView(window);
  1321     CAMetalLayer *layer = (CAMetalLayer *)[view layer];
  1322 
  1323     layer.device = mtldevice;
  1324 
  1325     //layer.colorspace = nil;
  1326 
  1327 #else
  1328     UIView *view = UIKit_Mtl_AddMetalView(window);
  1329     CAMetalLayer *layer = (CAMetalLayer *)[view layer];
  1330 #endif
  1331 
  1332     // Necessary for RenderReadPixels.
  1333     layer.framebufferOnly = NO;
  1334 
  1335     data.mtldevice = layer.device;
  1336     data.mtllayer = layer;
  1337     id<MTLCommandQueue> mtlcmdqueue = [data.mtldevice newCommandQueue];
  1338     data.mtlcmdqueue = mtlcmdqueue;
  1339     data.mtlcmdqueue.label = @"SDL Metal Renderer";
  1340     data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
  1341 
  1342     NSError *err = nil;
  1343 
  1344     // The compiled .metallib is embedded in a static array in a header file
  1345     // but the original shader source code is in SDL_shaders_metal.metal.
  1346     dispatch_data_t mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{});
  1347     id<MTLLibrary> mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
  1348     data.mtllibrary = mtllibrary;
  1349     SDL_assert(err == nil);
  1350 #if !__has_feature(objc_arc)
  1351     dispatch_release(mtllibdata);
  1352 #endif
  1353     data.mtllibrary.label = @"SDL Metal renderer shader library";
  1354 
  1355     /* Do some shader pipeline state loading up-front rather than on demand. */
  1356     data.pipelinescount = 0;
  1357     data.allpipelines = NULL;
  1358     ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm);
  1359 
  1360     MTLSamplerDescriptor *samplerdesc = [[MTLSamplerDescriptor alloc] init];
  1361 
  1362     samplerdesc.minFilter = MTLSamplerMinMagFilterNearest;
  1363     samplerdesc.magFilter = MTLSamplerMinMagFilterNearest;
  1364     id<MTLSamplerState> mtlsamplernearest = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
  1365     data.mtlsamplernearest = mtlsamplernearest;
  1366 
  1367     samplerdesc.minFilter = MTLSamplerMinMagFilterLinear;
  1368     samplerdesc.magFilter = MTLSamplerMinMagFilterLinear;
  1369     id<MTLSamplerState> mtlsamplerlinear = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
  1370     data.mtlsamplerlinear = mtlsamplerlinear;
  1371 
  1372     /* Note: matrices are column major. */
  1373     float identitytransform[16] = {
  1374         1.0f, 0.0f, 0.0f, 0.0f,
  1375         0.0f, 1.0f, 0.0f, 0.0f,
  1376         0.0f, 0.0f, 1.0f, 0.0f,
  1377         0.0f, 0.0f, 0.0f, 1.0f,
  1378     };
  1379 
  1380     float halfpixeltransform[16] = {
  1381         1.0f, 0.0f, 0.0f, 0.0f,
  1382         0.0f, 1.0f, 0.0f, 0.0f,
  1383         0.0f, 0.0f, 1.0f, 0.0f,
  1384         0.5f, 0.5f, 0.0f, 1.0f,
  1385     };
  1386 
  1387     /* Metal pads float3s to 16 bytes. */
  1388     float decodetransformJPEG[4*4] = {
  1389         0.0, -0.501960814, -0.501960814, 0.0, /* offset */
  1390         1.0000,  0.0000,  1.4020, 0.0,        /* Rcoeff */
  1391         1.0000, -0.3441, -0.7141, 0.0,        /* Gcoeff */
  1392         1.0000,  1.7720,  0.0000, 0.0,        /* Bcoeff */
  1393     };
  1394 
  1395     float decodetransformBT601[4*4] = {
  1396         -0.0627451017, -0.501960814, -0.501960814, 0.0, /* offset */
  1397         1.1644,  0.0000,  1.5960, 0.0,                  /* Rcoeff */
  1398         1.1644, -0.3918, -0.8130, 0.0,                  /* Gcoeff */
  1399         1.1644,  2.0172,  0.0000, 0.0,                  /* Bcoeff */
  1400     };
  1401 
  1402     float decodetransformBT709[4*4] = {
  1403         0.0, -0.501960814, -0.501960814, 0.0, /* offset */
  1404         1.0000,  0.0000,  1.4020, 0.0,        /* Rcoeff */
  1405         1.0000, -0.3441, -0.7141, 0.0,        /* Gcoeff */
  1406         1.0000,  1.7720,  0.0000, 0.0,        /* Bcoeff */
  1407     };
  1408 
  1409     id<MTLBuffer> mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared];
  1410     #if !__has_feature(objc_arc)
  1411     [mtlbufconstantstaging autorelease];
  1412     #endif
  1413     mtlbufconstantstaging.label = @"SDL constant staging data";
  1414 
  1415     id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate];
  1416     data.mtlbufconstants = mtlbufconstants;
  1417     data.mtlbufconstants.label = @"SDL constant data";
  1418 
  1419     char *constantdata = [mtlbufconstantstaging contents];
  1420     SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform));
  1421     SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform));
  1422     SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_JPEG, decodetransformJPEG, sizeof(decodetransformJPEG));
  1423     SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601, decodetransformBT601, sizeof(decodetransformBT601));
  1424     SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709, decodetransformBT709, sizeof(decodetransformBT709));
  1425 
  1426     id<MTLCommandBuffer> cmdbuffer = [data.mtlcmdqueue commandBuffer];
  1427     id<MTLBlitCommandEncoder> blitcmd = [cmdbuffer blitCommandEncoder];
  1428 
  1429     [blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:data.mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH];
  1430 
  1431     [blitcmd endEncoding];
  1432     [cmdbuffer commit];
  1433 
  1434     // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
  1435 
  1436     renderer->WindowEvent = METAL_WindowEvent;
  1437     renderer->GetOutputSize = METAL_GetOutputSize;
  1438     renderer->SupportsBlendMode = METAL_SupportsBlendMode;
  1439     renderer->CreateTexture = METAL_CreateTexture;
  1440     renderer->UpdateTexture = METAL_UpdateTexture;
  1441     renderer->UpdateTextureYUV = METAL_UpdateTextureYUV;
  1442     renderer->LockTexture = METAL_LockTexture;
  1443     renderer->UnlockTexture = METAL_UnlockTexture;
  1444     renderer->SetRenderTarget = METAL_SetRenderTarget;
  1445     renderer->QueueSetViewport = METAL_QueueSetViewport;
  1446     renderer->QueueSetDrawColor = METAL_QueueSetDrawColor;
  1447     renderer->QueueDrawPoints = METAL_QueueDrawPoints;
  1448     renderer->QueueDrawLines = METAL_QueueDrawPoints;  // lines and points queue the same way.
  1449     renderer->QueueFillRects = METAL_QueueFillRects;
  1450     renderer->QueueCopy = METAL_QueueCopy;
  1451     renderer->QueueCopyEx = METAL_QueueCopyEx;
  1452     renderer->RunCommandQueue = METAL_RunCommandQueue;
  1453     renderer->RenderReadPixels = METAL_RenderReadPixels;
  1454     renderer->RenderPresent = METAL_RenderPresent;
  1455     renderer->DestroyTexture = METAL_DestroyTexture;
  1456     renderer->DestroyRenderer = METAL_DestroyRenderer;
  1457     renderer->GetMetalLayer = METAL_GetMetalLayer;
  1458     renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
  1459 
  1460     renderer->info = METAL_RenderDriver.info;
  1461     renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
  1462 
  1463     renderer->always_batch = SDL_TRUE;
  1464 
  1465 #if defined(__MACOSX__) && defined(MAC_OS_X_VERSION_10_13)
  1466     if (@available(macOS 10.13, *)) {
  1467         data.mtllayer.displaySyncEnabled = (flags & SDL_RENDERER_PRESENTVSYNC) != 0;
  1468     } else
  1469 #endif
  1470     {
  1471         renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
  1472     }
  1473 
  1474     /* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
  1475     int maxtexsize = 4096;
  1476 #if defined(__MACOSX__)
  1477     maxtexsize = 16384;
  1478 #elif defined(__TVOS__)
  1479     maxtexsize = 8192;
  1480 #ifdef __TVOS_11_0
  1481     if (@available(tvOS 11.0, *)) {
  1482         if ([mtldevice supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) {
  1483             maxtexsize = 16384;
  1484         }
  1485     }
  1486 #endif
  1487 #else
  1488 #ifdef __IPHONE_11_0
  1489     if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) {
  1490         maxtexsize = 16384;
  1491     } else
  1492 #endif
  1493 #ifdef __IPHONE_10_0
  1494     if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) {
  1495         maxtexsize = 16384;
  1496     } else
  1497 #endif
  1498     if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2] || [mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) {
  1499         maxtexsize = 8192;
  1500     } else {
  1501         maxtexsize = 4096;
  1502     }
  1503 #endif
  1504 
  1505     renderer->info.max_texture_width = maxtexsize;
  1506     renderer->info.max_texture_height = maxtexsize;
  1507 
  1508 #if !__has_feature(objc_arc)
  1509     [mtlcmdqueue release];
  1510     [mtllibrary release];
  1511     [samplerdesc release];
  1512     [mtlsamplernearest release];
  1513     [mtlsamplerlinear release];
  1514     [mtlbufconstants release];
  1515     [view release];
  1516     [data release];
  1517     [mtldevice release];
  1518 #endif
  1519 
  1520     return renderer;
  1521 }}
  1522 
  1523 SDL_RenderDriver METAL_RenderDriver = {
  1524     METAL_CreateRenderer,
  1525     {
  1526         "metal",
  1527         (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
  1528         6,
  1529         {
  1530             SDL_PIXELFORMAT_ARGB8888,
  1531             SDL_PIXELFORMAT_ABGR8888,
  1532             SDL_PIXELFORMAT_YV12,
  1533             SDL_PIXELFORMAT_IYUV,
  1534             SDL_PIXELFORMAT_NV12,
  1535             SDL_PIXELFORMAT_NV21
  1536         },
  1537     0, 0,
  1538     }
  1539 };
  1540 
  1541 #endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */
  1542 
  1543 /* vi: set ts=4 sw=4 expandtab: */