src/render/metal/SDL_render_metal.m
author Sam Lantinga <slouken@libsdl.org>
Sat, 09 Dec 2017 15:00:41 -0800
changeset 11753 963b438b949f
parent 11751 f5a8e41ac249
child 11754 09f7bead4e7c
permissions -rw-r--r--
Added support for linear sampling and pixel coordinates 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 <Cocoa/Cocoa.h>
    33 #else
    34 #include "../../video/uikit/SDL_uikitmetalview.h"
    35 #endif
    36 #include <Metal/Metal.h>
    37 #include <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 int METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture);
    53 static int METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
    54                             const SDL_Rect * rect, const void *pixels,
    55                             int pitch);
    56 static int METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
    57                                const SDL_Rect * rect,
    58                                const Uint8 *Yplane, int Ypitch,
    59                                const Uint8 *Uplane, int Upitch,
    60                                const Uint8 *Vplane, int Vpitch);
    61 static int METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
    62                           const SDL_Rect * rect, void **pixels, int *pitch);
    63 static void METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture);
    64 static int METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture);
    65 static int METAL_UpdateViewport(SDL_Renderer * renderer);
    66 static int METAL_UpdateClipRect(SDL_Renderer * renderer);
    67 static int METAL_RenderClear(SDL_Renderer * renderer);
    68 static int METAL_RenderDrawPoints(SDL_Renderer * renderer,
    69                                const SDL_FPoint * points, int count);
    70 static int METAL_RenderDrawLines(SDL_Renderer * renderer,
    71                               const SDL_FPoint * points, int count);
    72 static int METAL_RenderFillRects(SDL_Renderer * renderer,
    73                               const SDL_FRect * rects, int count);
    74 static int METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
    75                          const SDL_Rect * srcrect, const SDL_FRect * dstrect);
    76 static int METAL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture,
    77                          const SDL_Rect * srcrect, const SDL_FRect * dstrect,
    78                          const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip);
    79 static int METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
    80                                Uint32 pixel_format, void * pixels, int pitch);
    81 static void METAL_RenderPresent(SDL_Renderer * renderer);
    82 static void METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture);
    83 static void METAL_DestroyRenderer(SDL_Renderer * renderer);
    84 static void *METAL_GetMetalLayer(SDL_Renderer * renderer);
    85 static void *METAL_GetMetalCommandEncoder(SDL_Renderer * renderer);
    86 
    87 SDL_RenderDriver METAL_RenderDriver = {
    88     METAL_CreateRenderer,
    89     {
    90      "metal",
    91      (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
    92      2,
    93      {SDL_PIXELFORMAT_ARGB8888, SDL_PIXELFORMAT_ABGR8888},
    94 
    95      // !!! FIXME: how do you query Metal for this?
    96      // (the weakest GPU supported by Metal on iOS has 4k texture max, and
    97      //  other models might be 2x or 4x more. On macOS, it's 16k across the
    98      //  board right now.)
    99 #ifdef __MACOSX__
   100      16384, 16384
   101 #else
   102      4096, 4096
   103 #endif
   104     }
   105 };
   106 
   107 @interface METAL_RenderData : NSObject
   108     @property (nonatomic, assign) BOOL beginScene;
   109     @property (nonatomic, retain) id<MTLDevice> mtldevice;
   110     @property (nonatomic, retain) id<MTLCommandQueue> mtlcmdqueue;
   111     @property (nonatomic, retain) id<MTLCommandBuffer> mtlcmdbuffer;
   112     @property (nonatomic, retain) id<MTLRenderCommandEncoder> mtlcmdencoder;
   113     @property (nonatomic, retain) id<MTLLibrary> mtllibrary;
   114     @property (nonatomic, retain) id<CAMetalDrawable> mtlbackbuffer;
   115     @property (nonatomic, retain) NSMutableArray *mtlpipelineprims;
   116     @property (nonatomic, retain) NSMutableArray *mtlpipelinecopynearest;
   117     @property (nonatomic, retain) NSMutableArray *mtlpipelinecopylinear;
   118     @property (nonatomic, retain) id<MTLBuffer> mtlbufclearverts;
   119     @property (nonatomic, retain) CAMetalLayer *mtllayer;
   120     @property (nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
   121 @end
   122 
   123 @implementation METAL_RenderData
   124 @end
   125 
   126 @interface METAL_TextureData : NSObject
   127     @property (nonatomic, retain) id<MTLTexture> mtltexture;
   128     @property (nonatomic, retain) NSMutableArray *mtlpipeline;
   129 @end
   130 
   131 @implementation METAL_TextureData
   132 @end
   133 
   134 static int
   135 IsMetalAvailable(const SDL_SysWMinfo *syswm)
   136 {
   137     if (syswm->subsystem != SDL_SYSWM_COCOA && syswm->subsystem != SDL_SYSWM_UIKIT) {
   138         return SDL_SetError("Metal render target only supports Cocoa and UIKit video targets at the moment.");
   139     }
   140 
   141     // this checks a weak symbol.
   142 #if (defined(__MACOSX__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101100))
   143     if (MTLCreateSystemDefaultDevice == NULL) {  // probably on 10.10 or lower.
   144         return SDL_SetError("Metal framework not available on this system");
   145     }
   146 #endif
   147 
   148     return 0;
   149 }
   150 
   151 static id<MTLRenderPipelineState>
   152 MakePipelineState(METAL_RenderData *data, NSString *label, NSString *vertfn,
   153                   NSString *fragfn, const SDL_BlendMode blendmode)
   154 {
   155     id<MTLFunction> mtlvertfn = [data.mtllibrary newFunctionWithName:vertfn];
   156     id<MTLFunction> mtlfragfn = [data.mtllibrary newFunctionWithName:fragfn];
   157     SDL_assert(mtlvertfn != nil);
   158     SDL_assert(mtlfragfn != nil);
   159 
   160     MTLRenderPipelineDescriptor *mtlpipedesc = [[MTLRenderPipelineDescriptor alloc] init];
   161     mtlpipedesc.vertexFunction = mtlvertfn;
   162     mtlpipedesc.fragmentFunction = mtlfragfn;
   163     mtlpipedesc.colorAttachments[0].pixelFormat = data.mtllayer.pixelFormat;
   164 
   165     switch (blendmode) {
   166         case SDL_BLENDMODE_BLEND:
   167             mtlpipedesc.colorAttachments[0].blendingEnabled = YES;
   168             mtlpipedesc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
   169             mtlpipedesc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
   170             mtlpipedesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
   171             mtlpipedesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
   172             mtlpipedesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
   173             mtlpipedesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
   174             break;
   175 
   176         case SDL_BLENDMODE_ADD:
   177             mtlpipedesc.colorAttachments[0].blendingEnabled = YES;
   178             mtlpipedesc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
   179             mtlpipedesc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
   180             mtlpipedesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
   181             mtlpipedesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOne;
   182             mtlpipedesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorZero;
   183             mtlpipedesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOne;
   184             break;
   185 
   186         case SDL_BLENDMODE_MOD:
   187             mtlpipedesc.colorAttachments[0].blendingEnabled = YES;
   188             mtlpipedesc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
   189             mtlpipedesc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
   190             mtlpipedesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorZero;
   191             mtlpipedesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorSourceColor;
   192             mtlpipedesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorZero;
   193             mtlpipedesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOne;
   194             break;
   195 
   196         default:
   197             mtlpipedesc.colorAttachments[0].blendingEnabled = NO;
   198             break;
   199     }
   200 
   201     mtlpipedesc.label = label;
   202 
   203     NSError *err = nil;
   204     id<MTLRenderPipelineState> retval = [data.mtldevice newRenderPipelineStateWithDescriptor:mtlpipedesc error:&err];
   205     SDL_assert(err == nil);
   206 #if !__has_feature(objc_arc)
   207     [mtlpipedesc release];  // !!! FIXME: can these be reused for each creation, or does the pipeline obtain it?
   208     [mtlvertfn release];
   209     [mtlfragfn release];
   210     [label release];
   211 #endif
   212     return retval;
   213 }
   214 
   215 static void
   216 MakePipelineStates(METAL_RenderData *data, NSMutableArray *states,
   217                    NSString *label, NSString *vertfn, NSString *fragfn)
   218 {
   219     [states addObject:MakePipelineState(data, [label stringByAppendingString:@" (blendmode=none)"], vertfn, fragfn, SDL_BLENDMODE_NONE)];
   220     [states addObject:MakePipelineState(data, [label stringByAppendingString:@" (blendmode=blend)"], vertfn, fragfn, SDL_BLENDMODE_BLEND)];
   221     [states addObject:MakePipelineState(data, [label stringByAppendingString:@" (blendmode=add)"], vertfn, fragfn, SDL_BLENDMODE_ADD)];
   222     [states addObject:MakePipelineState(data, [label stringByAppendingString:@" (blendmode=mod)"], vertfn, fragfn, SDL_BLENDMODE_MOD)];
   223 }
   224 
   225 static inline id<MTLRenderPipelineState>
   226 ChoosePipelineState(NSMutableArray *states, const SDL_BlendMode blendmode)
   227 {
   228     switch (blendmode) {
   229         case SDL_BLENDMODE_BLEND: return (id<MTLRenderPipelineState>)states[1];
   230         case SDL_BLENDMODE_ADD: return (id<MTLRenderPipelineState>)states[2];
   231         case SDL_BLENDMODE_MOD: return (id<MTLRenderPipelineState>)states[3];
   232         default: return (id<MTLRenderPipelineState>)states[0];
   233     }
   234     return nil;
   235 }
   236 
   237 static SDL_Renderer *
   238 METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
   239 {
   240     SDL_Renderer *renderer = NULL;
   241     METAL_RenderData *data = NULL;
   242     SDL_SysWMinfo syswm;
   243 
   244     SDL_VERSION(&syswm.version);
   245     if (!SDL_GetWindowWMInfo(window, &syswm)) {
   246         return NULL;
   247     }
   248 
   249     if (IsMetalAvailable(&syswm) == -1) {
   250         return NULL;
   251     }
   252 
   253     renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer));
   254     if (!renderer) {
   255         SDL_OutOfMemory();
   256         return NULL;
   257     }
   258 
   259     data = [[METAL_RenderData alloc] init];
   260     data.beginScene = YES;
   261 
   262 #if __has_feature(objc_arc)
   263     renderer->driverdata = (void*)CFBridgingRetain(data);
   264 #else
   265     renderer->driverdata = data;
   266 #endif
   267     renderer->window = window;
   268 
   269 #ifdef __MACOSX__
   270     id<MTLDevice> mtldevice = MTLCreateSystemDefaultDevice();  // !!! FIXME: MTLCopyAllDevices() can find other GPUs...
   271     if (mtldevice == nil) {
   272         SDL_free(renderer);
   273 #if !__has_feature(objc_arc)
   274         [data release];
   275 #endif
   276         SDL_SetError("Failed to obtain Metal device");
   277         return NULL;
   278     }
   279 
   280     // !!! FIXME: error checking on all of this.
   281 
   282     NSView *nsview = [syswm.info.cocoa.window contentView];
   283 
   284     // CAMetalLayer is available in QuartzCore starting at OSX 10.11
   285     CAMetalLayer *layer = [NSClassFromString( @"CAMetalLayer" ) layer];
   286 
   287     layer.device = mtldevice;
   288     //layer.pixelFormat = MTLPixelFormatBGRA8Unorm;  // !!! FIXME: MTLPixelFormatBGRA8Unorm_sRGB ?
   289     layer.framebufferOnly = YES;
   290     //layer.drawableSize = (CGSize) [nsview convertRectToBacking:[nsview bounds]].size;
   291     //layer.colorspace = nil;
   292 
   293     [nsview setWantsLayer:YES];
   294     [nsview setLayer:layer];
   295 
   296     [layer retain];
   297 #else
   298     UIView *view = UIKit_Mtl_AddMetalView(window);
   299     CAMetalLayer *layer = (CAMetalLayer *)[view layer];
   300 #endif
   301 
   302     data.mtldevice = layer.device;
   303     data.mtllayer = layer;
   304     data.mtlcmdqueue = [data.mtldevice newCommandQueue];
   305     data.mtlcmdqueue.label = @"SDL Metal Renderer";
   306     data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
   307 
   308     NSError *err = nil;
   309 
   310     // The compiled .metallib is embedded in a static array in a header file
   311     // but the original shader source code is in SDL_shaders_metal.metal.
   312     dispatch_data_t mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{});
   313     data.mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
   314     SDL_assert(err == nil);
   315 #if !__has_feature(objc_arc)
   316     dispatch_release(mtllibdata);
   317 #endif
   318     data.mtllibrary.label = @"SDL Metal renderer shader library";
   319 
   320     data.mtlpipelineprims = [[NSMutableArray alloc] init];
   321     MakePipelineStates(data, data.mtlpipelineprims, @"SDL primitives pipeline", @"SDL_Solid_vertex", @"SDL_Solid_fragment");
   322     data.mtlpipelinecopynearest = [[NSMutableArray alloc] init];
   323     MakePipelineStates(data, data.mtlpipelinecopynearest, @"SDL texture pipeline (nearest)", @"SDL_Copy_vertex", @"SDL_Copy_fragment_nearest");
   324     data.mtlpipelinecopylinear = [[NSMutableArray alloc] init];
   325     MakePipelineStates(data, data.mtlpipelinecopylinear, @"SDL texture pipeline (linear)", @"SDL_Copy_vertex", @"SDL_Copy_fragment_linear");
   326 
   327     static const float clearverts[] = { -1, -1, -1, 1, 1, 1, 1, -1, -1, -1 };
   328     data.mtlbufclearverts = [data.mtldevice newBufferWithBytes:clearverts length:sizeof(clearverts) options:MTLResourceCPUCacheModeWriteCombined];
   329     data.mtlbufclearverts.label = @"SDL_RenderClear vertices";
   330 
   331     // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
   332 
   333     renderer->WindowEvent = METAL_WindowEvent;
   334     renderer->GetOutputSize = METAL_GetOutputSize;
   335     renderer->CreateTexture = METAL_CreateTexture;
   336     renderer->UpdateTexture = METAL_UpdateTexture;
   337     renderer->UpdateTextureYUV = METAL_UpdateTextureYUV;
   338     renderer->LockTexture = METAL_LockTexture;
   339     renderer->UnlockTexture = METAL_UnlockTexture;
   340     renderer->SetRenderTarget = METAL_SetRenderTarget;
   341     renderer->UpdateViewport = METAL_UpdateViewport;
   342     renderer->UpdateClipRect = METAL_UpdateClipRect;
   343     renderer->RenderClear = METAL_RenderClear;
   344     renderer->RenderDrawPoints = METAL_RenderDrawPoints;
   345     renderer->RenderDrawLines = METAL_RenderDrawLines;
   346     renderer->RenderFillRects = METAL_RenderFillRects;
   347     renderer->RenderCopy = METAL_RenderCopy;
   348     renderer->RenderCopyEx = METAL_RenderCopyEx;
   349     renderer->RenderReadPixels = METAL_RenderReadPixels;
   350     renderer->RenderPresent = METAL_RenderPresent;
   351     renderer->DestroyTexture = METAL_DestroyTexture;
   352     renderer->DestroyRenderer = METAL_DestroyRenderer;
   353     renderer->GetMetalLayer = METAL_GetMetalLayer;
   354     renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
   355 
   356     renderer->info = METAL_RenderDriver.info;
   357     renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
   358 
   359     // !!! FIXME: how do you control this in Metal?
   360     renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
   361 
   362     return renderer;
   363 }
   364 
   365 static void METAL_ActivateRenderer(SDL_Renderer * renderer)
   366 {
   367     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   368 
   369     if (data.beginScene) {
   370         data.beginScene = NO;
   371         data.mtlbackbuffer = [data.mtllayer nextDrawable];
   372         SDL_assert(data.mtlbackbuffer);
   373         data.mtlpassdesc.colorAttachments[0].texture = data.mtlbackbuffer.texture;
   374         data.mtlpassdesc.colorAttachments[0].loadAction = MTLLoadActionDontCare;
   375         data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
   376         data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
   377         data.mtlcmdencoder.label = @"SDL metal renderer start of frame";
   378 
   379         // Set up our current renderer state for the next frame...
   380         METAL_UpdateViewport(renderer);
   381         METAL_UpdateClipRect(renderer);
   382     }
   383 }
   384 
   385 static void
   386 METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
   387 {
   388     if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
   389         event->event == SDL_WINDOWEVENT_SHOWN ||
   390         event->event == SDL_WINDOWEVENT_HIDDEN) {
   391         // !!! FIXME: write me
   392     }
   393 }
   394 
   395 static int
   396 METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
   397 { @autoreleasepool {
   398     METAL_ActivateRenderer(renderer);
   399     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   400     *w = (int) data.mtlbackbuffer.texture.width;
   401     *h = (int) data.mtlbackbuffer.texture.height;
   402     return 0;
   403 }}
   404 
   405 static int
   406 METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
   407 { @autoreleasepool {
   408     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   409     MTLPixelFormat mtlpixfmt;
   410 
   411     switch (texture->format) {
   412         case SDL_PIXELFORMAT_ABGR8888: mtlpixfmt = MTLPixelFormatRGBA8Unorm; break;
   413         case SDL_PIXELFORMAT_ARGB8888: mtlpixfmt = MTLPixelFormatBGRA8Unorm; break;
   414         default: return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
   415     }
   416 
   417     MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:mtlpixfmt
   418                                             width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO];
   419  
   420     if (texture->access == SDL_TEXTUREACCESS_TARGET) {
   421         mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
   422     } else {
   423         mtltexdesc.usage = MTLTextureUsageShaderRead;
   424     }
   425     //mtltexdesc.resourceOptions = MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeManaged;
   426     //mtltexdesc.storageMode = MTLStorageModeManaged;
   427     
   428     id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
   429     if (mtltexture == nil) {
   430         return SDL_SetError("Texture allocation failed");
   431     }
   432 
   433     METAL_TextureData *texturedata = [[METAL_TextureData alloc] init];
   434     const char *hint = SDL_GetHint(SDL_HINT_RENDER_SCALE_QUALITY);
   435     if (!hint || *hint == '0' || SDL_strcasecmp(hint, "nearest") == 0) {
   436         texturedata.mtlpipeline = data.mtlpipelinecopynearest;
   437     } else {
   438         texturedata.mtlpipeline = data.mtlpipelinecopylinear;
   439     }
   440     texturedata.mtltexture = mtltexture;
   441 
   442     texture->driverdata = (void*)CFBridgingRetain(texturedata);
   443 
   444     return 0;
   445 }}
   446 
   447 static int
   448 METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
   449                  const SDL_Rect * rect, const void *pixels, int pitch)
   450 { @autoreleasepool {
   451     // !!! FIXME: this is a synchronous call; it doesn't return until data is uploaded in some form.
   452     // !!! FIXME:  Maybe move this off to a thread that marks the texture as uploaded and only stall the main thread if we try to
   453     // !!! FIXME:  use this texture before the marking is done? Is it worth it? Or will we basically always be uploading a bunch of
   454     // !!! FIXME:  stuff way ahead of time and/or using it immediately after upload?
   455     id<MTLTexture> mtltexture = ((__bridge METAL_TextureData *)texture->driverdata).mtltexture;
   456     [mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h) mipmapLevel:0 withBytes:pixels bytesPerRow:pitch];
   457     return 0;
   458 }}
   459 
   460 static int
   461 METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
   462                     const SDL_Rect * rect,
   463                     const Uint8 *Yplane, int Ypitch,
   464                     const Uint8 *Uplane, int Upitch,
   465                     const Uint8 *Vplane, int Vpitch)
   466 {
   467     return SDL_Unsupported();  // !!! FIXME
   468 }
   469 
   470 static int
   471 METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
   472                const SDL_Rect * rect, void **pixels, int *pitch)
   473 {
   474     return SDL_Unsupported();   // !!! FIXME: write me
   475 }
   476 
   477 static void
   478 METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture)
   479 {
   480     // !!! FIXME: write me
   481 }
   482 
   483 static int
   484 METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
   485 { @autoreleasepool {
   486     METAL_ActivateRenderer(renderer);
   487     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   488 
   489     // commit the current command buffer, so that any work on a render target
   490     //  will be available to the next one we're about to queue up.
   491     [data.mtlcmdencoder endEncoding];
   492     [data.mtlcmdbuffer commit];
   493 
   494     id<MTLTexture> mtltexture = texture ? ((__bridge METAL_TextureData *)texture->driverdata).mtltexture : data.mtlbackbuffer.texture;
   495     data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
   496     // !!! FIXME: this can be MTLLoadActionDontCare for textures (not the backbuffer) if SDL doesn't guarantee the texture contents should survive.
   497     data.mtlpassdesc.colorAttachments[0].loadAction = MTLLoadActionLoad;
   498     data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
   499     data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
   500     data.mtlcmdencoder.label = texture ? @"SDL metal renderer render texture" : @"SDL metal renderer backbuffer";
   501 
   502     // The higher level will reset the viewport and scissor after this call returns.
   503 
   504     return 0;
   505 }}
   506 
   507 static int
   508 METAL_UpdateViewport(SDL_Renderer * renderer)
   509 { @autoreleasepool {
   510     METAL_ActivateRenderer(renderer);
   511     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   512     MTLViewport viewport;
   513     viewport.originX = renderer->viewport.x;
   514     viewport.originY = renderer->viewport.y;
   515     viewport.width = renderer->viewport.w;
   516     viewport.height = renderer->viewport.h;
   517     viewport.znear = 0.0;
   518     viewport.zfar = 1.0;
   519     [data.mtlcmdencoder setViewport:viewport];
   520     return 0;
   521 }}
   522 
   523 static int
   524 METAL_UpdateClipRect(SDL_Renderer * renderer)
   525 { @autoreleasepool {
   526     METAL_ActivateRenderer(renderer);
   527     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   528     MTLScissorRect mtlrect;
   529     // !!! FIXME: should this care about the viewport?
   530     if (renderer->clipping_enabled) {
   531         const SDL_Rect *rect = &renderer->clip_rect;
   532         mtlrect.x = renderer->viewport.x + rect->x;
   533         mtlrect.y = renderer->viewport.x + rect->y;
   534         mtlrect.width = rect->w;
   535         mtlrect.height = rect->h;
   536     } else {
   537         mtlrect.x = renderer->viewport.x;
   538         mtlrect.y = renderer->viewport.y;
   539         mtlrect.width = renderer->viewport.w;
   540         mtlrect.height = renderer->viewport.h;
   541     }
   542     if (mtlrect.width > 0 && mtlrect.height > 0) {
   543         [data.mtlcmdencoder setScissorRect:mtlrect];
   544     }
   545     return 0;
   546 }}
   547 
   548 static int
   549 METAL_RenderClear(SDL_Renderer * renderer)
   550 { @autoreleasepool {
   551     // We could dump the command buffer and force a clear on a new one, but this will respect the scissor state.
   552     METAL_ActivateRenderer(renderer);
   553     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   554 
   555     // !!! FIXME: render color should live in a dedicated uniform buffer.
   556     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   557 
   558     MTLViewport viewport;  // RenderClear ignores the viewport state, though, so reset that.
   559     viewport.originX = viewport.originY = 0.0;
   560     viewport.width = data.mtlpassdesc.colorAttachments[0].texture.width;
   561     viewport.height = data.mtlpassdesc.colorAttachments[0].texture.height;
   562     viewport.znear = 0.0;
   563     viewport.zfar = 1.0;
   564 
   565     // Draw as if we're doing a simple filled rect to the screen now.
   566     [data.mtlcmdencoder setViewport:viewport];
   567     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
   568     [data.mtlcmdencoder setVertexBuffer:data.mtlbufclearverts offset:0 atIndex:0];
   569     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   570     [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:5];
   571 
   572     // reset the viewport for the rest of our usual drawing work...
   573     viewport.originX = renderer->viewport.x;
   574     viewport.originY = renderer->viewport.y;
   575     viewport.width = renderer->viewport.w;
   576     viewport.height = renderer->viewport.h;
   577     viewport.znear = 0.0;
   578     viewport.zfar = 1.0;
   579     [data.mtlcmdencoder setViewport:viewport];
   580 
   581     return 0;
   582 }}
   583 
   584 // normalize a value from 0.0f to len into -1.0f to 1.0f.
   585 static inline float
   586 normx(const float _val, const float len)
   587 {
   588     const float val = (_val < 0.0f) ? 0.0f : (_val > len) ? len : _val;
   589     return (((val + 0.5f) / len) * 2.0f) - 1.0f;
   590 }
   591 
   592 // normalize a value from 0.0f to len into -1.0f to 1.0f.
   593 static inline float
   594 normy(const float _val, const float len)
   595 {
   596     const float val = (_val <= 0.0f) ? len : (_val >= len) ? 0.0f : (len - _val);
   597     return (((val - 0.5f) / len) * 2.0f) - 1.0f;
   598 }
   599 
   600 static int
   601 DrawVerts(SDL_Renderer * renderer, const SDL_FPoint * points, int count,
   602           const MTLPrimitiveType primtype)
   603 { @autoreleasepool {
   604     METAL_ActivateRenderer(renderer);
   605 
   606     const size_t vertlen = (sizeof (float) * 2) * count;
   607     float *verts = SDL_malloc(vertlen);
   608     if (!verts) {
   609         return SDL_OutOfMemory();
   610     }
   611 
   612     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   613 
   614     // !!! FIXME: render color should live in a dedicated uniform buffer.
   615     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   616 
   617     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
   618     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   619 
   620     const float w = (float) data.mtlpassdesc.colorAttachments[0].texture.width;
   621     const float h = (float) data.mtlpassdesc.colorAttachments[0].texture.height;
   622 
   623     // !!! FIXME: we can convert this in the shader. This will save the malloc and for-loop, but we still need to upload.
   624     float *ptr = verts;
   625     for (int i = 0; i < count; i++, points++) {
   626         *ptr = normx(points->x, w); ptr++;
   627         *ptr = normy(points->y, h); ptr++;
   628     }
   629 
   630     [data.mtlcmdencoder setVertexBytes:verts length:vertlen atIndex:0];
   631     [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
   632 
   633     SDL_free(verts);
   634     return 0;
   635 }}
   636 
   637 static int
   638 METAL_RenderDrawPoints(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
   639 {
   640     return DrawVerts(renderer, points, count, MTLPrimitiveTypePoint);
   641 }
   642 
   643 static int
   644 METAL_RenderDrawLines(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
   645 {
   646     return DrawVerts(renderer, points, count, MTLPrimitiveTypeLineStrip);
   647 }
   648 
   649 static int
   650 METAL_RenderFillRects(SDL_Renderer * renderer, const SDL_FRect * rects, int count)
   651 { @autoreleasepool {
   652     METAL_ActivateRenderer(renderer);
   653     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   654 
   655     // !!! FIXME: render color should live in a dedicated uniform buffer.
   656     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   657 
   658     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
   659     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   660 
   661     const float w = (float) data.mtlpassdesc.colorAttachments[0].texture.width;
   662     const float h = (float) data.mtlpassdesc.colorAttachments[0].texture.height;
   663 
   664     for (int i = 0; i < count; i++, rects++) {
   665         if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) continue;
   666 
   667         const float verts[] = {
   668             normx(rects->x, w), normy(rects->y + rects->h, h),
   669             normx(rects->x, w), normy(rects->y, h),
   670             normx(rects->x + rects->w, w), normy(rects->y, h),
   671             normx(rects->x, w), normy(rects->y + rects->h, h),
   672             normx(rects->x + rects->w, w), normy(rects->y + rects->h, h)
   673         };
   674 
   675         [data.mtlcmdencoder setVertexBytes:verts length:sizeof(verts) atIndex:0];
   676         [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:5];
   677     }
   678 
   679     return 0;
   680 }}
   681 
   682 static int
   683 METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
   684               const SDL_Rect * srcrect, const SDL_FRect * dstrect)
   685 { @autoreleasepool {
   686     METAL_ActivateRenderer(renderer);
   687     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   688     METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
   689     const float w = (float) data.mtlpassdesc.colorAttachments[0].texture.width;
   690     const float h = (float) data.mtlpassdesc.colorAttachments[0].texture.height;
   691     const float texw = (float) texturedata.mtltexture.width;
   692     const float texh = (float) texturedata.mtltexture.height;
   693 
   694     const float xy[] = {
   695         normx(dstrect->x, w), normy(dstrect->y + dstrect->h, h),
   696         normx(dstrect->x, w), normy(dstrect->y, h),
   697         normx(dstrect->x + dstrect->w, w), normy(dstrect->y, h),
   698         normx(dstrect->x, w), normy(dstrect->y + dstrect->h, h),
   699         normx(dstrect->x + dstrect->w, w), normy(dstrect->y + dstrect->h, h)
   700     };
   701 
   702     const float uv[] = {
   703         srcrect->x, srcrect->y + srcrect->h,
   704         srcrect->x, srcrect->y,
   705         srcrect->x + srcrect->w, srcrect->y,
   706         srcrect->x, srcrect->y + srcrect->h,
   707         srcrect->x + srcrect->w, srcrect->y + srcrect->h
   708     };
   709 
   710     float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
   711     if (texture->modMode) {
   712         color[0] = ((float)texture->r) / 255.0f;
   713         color[1] = ((float)texture->g) / 255.0f;
   714         color[2] = ((float)texture->b) / 255.0f;
   715         color[3] = ((float)texture->a) / 255.0f;
   716     }
   717 
   718     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(texturedata.mtlpipeline, texture->blendMode)];
   719     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   720     [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0];
   721     [data.mtlcmdencoder setVertexBytes:xy length:sizeof(xy) atIndex:0];
   722     [data.mtlcmdencoder setVertexBytes:uv length:sizeof(uv) atIndex:1];
   723     [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:5];
   724 
   725     return 0;
   726 }}
   727 
   728 static int
   729 METAL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture,
   730               const SDL_Rect * srcrect, const SDL_FRect * dstrect,
   731               const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
   732 {
   733     return SDL_Unsupported();  // !!! FIXME
   734 }
   735 
   736 static int
   737 METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
   738                     Uint32 pixel_format, void * pixels, int pitch)
   739 { @autoreleasepool {
   740     METAL_ActivateRenderer(renderer);
   741     // !!! FIXME: this probably needs to commit the current command buffer, and probably waitUntilCompleted
   742     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   743     MTLRenderPassColorAttachmentDescriptor *colorAttachment = data.mtlpassdesc.colorAttachments[0];
   744     id<MTLTexture> mtltexture = colorAttachment.texture;
   745     MTLRegion mtlregion;
   746 
   747     mtlregion.origin.x = rect->x;
   748     mtlregion.origin.y = rect->y;
   749     mtlregion.origin.z = 0;
   750     mtlregion.size.width = rect->w;
   751     mtlregion.size.height = rect->w;
   752     mtlregion.size.depth = 1;
   753 
   754     // we only do BGRA8 or RGBA8 at the moment, so 4 will do.
   755     const int temp_pitch = rect->w * 4;
   756     void *temp_pixels = SDL_malloc(temp_pitch * rect->h);
   757     if (!temp_pixels) {
   758         return SDL_OutOfMemory();
   759     }
   760 
   761     [mtltexture getBytes:temp_pixels bytesPerRow:temp_pitch fromRegion:mtlregion mipmapLevel:0];
   762 
   763     const Uint32 temp_format = (mtltexture.pixelFormat == MTLPixelFormatBGRA8Unorm) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_ABGR8888;
   764     const int status = SDL_ConvertPixels(rect->w, rect->h, temp_format, temp_pixels, temp_pitch, pixel_format, pixels, pitch);
   765     SDL_free(temp_pixels);
   766     return status;
   767 }}
   768 
   769 static void
   770 METAL_RenderPresent(SDL_Renderer * renderer)
   771 { @autoreleasepool {
   772     METAL_ActivateRenderer(renderer);
   773     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   774 
   775     [data.mtlcmdencoder endEncoding];
   776     [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
   777     [data.mtlcmdbuffer commit];
   778     data.mtlcmdencoder = nil;
   779     data.mtlcmdbuffer = nil;
   780     data.mtlbackbuffer = nil;
   781     data.beginScene = YES;
   782 }}
   783 
   784 static void
   785 METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture)
   786 { @autoreleasepool {
   787     METAL_TextureData *texturedata = CFBridgingRelease(texture->driverdata);
   788 #if __has_feature(objc_arc)
   789     texturedata = nil;
   790 #else
   791     [texturedata.mtltexture release];
   792     [texturedata release];
   793 #endif
   794     texture->driverdata = NULL;
   795 }}
   796 
   797 static void
   798 METAL_DestroyRenderer(SDL_Renderer * renderer)
   799 { @autoreleasepool {
   800     if (renderer->driverdata) {
   801         METAL_RenderData *data = CFBridgingRelease(renderer->driverdata);
   802 
   803         if (data.mtlcmdencoder != nil) {
   804             [data.mtlcmdencoder endEncoding];
   805         }
   806 
   807 #if !__has_feature(objc_arc)
   808         [data.mtlbackbuffer release];
   809         [data.mtlcmdencoder release];
   810         [data.mtlcmdbuffer release];
   811         [data.mtlcmdqueue release];
   812         for (int i = 0; i < 4; i++) {
   813             [data.mtlpipelineprims[i] release];
   814             [data.mtlpipelinecopy[i] release];
   815         }
   816         [data.mtlpipelineprims release];
   817         [data.mtlpipelinecopy release];
   818         [data.mtlbufclearverts release];
   819         [data.mtllibrary release];
   820         [data.mtldevice release];
   821         [data.mtlpassdesc release];
   822         [data.mtllayer release];
   823 #endif
   824     }
   825 
   826     SDL_free(renderer);
   827 }}
   828 
   829 void *METAL_GetMetalLayer(SDL_Renderer * renderer)
   830 { @autoreleasepool {
   831     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   832     return (__bridge void*)data.mtllayer;
   833 }}
   834 
   835 void *METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
   836 { @autoreleasepool {
   837     METAL_ActivateRenderer(renderer);
   838     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   839     return (__bridge void*)data.mtlcmdencoder;
   840 }}
   841 
   842 #endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */
   843 
   844 /* vi: set ts=4 sw=4 expandtab: */