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