src/render/metal/SDL_render_metal.m
author Alex Szpakowski <slime73@gmail.com>
Thu, 04 Jan 2018 19:29:33 -0400
changeset 11817 5724c41db8fd
parent 11811 5d94cb6b24d3
child 11818 4f3cdd0a5768
permissions -rw-r--r--
metal: Implement fast hardware clearing when possible, by deferring the start of a render pass until a clear or draw operation happens.
     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 #import <Metal/Metal.h>
    37 #import <QuartzCore/CAMetalLayer.h>
    38 
    39 /* Regenerate these with build-metal-shaders.sh */
    40 #ifdef __MACOSX__
    41 #include "SDL_shaders_metal_osx.h"
    42 #else
    43 #include "SDL_shaders_metal_ios.h"
    44 #endif
    45 
    46 /* Apple Metal renderer implementation */
    47 
    48 static SDL_Renderer *METAL_CreateRenderer(SDL_Window * window, Uint32 flags);
    49 static void METAL_WindowEvent(SDL_Renderer * renderer,
    50                            const SDL_WindowEvent *event);
    51 static int METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h);
    52 static SDL_bool METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode);
    53 static int METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture);
    54 static int METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
    55                             const SDL_Rect * rect, const void *pixels,
    56                             int pitch);
    57 static int METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
    58                                const SDL_Rect * rect,
    59                                const Uint8 *Yplane, int Ypitch,
    60                                const Uint8 *Uplane, int Upitch,
    61                                const Uint8 *Vplane, int Vpitch);
    62 static int METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
    63                           const SDL_Rect * rect, void **pixels, int *pitch);
    64 static void METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture);
    65 static int METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture);
    66 static int METAL_UpdateViewport(SDL_Renderer * renderer);
    67 static int METAL_UpdateClipRect(SDL_Renderer * renderer);
    68 static int METAL_RenderClear(SDL_Renderer * renderer);
    69 static int METAL_RenderDrawPoints(SDL_Renderer * renderer,
    70                                const SDL_FPoint * points, int count);
    71 static int METAL_RenderDrawLines(SDL_Renderer * renderer,
    72                               const SDL_FPoint * points, int count);
    73 static int METAL_RenderFillRects(SDL_Renderer * renderer,
    74                               const SDL_FRect * rects, int count);
    75 static int METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
    76                          const SDL_Rect * srcrect, const SDL_FRect * dstrect);
    77 static int METAL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture,
    78                          const SDL_Rect * srcrect, const SDL_FRect * dstrect,
    79                          const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip);
    80 static int METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
    81                                Uint32 pixel_format, void * pixels, int pitch);
    82 static void METAL_RenderPresent(SDL_Renderer * renderer);
    83 static void METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture);
    84 static void METAL_DestroyRenderer(SDL_Renderer * renderer);
    85 static void *METAL_GetMetalLayer(SDL_Renderer * renderer);
    86 static void *METAL_GetMetalCommandEncoder(SDL_Renderer * renderer);
    87 
    88 SDL_RenderDriver METAL_RenderDriver = {
    89     METAL_CreateRenderer,
    90     {
    91      "metal",
    92      (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
    93      2,
    94      {SDL_PIXELFORMAT_ARGB8888, SDL_PIXELFORMAT_ABGR8888},
    95 
    96      // !!! FIXME: how do you query Metal for this?
    97      // (the weakest GPU supported by Metal on iOS has 4k texture max, and
    98      //  other models might be 2x or 4x more. On macOS, it's 16k across the
    99      //  board right now.)
   100 #ifdef __MACOSX__
   101      16384, 16384
   102 #else
   103      4096, 4096
   104 #endif
   105     }
   106 };
   107 
   108 /* macOS requires constants in a buffer to have a 256 byte alignment. */
   109 #ifdef __MACOSX__
   110 #define CONSTANT_ALIGN 256
   111 #else
   112 #define CONSTANT_ALIGN 4
   113 #endif
   114 
   115 #define ALIGN_CONSTANTS(size) ((size + CONSTANT_ALIGN - 1) & (~(CONSTANT_ALIGN - 1)))
   116 
   117 static const size_t CONSTANTS_OFFSET_IDENTITY = 0;
   118 static const size_t CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM = ALIGN_CONSTANTS(CONSTANTS_OFFSET_IDENTITY + sizeof(float) * 16);
   119 static const size_t CONSTANTS_OFFSET_CLEAR_VERTS = ALIGN_CONSTANTS(CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM + sizeof(float) * 16);
   120 static const size_t CONSTANTS_LENGTH = CONSTANTS_OFFSET_CLEAR_VERTS + sizeof(float) * 6;
   121 
   122 typedef enum SDL_MetalVertexFunction
   123 {
   124     SDL_METAL_VERTEX_SOLID,
   125     SDL_METAL_VERTEX_COPY,
   126 } SDL_MetalVertexFunction;
   127 
   128 typedef enum SDL_MetalFragmentFunction
   129 {
   130     SDL_METAL_FRAGMENT_SOLID,
   131     SDL_METAL_FRAGMENT_COPY,
   132 } SDL_MetalFragmentFunction;
   133 
   134 typedef struct METAL_PipelineState
   135 {
   136     SDL_BlendMode blendMode;
   137     void *pipe;
   138 } METAL_PipelineState;
   139 
   140 typedef struct METAL_PipelineCache
   141 {
   142     METAL_PipelineState *states;
   143     int count;
   144     SDL_MetalVertexFunction vertexFunction;
   145     SDL_MetalFragmentFunction fragmentFunction;
   146     const char *label;
   147 } METAL_PipelineCache;
   148 
   149 @interface METAL_RenderData : NSObject
   150     @property (nonatomic, retain) id<MTLDevice> mtldevice;
   151     @property (nonatomic, retain) id<MTLCommandQueue> mtlcmdqueue;
   152     @property (nonatomic, retain) id<MTLCommandBuffer> mtlcmdbuffer;
   153     @property (nonatomic, retain) id<MTLRenderCommandEncoder> mtlcmdencoder;
   154     @property (nonatomic, retain) id<MTLLibrary> mtllibrary;
   155     @property (nonatomic, retain) id<CAMetalDrawable> mtlbackbuffer;
   156     @property (nonatomic, assign) METAL_PipelineCache *mtlpipelineprims;
   157     @property (nonatomic, assign) METAL_PipelineCache *mtlpipelinecopy;
   158     @property (nonatomic, retain) id<MTLSamplerState> mtlsamplernearest;
   159     @property (nonatomic, retain) id<MTLSamplerState> mtlsamplerlinear;
   160     @property (nonatomic, retain) id<MTLBuffer> mtlbufconstants;
   161     @property (nonatomic, retain) CAMetalLayer *mtllayer;
   162     @property (nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
   163 @end
   164 
   165 @implementation METAL_RenderData
   166 #if !__has_feature(objc_arc)
   167 - (void)dealloc
   168 {
   169     [_mtldevice release];
   170     [_mtlcmdqueue release];
   171     [_mtlcmdbuffer release];
   172     [_mtlcmdencoder release];
   173     [_mtllibrary release];
   174     [_mtlbackbuffer release];
   175     [_mtlsamplernearest release];
   176     [_mtlsamplerlinear release];
   177     [_mtlbufconstants release];
   178     [_mtllayer release];
   179     [_mtlpassdesc release];
   180     [super dealloc];
   181 }
   182 #endif
   183 @end
   184 
   185 @interface METAL_TextureData : NSObject
   186     @property (nonatomic, retain) id<MTLTexture> mtltexture;
   187     @property (nonatomic, retain) id<MTLSamplerState> mtlsampler;
   188 @end
   189 
   190 @implementation METAL_TextureData
   191 #if !__has_feature(objc_arc)
   192 - (void)dealloc
   193 {
   194     [_mtltexture release];
   195     [_mtlsampler release];
   196     [super dealloc];
   197 }
   198 #endif
   199 @end
   200 
   201 static int
   202 IsMetalAvailable(const SDL_SysWMinfo *syswm)
   203 {
   204     if (syswm->subsystem != SDL_SYSWM_COCOA && syswm->subsystem != SDL_SYSWM_UIKIT) {
   205         return SDL_SetError("Metal render target only supports Cocoa and UIKit video targets at the moment.");
   206     }
   207 
   208     // this checks a weak symbol.
   209 #if (defined(__MACOSX__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101100))
   210     if (MTLCreateSystemDefaultDevice == NULL) {  // probably on 10.10 or lower.
   211         return SDL_SetError("Metal framework not available on this system");
   212     }
   213 #endif
   214 
   215     return 0;
   216 }
   217 
   218 static const MTLBlendOperation invalidBlendOperation = (MTLBlendOperation)0xFFFFFFFF;
   219 static const MTLBlendFactor invalidBlendFactor = (MTLBlendFactor)0xFFFFFFFF;
   220 
   221 static MTLBlendOperation
   222 GetBlendOperation(SDL_BlendOperation operation)
   223 {
   224     switch (operation) {
   225         case SDL_BLENDOPERATION_ADD: return MTLBlendOperationAdd;
   226         case SDL_BLENDOPERATION_SUBTRACT: return MTLBlendOperationSubtract;
   227         case SDL_BLENDOPERATION_REV_SUBTRACT: return MTLBlendOperationReverseSubtract;
   228         case SDL_BLENDOPERATION_MINIMUM: return MTLBlendOperationMin;
   229         case SDL_BLENDOPERATION_MAXIMUM: return MTLBlendOperationMax;
   230         default: return invalidBlendOperation;
   231     }
   232 }
   233 
   234 static MTLBlendFactor
   235 GetBlendFactor(SDL_BlendFactor factor)
   236 {
   237     switch (factor) {
   238         case SDL_BLENDFACTOR_ZERO: return MTLBlendFactorZero;
   239         case SDL_BLENDFACTOR_ONE: return MTLBlendFactorOne;
   240         case SDL_BLENDFACTOR_SRC_COLOR: return MTLBlendFactorSourceColor;
   241         case SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return MTLBlendFactorOneMinusSourceColor;
   242         case SDL_BLENDFACTOR_SRC_ALPHA: return MTLBlendFactorSourceAlpha;
   243         case SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return MTLBlendFactorOneMinusSourceAlpha;
   244         case SDL_BLENDFACTOR_DST_COLOR: return MTLBlendFactorDestinationColor;
   245         case SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR: return MTLBlendFactorOneMinusDestinationColor;
   246         case SDL_BLENDFACTOR_DST_ALPHA: return MTLBlendFactorDestinationAlpha;
   247         case SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return MTLBlendFactorOneMinusDestinationAlpha;
   248         default: return invalidBlendFactor;
   249     }
   250 }
   251 
   252 static NSString *
   253 GetVertexFunctionName(SDL_MetalVertexFunction function)
   254 {
   255     switch (function) {
   256         case SDL_METAL_VERTEX_SOLID: return @"SDL_Solid_vertex";
   257         case SDL_METAL_VERTEX_COPY: return @"SDL_Copy_vertex";
   258         default: return nil;
   259     }
   260 }
   261 
   262 static NSString *
   263 GetFragmentFunctionName(SDL_MetalFragmentFunction function)
   264 {
   265     switch (function) {
   266         case SDL_METAL_FRAGMENT_SOLID: return @"SDL_Solid_fragment";
   267         case SDL_METAL_FRAGMENT_COPY: return @"SDL_Copy_fragment";
   268         default: return nil;
   269     }
   270 }
   271 
   272 static id<MTLRenderPipelineState>
   273 MakePipelineState(METAL_RenderData *data, METAL_PipelineCache *cache,
   274                   NSString *blendlabel, SDL_BlendMode blendmode)
   275 {
   276     id<MTLFunction> mtlvertfn = [data.mtllibrary newFunctionWithName:GetVertexFunctionName(cache->vertexFunction)];
   277     id<MTLFunction> mtlfragfn = [data.mtllibrary newFunctionWithName:GetFragmentFunctionName(cache->fragmentFunction)];
   278     SDL_assert(mtlvertfn != nil);
   279     SDL_assert(mtlfragfn != nil);
   280 
   281     MTLRenderPipelineDescriptor *mtlpipedesc = [[MTLRenderPipelineDescriptor alloc] init];
   282     mtlpipedesc.vertexFunction = mtlvertfn;
   283     mtlpipedesc.fragmentFunction = mtlfragfn;
   284 
   285     MTLRenderPipelineColorAttachmentDescriptor *rtdesc = mtlpipedesc.colorAttachments[0];
   286 
   287     // !!! FIXME: This should be part of the pipeline state cache.
   288     rtdesc.pixelFormat = data.mtllayer.pixelFormat;
   289 
   290     if (blendmode != SDL_BLENDMODE_NONE) {
   291         rtdesc.blendingEnabled = YES;
   292         rtdesc.sourceRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcColorFactor(blendmode));
   293         rtdesc.destinationRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeDstColorFactor(blendmode));
   294         rtdesc.rgbBlendOperation = GetBlendOperation(SDL_GetBlendModeColorOperation(blendmode));
   295         rtdesc.sourceAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcAlphaFactor(blendmode));
   296         rtdesc.destinationAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeDstAlphaFactor(blendmode));
   297         rtdesc.alphaBlendOperation = GetBlendOperation(SDL_GetBlendModeAlphaOperation(blendmode));
   298     } else {
   299         rtdesc.blendingEnabled = NO;
   300     }
   301 
   302     mtlpipedesc.label = [@(cache->label) stringByAppendingString:blendlabel];
   303 
   304     NSError *err = nil;
   305     id<MTLRenderPipelineState> state = [data.mtldevice newRenderPipelineStateWithDescriptor:mtlpipedesc error:&err];
   306     SDL_assert(err == nil);
   307 
   308     METAL_PipelineState pipeline;
   309     pipeline.blendMode = blendmode;
   310     pipeline.pipe = (void *)CFBridgingRetain(state);
   311 
   312     METAL_PipelineState *states = SDL_realloc(cache->states, (cache->count + 1) * sizeof(pipeline));
   313 
   314 #if !__has_feature(objc_arc)
   315     [mtlpipedesc release];  // !!! FIXME: can these be reused for each creation, or does the pipeline obtain it?
   316     [mtlvertfn release];
   317     [mtlfragfn release];
   318     [state release];
   319 #endif
   320 
   321     if (states) {
   322         states[cache->count++] = pipeline;
   323         cache->states = states;
   324         return (__bridge id<MTLRenderPipelineState>)pipeline.pipe;
   325     } else {
   326         CFBridgingRelease(pipeline.pipe);
   327         SDL_OutOfMemory();
   328         return NULL;
   329     }
   330 }
   331 
   332 static METAL_PipelineCache *
   333 MakePipelineCache(METAL_RenderData *data, const char *label, SDL_MetalVertexFunction vertfn, SDL_MetalFragmentFunction fragfn)
   334 {
   335     METAL_PipelineCache *cache = SDL_malloc(sizeof(METAL_PipelineCache));
   336 
   337     if (!cache) {
   338         SDL_OutOfMemory();
   339         return NULL;
   340     }
   341 
   342     SDL_zerop(cache);
   343 
   344     cache->vertexFunction = vertfn;
   345     cache->fragmentFunction = fragfn;
   346     cache->label = label;
   347 
   348     /* Create pipeline states for the default blend modes. Custom blend modes
   349      * will be added to the cache on-demand. */
   350     MakePipelineState(data, cache, @"(blend=none)", SDL_BLENDMODE_NONE);
   351     MakePipelineState(data, cache, @"(blend=blend)", SDL_BLENDMODE_BLEND);
   352     MakePipelineState(data, cache, @"(blend=add)", SDL_BLENDMODE_ADD);
   353     MakePipelineState(data, cache, @"(blend=mod)", SDL_BLENDMODE_MOD);
   354 
   355     return cache;
   356 }
   357 
   358 static void
   359 DestroyPipelineCache(METAL_PipelineCache *cache)
   360 {
   361     if (cache != NULL) {
   362         for (int i = 0; i < cache->count; i++) {
   363             CFBridgingRelease(cache->states[i].pipe);
   364         }
   365 
   366         SDL_free(cache->states);
   367         SDL_free(cache);
   368     }
   369 }
   370 
   371 static inline id<MTLRenderPipelineState>
   372 ChoosePipelineState(METAL_RenderData *data, METAL_PipelineCache *cache, const SDL_BlendMode blendmode)
   373 {
   374     for (int i = 0; i < cache->count; i++) {
   375         if (cache->states[i].blendMode == blendmode) {
   376             return (__bridge id<MTLRenderPipelineState>)cache->states[i].pipe;
   377         }
   378     }
   379 
   380     return MakePipelineState(data, cache, [NSString stringWithFormat:@"(blend=custom 0x%x)", blendmode], blendmode);
   381 }
   382 
   383 static SDL_Renderer *
   384 METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
   385 {
   386     SDL_Renderer *renderer = NULL;
   387     METAL_RenderData *data = NULL;
   388     SDL_SysWMinfo syswm;
   389 
   390     SDL_VERSION(&syswm.version);
   391     if (!SDL_GetWindowWMInfo(window, &syswm)) {
   392         return NULL;
   393     }
   394 
   395     if (IsMetalAvailable(&syswm) == -1) {
   396         return NULL;
   397     }
   398 
   399     renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer));
   400     if (!renderer) {
   401         SDL_OutOfMemory();
   402         return NULL;
   403     }
   404 
   405     data = [[METAL_RenderData alloc] init];
   406 
   407     renderer->driverdata = (void*)CFBridgingRetain(data);
   408     renderer->window = window;
   409 
   410 #ifdef __MACOSX__
   411     id<MTLDevice> mtldevice = MTLCreateSystemDefaultDevice();  // !!! FIXME: MTLCopyAllDevices() can find other GPUs...
   412     if (mtldevice == nil) {
   413         SDL_free(renderer);
   414 #if !__has_feature(objc_arc)
   415         [data release];
   416 #endif
   417         SDL_SetError("Failed to obtain Metal device");
   418         return NULL;
   419     }
   420 
   421     // !!! FIXME: error checking on all of this.
   422 
   423     NSView *view = Cocoa_Mtl_AddMetalView(window);
   424     CAMetalLayer *layer = (CAMetalLayer *)[view layer];
   425 
   426     layer.device = mtldevice;
   427 
   428     //layer.colorspace = nil;
   429 
   430 #else
   431     UIView *view = UIKit_Mtl_AddMetalView(window);
   432     CAMetalLayer *layer = (CAMetalLayer *)[view layer];
   433 #endif
   434 
   435     // Necessary for RenderReadPixels.
   436     layer.framebufferOnly = NO;
   437 
   438     data.mtldevice = layer.device;
   439     data.mtllayer = layer;
   440     id<MTLCommandQueue> mtlcmdqueue = [data.mtldevice newCommandQueue];
   441     data.mtlcmdqueue = mtlcmdqueue;
   442     data.mtlcmdqueue.label = @"SDL Metal Renderer";
   443     data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
   444 
   445     NSError *err = nil;
   446 
   447     // The compiled .metallib is embedded in a static array in a header file
   448     // but the original shader source code is in SDL_shaders_metal.metal.
   449     dispatch_data_t mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{});
   450     id<MTLLibrary> mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
   451     data.mtllibrary = mtllibrary;
   452     SDL_assert(err == nil);
   453 #if !__has_feature(objc_arc)
   454     dispatch_release(mtllibdata);
   455 #endif
   456     data.mtllibrary.label = @"SDL Metal renderer shader library";
   457 
   458     data.mtlpipelineprims = MakePipelineCache(data, "SDL primitives pipeline ", SDL_METAL_VERTEX_SOLID, SDL_METAL_FRAGMENT_SOLID);
   459     data.mtlpipelinecopy = MakePipelineCache(data, "SDL texture pipeline ", SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_COPY);
   460 
   461     MTLSamplerDescriptor *samplerdesc = [[MTLSamplerDescriptor alloc] init];
   462 
   463     samplerdesc.minFilter = MTLSamplerMinMagFilterNearest;
   464     samplerdesc.magFilter = MTLSamplerMinMagFilterNearest;
   465     id<MTLSamplerState> mtlsamplernearest = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
   466     data.mtlsamplernearest = mtlsamplernearest;
   467 
   468     samplerdesc.minFilter = MTLSamplerMinMagFilterLinear;
   469     samplerdesc.magFilter = MTLSamplerMinMagFilterLinear;
   470     id<MTLSamplerState> mtlsamplerlinear = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
   471     data.mtlsamplerlinear = mtlsamplerlinear;
   472 
   473     /* Note: matrices are column major. */
   474     float identitytransform[16] = {
   475         1.0f, 0.0f, 0.0f, 0.0f,
   476         0.0f, 1.0f, 0.0f, 0.0f,
   477         0.0f, 0.0f, 1.0f, 0.0f,
   478         0.0f, 0.0f, 0.0f, 1.0f,
   479     };
   480 
   481     float halfpixeltransform[16] = {
   482         1.0f, 0.0f, 0.0f, 0.0f,
   483         0.0f, 1.0f, 0.0f, 0.0f,
   484         0.0f, 0.0f, 1.0f, 0.0f,
   485         0.5f, 0.5f, 0.0f, 1.0f,
   486     };
   487 
   488     float clearverts[6] = {0.0f, 0.0f,  0.0f, 2.0f,  2.0f, 0.0f};
   489 
   490     MTLResourceOptions constantsopts = 0;
   491 #ifdef __MACOSX__
   492     constantsopts |= MTLResourceStorageModeManaged;
   493 #endif
   494 
   495     id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:constantsopts];
   496     data.mtlbufconstants = mtlbufconstants;
   497     data.mtlbufconstants.label = @"SDL constant data";
   498 
   499     char *constantdata = [data.mtlbufconstants contents];
   500     SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform));
   501     SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform));
   502     SDL_memcpy(constantdata + CONSTANTS_OFFSET_CLEAR_VERTS, clearverts, sizeof(clearverts));
   503 #ifdef __MACOSX__
   504     [data.mtlbufconstants didModifyRange:NSMakeRange(0, CONSTANTS_LENGTH)];
   505 #endif
   506 
   507     // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
   508 
   509     renderer->WindowEvent = METAL_WindowEvent;
   510     renderer->GetOutputSize = METAL_GetOutputSize;
   511     renderer->SupportsBlendMode = METAL_SupportsBlendMode;
   512     renderer->CreateTexture = METAL_CreateTexture;
   513     renderer->UpdateTexture = METAL_UpdateTexture;
   514     renderer->UpdateTextureYUV = METAL_UpdateTextureYUV;
   515     renderer->LockTexture = METAL_LockTexture;
   516     renderer->UnlockTexture = METAL_UnlockTexture;
   517     renderer->SetRenderTarget = METAL_SetRenderTarget;
   518     renderer->UpdateViewport = METAL_UpdateViewport;
   519     renderer->UpdateClipRect = METAL_UpdateClipRect;
   520     renderer->RenderClear = METAL_RenderClear;
   521     renderer->RenderDrawPoints = METAL_RenderDrawPoints;
   522     renderer->RenderDrawLines = METAL_RenderDrawLines;
   523     renderer->RenderFillRects = METAL_RenderFillRects;
   524     renderer->RenderCopy = METAL_RenderCopy;
   525     renderer->RenderCopyEx = METAL_RenderCopyEx;
   526     renderer->RenderReadPixels = METAL_RenderReadPixels;
   527     renderer->RenderPresent = METAL_RenderPresent;
   528     renderer->DestroyTexture = METAL_DestroyTexture;
   529     renderer->DestroyRenderer = METAL_DestroyRenderer;
   530     renderer->GetMetalLayer = METAL_GetMetalLayer;
   531     renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
   532 
   533     renderer->info = METAL_RenderDriver.info;
   534     renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
   535 
   536 #if defined(__MACOSX__) && defined(MAC_OS_X_VERSION_10_13)
   537     if (@available(macOS 10.13, *)) {
   538         data.mtllayer.displaySyncEnabled = (flags & SDL_RENDERER_PRESENTVSYNC) != 0;
   539     } else
   540 #endif
   541     {
   542         renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
   543     }
   544 
   545 #if !__has_feature(objc_arc)
   546     [mtlcmdqueue release];
   547     [mtllibrary release];
   548     [samplerdesc release];
   549     [mtlsamplernearest release];
   550     [mtlsamplerlinear release];
   551     [mtlbufconstants release];
   552     [view release];
   553     [data release];
   554 #ifdef __MACOSX__
   555     [mtldevice release];
   556 #endif
   557 #endif
   558 
   559     return renderer;
   560 }
   561 
   562 static void
   563 METAL_ActivateRenderCommandEncoder(SDL_Renderer * renderer, MTLLoadAction load)
   564 {
   565     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   566 
   567     /* Our SetRenderTarget just signals that the next render operation should
   568      * set up a new render pass. This is where that work happens. */
   569     if (data.mtlcmdencoder == nil) {
   570         id<MTLTexture> mtltexture = nil;
   571 
   572         if (renderer->target != NULL) {
   573             METAL_TextureData *texdata = (__bridge METAL_TextureData *)renderer->target->driverdata;
   574             mtltexture = texdata.mtltexture;
   575         } else {
   576             if (data.mtlbackbuffer == nil) {
   577                 /* The backbuffer's contents aren't guaranteed to persist after
   578                  * presenting, so we can leave it undefined when loading it. */
   579                 data.mtlbackbuffer = [data.mtllayer nextDrawable];
   580                 if (load == MTLLoadActionLoad) {
   581                     load = MTLLoadActionDontCare;
   582                 }
   583             }
   584             mtltexture = data.mtlbackbuffer.texture;
   585         }
   586 
   587         SDL_assert(mtltexture);
   588 
   589         if (load == MTLLoadActionClear) {
   590             MTLClearColor color = MTLClearColorMake(renderer->r/255.0, renderer->g/255.0, renderer->b/255.0, renderer->a/255.0);
   591             data.mtlpassdesc.colorAttachments[0].clearColor = color;
   592         }
   593 
   594         data.mtlpassdesc.colorAttachments[0].loadAction = load;
   595         data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
   596 
   597         data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
   598         data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
   599 
   600         if (data.mtlbackbuffer != nil && mtltexture == data.mtlbackbuffer.texture) {
   601             data.mtlcmdencoder.label = @"SDL metal renderer backbuffer";
   602         } else {
   603             data.mtlcmdencoder.label = @"SDL metal renderer render target";
   604         }
   605 
   606         /* Make sure the viewport and clip rect are set on the new render pass. */
   607         METAL_UpdateViewport(renderer);
   608         METAL_UpdateClipRect(renderer);
   609     }
   610 }
   611 
   612 static void
   613 METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
   614 {
   615     if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
   616         event->event == SDL_WINDOWEVENT_SHOWN ||
   617         event->event == SDL_WINDOWEVENT_HIDDEN) {
   618         // !!! FIXME: write me
   619     }
   620 }
   621 
   622 static int
   623 METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
   624 { @autoreleasepool {
   625     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   626     if (w) {
   627         *w = (int)data.mtllayer.drawableSize.width;
   628     }
   629     if (h) {
   630         *h = (int)data.mtllayer.drawableSize.height;
   631     }
   632     return 0;
   633 }}
   634 
   635 static SDL_bool
   636 METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode)
   637 {
   638     SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode);
   639     SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode);
   640     SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode);
   641     SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode);
   642     SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode);
   643     SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode);
   644 
   645     if (GetBlendFactor(srcColorFactor) == invalidBlendFactor ||
   646         GetBlendFactor(srcAlphaFactor) == invalidBlendFactor ||
   647         GetBlendOperation(colorOperation) == invalidBlendOperation ||
   648         GetBlendFactor(dstColorFactor) == invalidBlendFactor ||
   649         GetBlendFactor(dstAlphaFactor) == invalidBlendFactor ||
   650         GetBlendOperation(alphaOperation) == invalidBlendOperation) {
   651         return SDL_FALSE;
   652     }
   653     return SDL_TRUE;
   654 }
   655 
   656 static int
   657 METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
   658 { @autoreleasepool {
   659     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   660     MTLPixelFormat mtlpixfmt;
   661 
   662     switch (texture->format) {
   663         case SDL_PIXELFORMAT_ABGR8888: mtlpixfmt = MTLPixelFormatRGBA8Unorm; break;
   664         case SDL_PIXELFORMAT_ARGB8888: mtlpixfmt = MTLPixelFormatBGRA8Unorm; break;
   665         default: return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
   666     }
   667 
   668     MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:mtlpixfmt
   669                                             width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO];
   670 
   671     /* Not available in iOS 8. */
   672     if ([mtltexdesc respondsToSelector:@selector(usage)]) {
   673         if (texture->access == SDL_TEXTUREACCESS_TARGET) {
   674             mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
   675         } else {
   676             mtltexdesc.usage = MTLTextureUsageShaderRead;
   677         }
   678     }
   679     //mtltexdesc.resourceOptions = MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeManaged;
   680     //mtltexdesc.storageMode = MTLStorageModeManaged;
   681     
   682     id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
   683     if (mtltexture == nil) {
   684         return SDL_SetError("Texture allocation failed");
   685     }
   686 
   687     METAL_TextureData *texturedata = [[METAL_TextureData alloc] init];
   688     const char *hint = SDL_GetHint(SDL_HINT_RENDER_SCALE_QUALITY);
   689     if (!hint || *hint == '0' || SDL_strcasecmp(hint, "nearest") == 0) {
   690         texturedata.mtlsampler = data.mtlsamplernearest;
   691     } else {
   692         texturedata.mtlsampler = data.mtlsamplerlinear;
   693     }
   694     texturedata.mtltexture = mtltexture;
   695 
   696     texture->driverdata = (void*)CFBridgingRetain(texturedata);
   697 
   698 #if !__has_feature(objc_arc)
   699     [texturedata release];
   700     [mtltexture release];
   701 #endif
   702 
   703     return 0;
   704 }}
   705 
   706 static int
   707 METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
   708                  const SDL_Rect * rect, const void *pixels, int pitch)
   709 { @autoreleasepool {
   710     // !!! FIXME: this is a synchronous call; it doesn't return until data is uploaded in some form.
   711     // !!! FIXME:  Maybe move this off to a thread that marks the texture as uploaded and only stall the main thread if we try to
   712     // !!! FIXME:  use this texture before the marking is done? Is it worth it? Or will we basically always be uploading a bunch of
   713     // !!! FIXME:  stuff way ahead of time and/or using it immediately after upload?
   714     id<MTLTexture> mtltexture = ((__bridge METAL_TextureData *)texture->driverdata).mtltexture;
   715     [mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h) mipmapLevel:0 withBytes:pixels bytesPerRow:pitch];
   716     return 0;
   717 }}
   718 
   719 static int
   720 METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
   721                     const SDL_Rect * rect,
   722                     const Uint8 *Yplane, int Ypitch,
   723                     const Uint8 *Uplane, int Upitch,
   724                     const Uint8 *Vplane, int Vpitch)
   725 {
   726     return SDL_Unsupported();  // !!! FIXME
   727 }
   728 
   729 static int
   730 METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
   731                const SDL_Rect * rect, void **pixels, int *pitch)
   732 {
   733     return SDL_Unsupported();   // !!! FIXME: write me
   734 }
   735 
   736 static void
   737 METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture)
   738 {
   739     // !!! FIXME: write me
   740 }
   741 
   742 static int
   743 METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
   744 { @autoreleasepool {
   745     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   746 
   747     if (data.mtlcmdencoder) {
   748         /* End encoding for the previous render target so we can set up a new
   749          * render pass for this one. */
   750         [data.mtlcmdencoder endEncoding];
   751         [data.mtlcmdbuffer commit];
   752 
   753         data.mtlcmdencoder = nil;
   754         data.mtlcmdbuffer = nil;
   755     }
   756 
   757     /* We don't begin a new render pass right away - we delay it until an actual
   758      * draw or clear happens. That way we can use hardware clears when possible,
   759      * which are only available when beginning a new render pass. */
   760     return 0;
   761 }}
   762 
   763 static int
   764 METAL_SetOrthographicProjection(SDL_Renderer *renderer, int w, int h)
   765 { @autoreleasepool {
   766     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   767     float projection[4][4];
   768 
   769     if (!w || !h) {
   770         return 0;
   771     }
   772 
   773     /* Prepare an orthographic projection */
   774     projection[0][0] = 2.0f / w;
   775     projection[0][1] = 0.0f;
   776     projection[0][2] = 0.0f;
   777     projection[0][3] = 0.0f;
   778     projection[1][0] = 0.0f;
   779     projection[1][1] = -2.0f / h;
   780     projection[1][2] = 0.0f;
   781     projection[1][3] = 0.0f;
   782     projection[2][0] = 0.0f;
   783     projection[2][1] = 0.0f;
   784     projection[2][2] = 0.0f;
   785     projection[2][3] = 0.0f;
   786     projection[3][0] = -1.0f;
   787     projection[3][1] = 1.0f;
   788     projection[3][2] = 0.0f;
   789     projection[3][3] = 1.0f;
   790 
   791     // !!! FIXME: This should be in a buffer...
   792     [data.mtlcmdencoder setVertexBytes:projection length:sizeof(float)*16 atIndex:2];
   793     return 0;
   794 }}
   795 
   796 static int
   797 METAL_UpdateViewport(SDL_Renderer * renderer)
   798 { @autoreleasepool {
   799     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   800     if (data.mtlcmdencoder) {
   801         MTLViewport viewport;
   802         viewport.originX = renderer->viewport.x;
   803         viewport.originY = renderer->viewport.y;
   804         viewport.width = renderer->viewport.w;
   805         viewport.height = renderer->viewport.h;
   806         viewport.znear = 0.0;
   807         viewport.zfar = 1.0;
   808         [data.mtlcmdencoder setViewport:viewport];
   809         METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h);
   810     }
   811     return 0;
   812 }}
   813 
   814 static int
   815 METAL_UpdateClipRect(SDL_Renderer * renderer)
   816 { @autoreleasepool {
   817     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   818     if (data.mtlcmdencoder) {
   819         MTLScissorRect mtlrect;
   820         // !!! FIXME: should this care about the viewport?
   821         if (renderer->clipping_enabled) {
   822             const SDL_Rect *rect = &renderer->clip_rect;
   823             mtlrect.x = renderer->viewport.x + rect->x;
   824             mtlrect.y = renderer->viewport.x + rect->y;
   825             mtlrect.width = rect->w;
   826             mtlrect.height = rect->h;
   827         } else {
   828             mtlrect.x = renderer->viewport.x;
   829             mtlrect.y = renderer->viewport.y;
   830             mtlrect.width = renderer->viewport.w;
   831             mtlrect.height = renderer->viewport.h;
   832         }
   833         if (mtlrect.width > 0 && mtlrect.height > 0) {
   834             [data.mtlcmdencoder setScissorRect:mtlrect];
   835         }
   836     }
   837     return 0;
   838 }}
   839 
   840 static int
   841 METAL_RenderClear(SDL_Renderer * renderer)
   842 { @autoreleasepool {
   843     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   844 
   845     /* Since we set up the render command encoder lazily when a draw is
   846      * requested, we can do the fast path hardware clear if no draws have
   847      * happened since the last SetRenderTarget. */
   848     if (data.mtlcmdencoder == nil) {
   849         METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear);
   850     } else {
   851         // !!! FIXME: render color should live in a dedicated uniform buffer.
   852         const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   853 
   854         MTLViewport viewport;  // RenderClear ignores the viewport state, though, so reset that.
   855         viewport.originX = viewport.originY = 0.0;
   856         viewport.width = data.mtlpassdesc.colorAttachments[0].texture.width;
   857         viewport.height = data.mtlpassdesc.colorAttachments[0].texture.height;
   858         viewport.znear = 0.0;
   859         viewport.zfar = 1.0;
   860 
   861         // Slow path for clearing: draw a filled fullscreen triangle.
   862         METAL_SetOrthographicProjection(renderer, 1, 1);
   863         [data.mtlcmdencoder setViewport:viewport];
   864         [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelineprims, SDL_BLENDMODE_NONE)];
   865         [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_CLEAR_VERTS atIndex:0];
   866         [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3];
   867         [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   868         [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
   869 
   870         // reset the viewport for the rest of our usual drawing work...
   871         viewport.originX = renderer->viewport.x;
   872         viewport.originY = renderer->viewport.y;
   873         viewport.width = renderer->viewport.w;
   874         viewport.height = renderer->viewport.h;
   875         viewport.znear = 0.0;
   876         viewport.zfar = 1.0;
   877         [data.mtlcmdencoder setViewport:viewport];
   878         METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h);
   879     }
   880 
   881     return 0;
   882 }}
   883 
   884 // normalize a value from 0.0f to len into 0.0f to 1.0f.
   885 static inline float
   886 normtex(const float _val, const float len)
   887 {
   888     return _val / len;
   889 }
   890 
   891 static int
   892 DrawVerts(SDL_Renderer * renderer, const SDL_FPoint * points, int count,
   893           const MTLPrimitiveType primtype)
   894 { @autoreleasepool {
   895     METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad);
   896 
   897     const size_t vertlen = (sizeof (float) * 2) * count;
   898     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   899 
   900     // !!! FIXME: render color should live in a dedicated uniform buffer.
   901     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   902 
   903     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelineprims, renderer->blendMode)];
   904     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   905 
   906     [data.mtlcmdencoder setVertexBytes:points length:vertlen atIndex:0];
   907     [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM atIndex:3];
   908     [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
   909 
   910     return 0;
   911 }}
   912 
   913 static int
   914 METAL_RenderDrawPoints(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
   915 {
   916     return DrawVerts(renderer, points, count, MTLPrimitiveTypePoint);
   917 }
   918 
   919 static int
   920 METAL_RenderDrawLines(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
   921 {
   922     return DrawVerts(renderer, points, count, MTLPrimitiveTypeLineStrip);
   923 }
   924 
   925 static int
   926 METAL_RenderFillRects(SDL_Renderer * renderer, const SDL_FRect * rects, int count)
   927 { @autoreleasepool {
   928     METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad);
   929     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   930 
   931     // !!! FIXME: render color should live in a dedicated uniform buffer.
   932     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   933 
   934     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelineprims, renderer->blendMode)];
   935     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   936     [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3];
   937 
   938     for (int i = 0; i < count; i++, rects++) {
   939         if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) continue;
   940 
   941         const float verts[] = {
   942             rects->x, rects->y + rects->h,
   943             rects->x, rects->y,
   944             rects->x + rects->w, rects->y + rects->h,
   945             rects->x + rects->w, rects->y
   946         };
   947 
   948         [data.mtlcmdencoder setVertexBytes:verts length:sizeof(verts) atIndex:0];
   949         [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
   950     }
   951 
   952     return 0;
   953 }}
   954 
   955 static int
   956 METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
   957               const SDL_Rect * srcrect, const SDL_FRect * dstrect)
   958 { @autoreleasepool {
   959     METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad);
   960     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   961     METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
   962     const float texw = (float) texturedata.mtltexture.width;
   963     const float texh = (float) texturedata.mtltexture.height;
   964 
   965     const float xy[] = {
   966         dstrect->x, dstrect->y + dstrect->h,
   967         dstrect->x, dstrect->y,
   968         dstrect->x + dstrect->w, dstrect->y + dstrect->h,
   969         dstrect->x + dstrect->w, dstrect->y
   970     };
   971 
   972     const float uv[] = {
   973         normtex(srcrect->x, texw), normtex(srcrect->y + srcrect->h, texh),
   974         normtex(srcrect->x, texw), normtex(srcrect->y, texh),
   975         normtex(srcrect->x + srcrect->w, texw), normtex(srcrect->y + srcrect->h, texh),
   976         normtex(srcrect->x + srcrect->w, texw), normtex(srcrect->y, texh)
   977     };
   978 
   979     float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
   980     if (texture->modMode) {
   981         color[0] = ((float)texture->r) / 255.0f;
   982         color[1] = ((float)texture->g) / 255.0f;
   983         color[2] = ((float)texture->b) / 255.0f;
   984         color[3] = ((float)texture->a) / 255.0f;
   985     }
   986 
   987     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelinecopy, texture->blendMode)];
   988     [data.mtlcmdencoder setVertexBytes:xy length:sizeof(xy) atIndex:0];
   989     [data.mtlcmdencoder setVertexBytes:uv length:sizeof(uv) atIndex:1];
   990     [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3];
   991     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   992     [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0];
   993     [data.mtlcmdencoder setFragmentSamplerState:texturedata.mtlsampler atIndex:0];
   994     [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
   995 
   996     return 0;
   997 }}
   998 
   999 static int
  1000 METAL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture,
  1001               const SDL_Rect * srcrect, const SDL_FRect * dstrect,
  1002               const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
  1003 { @autoreleasepool {
  1004     METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad);
  1005     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1006     METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
  1007     const float texw = (float) texturedata.mtltexture.width;
  1008     const float texh = (float) texturedata.mtltexture.height;
  1009     float transform[16];
  1010     float minu, maxu, minv, maxv;
  1011 
  1012     minu = normtex(srcrect->x, texw);
  1013     maxu = normtex(srcrect->x + srcrect->w, texw);
  1014     minv = normtex(srcrect->y, texh);
  1015     maxv = normtex(srcrect->y + srcrect->h, texh);
  1016 
  1017     if (flip & SDL_FLIP_HORIZONTAL) {
  1018         float tmp = maxu;
  1019         maxu = minu;
  1020         minu = tmp;
  1021     }
  1022     if (flip & SDL_FLIP_VERTICAL) {
  1023         float tmp = maxv;
  1024         maxv = minv;
  1025         minv = tmp;
  1026     }
  1027 
  1028     const float uv[] = {
  1029         minu, maxv,
  1030         minu, minv,
  1031         maxu, maxv,
  1032         maxu, minv
  1033     };
  1034 
  1035     const float xy[] = {
  1036         -center->x, dstrect->h - center->y,
  1037         -center->x, -center->y,
  1038         dstrect->w - center->x, dstrect->h - center->y,
  1039         dstrect->w - center->x, -center->y
  1040     };
  1041 
  1042     {
  1043         float rads = (float)(M_PI * (float) angle / 180.0f);
  1044         float c = cosf(rads), s = sinf(rads);
  1045         SDL_memset(transform, 0, sizeof(transform));
  1046 
  1047         // matrix multiplication carried out on paper:
  1048         // |1     x+c| |c -s    |
  1049         // |  1   y+c| |s  c    |
  1050         // |    1    | |     1  |
  1051         // |        1| |       1|
  1052         //     move      rotate
  1053         transform[10] = transform[15] = 1.0f;
  1054         transform[0]  = c;
  1055         transform[1]  = s;
  1056         transform[4]  = -s;
  1057         transform[5]  = c;
  1058         transform[12] = dstrect->x + center->x;
  1059         transform[13] = dstrect->y + center->y;
  1060     }
  1061 
  1062     float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
  1063     if (texture->modMode) {
  1064         color[0] = ((float)texture->r) / 255.0f;
  1065         color[1] = ((float)texture->g) / 255.0f;
  1066         color[2] = ((float)texture->b) / 255.0f;
  1067         color[3] = ((float)texture->a) / 255.0f;
  1068     }
  1069 
  1070     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelinecopy, texture->blendMode)];
  1071     [data.mtlcmdencoder setVertexBytes:xy length:sizeof(xy) atIndex:0];
  1072     [data.mtlcmdencoder setVertexBytes:uv length:sizeof(uv) atIndex:1];
  1073     [data.mtlcmdencoder setVertexBytes:transform length:sizeof(transform) atIndex:3];
  1074     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
  1075     [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0];
  1076     [data.mtlcmdencoder setFragmentSamplerState:texturedata.mtlsampler atIndex:0];
  1077     [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
  1078 
  1079     return 0;
  1080 }}
  1081 
  1082 static int
  1083 METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
  1084                     Uint32 pixel_format, void * pixels, int pitch)
  1085 { @autoreleasepool {
  1086     METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad);
  1087 
  1088     // !!! FIXME: this probably needs to commit the current command buffer, and probably waitUntilCompleted
  1089     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1090     id<MTLTexture> mtltexture = data.mtlpassdesc.colorAttachments[0].texture;
  1091     MTLRegion mtlregion = MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h);
  1092 
  1093     // we only do BGRA8 or RGBA8 at the moment, so 4 will do.
  1094     const int temp_pitch = rect->w * 4;
  1095     void *temp_pixels = SDL_malloc(temp_pitch * rect->h);
  1096     if (!temp_pixels) {
  1097         return SDL_OutOfMemory();
  1098     }
  1099 
  1100     [mtltexture getBytes:temp_pixels bytesPerRow:temp_pitch fromRegion:mtlregion mipmapLevel:0];
  1101 
  1102     const Uint32 temp_format = (mtltexture.pixelFormat == MTLPixelFormatBGRA8Unorm) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_ABGR8888;
  1103     const int status = SDL_ConvertPixels(rect->w, rect->h, temp_format, temp_pixels, temp_pitch, pixel_format, pixels, pitch);
  1104     SDL_free(temp_pixels);
  1105     return status;
  1106 }}
  1107 
  1108 static void
  1109 METAL_RenderPresent(SDL_Renderer * renderer)
  1110 { @autoreleasepool {
  1111     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1112 
  1113     if (data.mtlcmdencoder != nil) {
  1114         [data.mtlcmdencoder endEncoding];
  1115     }
  1116     if (data.mtlbackbuffer != nil) {
  1117         [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
  1118     }
  1119     if (data.mtlcmdbuffer != nil) {
  1120         [data.mtlcmdbuffer commit];
  1121     }
  1122     data.mtlcmdencoder = nil;
  1123     data.mtlcmdbuffer = nil;
  1124     data.mtlbackbuffer = nil;
  1125 }}
  1126 
  1127 static void
  1128 METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture)
  1129 { @autoreleasepool {
  1130     CFBridgingRelease(texture->driverdata);
  1131     texture->driverdata = NULL;
  1132 }}
  1133 
  1134 static void
  1135 METAL_DestroyRenderer(SDL_Renderer * renderer)
  1136 { @autoreleasepool {
  1137     if (renderer->driverdata) {
  1138         METAL_RenderData *data = CFBridgingRelease(renderer->driverdata);
  1139 
  1140         if (data.mtlcmdencoder != nil) {
  1141             [data.mtlcmdencoder endEncoding];
  1142         }
  1143 
  1144         DestroyPipelineCache(data.mtlpipelineprims);
  1145         DestroyPipelineCache(data.mtlpipelinecopy);
  1146     }
  1147 
  1148     SDL_free(renderer);
  1149 }}
  1150 
  1151 static void *
  1152 METAL_GetMetalLayer(SDL_Renderer * renderer)
  1153 { @autoreleasepool {
  1154     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1155     return (__bridge void*)data.mtllayer;
  1156 }}
  1157 
  1158 static void *
  1159 METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
  1160 { @autoreleasepool {
  1161     METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad);
  1162     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
  1163     return (__bridge void*)data.mtlcmdencoder;
  1164 }}
  1165 
  1166 #endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */
  1167 
  1168 /* vi: set ts=4 sw=4 expandtab: */