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