src/render/metal/SDL_render_metal.m
author Sam Lantinga <slouken@libsdl.org>
Mon, 11 Dec 2017 11:34:53 -0800
changeset 11760 e85aa67038df
parent 11759 05cdab5a00f8
child 11788 d16d4c766611
permissions -rw-r--r--
The newer compilers generate the property boilerplate automatically
     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 // normalize a value from 0.0f to len into 0.0f to 1.0f.
   601 static inline float
   602 normtex(const float _val, const float len)
   603 {
   604     const float val = (_val < 0.0f) ? 0.0f : (_val > len) ? len : _val;
   605     return ((val + 0.5f) / len);
   606 }
   607 
   608 static int
   609 DrawVerts(SDL_Renderer * renderer, const SDL_FPoint * points, int count,
   610           const MTLPrimitiveType primtype)
   611 { @autoreleasepool {
   612     METAL_ActivateRenderer(renderer);
   613 
   614     const size_t vertlen = (sizeof (float) * 2) * count;
   615     float *verts = SDL_malloc(vertlen);
   616     if (!verts) {
   617         return SDL_OutOfMemory();
   618     }
   619 
   620     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   621 
   622     // !!! FIXME: render color should live in a dedicated uniform buffer.
   623     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   624 
   625     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
   626     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   627 
   628     const float w = (float)renderer->viewport.w;
   629     const float h = (float)renderer->viewport.h;
   630 
   631     // !!! FIXME: we can convert this in the shader. This will save the malloc and for-loop, but we still need to upload.
   632     float *ptr = verts;
   633     for (int i = 0; i < count; i++, points++) {
   634         *ptr = normx(points->x, w); ptr++;
   635         *ptr = normy(points->y, h); ptr++;
   636     }
   637 
   638     [data.mtlcmdencoder setVertexBytes:verts length:vertlen atIndex:0];
   639     [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
   640 
   641     SDL_free(verts);
   642     return 0;
   643 }}
   644 
   645 static int
   646 METAL_RenderDrawPoints(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
   647 {
   648     return DrawVerts(renderer, points, count, MTLPrimitiveTypePoint);
   649 }
   650 
   651 static int
   652 METAL_RenderDrawLines(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
   653 {
   654     return DrawVerts(renderer, points, count, MTLPrimitiveTypeLineStrip);
   655 }
   656 
   657 static int
   658 METAL_RenderFillRects(SDL_Renderer * renderer, const SDL_FRect * rects, int count)
   659 { @autoreleasepool {
   660     METAL_ActivateRenderer(renderer);
   661     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   662 
   663     // !!! FIXME: render color should live in a dedicated uniform buffer.
   664     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   665 
   666     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
   667     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   668 
   669     const float w = (float)renderer->viewport.w;
   670     const float h = (float)renderer->viewport.h;
   671 
   672     for (int i = 0; i < count; i++, rects++) {
   673         if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) continue;
   674 
   675         const float verts[] = {
   676             normx(rects->x, w), normy(rects->y + rects->h, h),
   677             normx(rects->x, w), normy(rects->y, h),
   678             normx(rects->x + rects->w, w), normy(rects->y, h),
   679             normx(rects->x, w), normy(rects->y + rects->h, h),
   680             normx(rects->x + rects->w, w), normy(rects->y + rects->h, h)
   681         };
   682 
   683         [data.mtlcmdencoder setVertexBytes:verts length:sizeof(verts) atIndex:0];
   684         [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:5];
   685     }
   686 
   687     return 0;
   688 }}
   689 
   690 static int
   691 METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
   692               const SDL_Rect * srcrect, const SDL_FRect * dstrect)
   693 { @autoreleasepool {
   694     METAL_ActivateRenderer(renderer);
   695     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   696     METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
   697     const float w = (float)renderer->viewport.w;
   698     const float h = (float)renderer->viewport.h;
   699     const float texw = (float) texturedata.mtltexture.width;
   700     const float texh = (float) texturedata.mtltexture.height;
   701 
   702     const float xy[] = {
   703         normx(dstrect->x, w), normy(dstrect->y + dstrect->h, h),
   704         normx(dstrect->x, w), normy(dstrect->y, h),
   705         normx(dstrect->x + dstrect->w, w), normy(dstrect->y, h),
   706         normx(dstrect->x, w), normy(dstrect->y + dstrect->h, h),
   707         normx(dstrect->x + dstrect->w, w), normy(dstrect->y + dstrect->h, h)
   708     };
   709 
   710     const float uv[] = {
   711         normtex(srcrect->x, texw), normtex(srcrect->y + srcrect->h, texh),
   712         normtex(srcrect->x, texw), normtex(srcrect->y, texh),
   713         normtex(srcrect->x + srcrect->w, texw), normtex(srcrect->y, texh),
   714         normtex(srcrect->x, texw), normtex(srcrect->y + srcrect->h, texh),
   715         normtex(srcrect->x + srcrect->w, texw), normtex(srcrect->y + srcrect->h, texh)
   716     };
   717 
   718     float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
   719     if (texture->modMode) {
   720         color[0] = ((float)texture->r) / 255.0f;
   721         color[1] = ((float)texture->g) / 255.0f;
   722         color[2] = ((float)texture->b) / 255.0f;
   723         color[3] = ((float)texture->a) / 255.0f;
   724     }
   725 
   726     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(texturedata.mtlpipeline, texture->blendMode)];
   727     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   728     [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0];
   729     [data.mtlcmdencoder setVertexBytes:xy length:sizeof(xy) atIndex:0];
   730     [data.mtlcmdencoder setVertexBytes:uv length:sizeof(uv) atIndex:1];
   731     [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:5];
   732 
   733     return 0;
   734 }}
   735 
   736 static int
   737 METAL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture,
   738               const SDL_Rect * srcrect, const SDL_FRect * dstrect,
   739               const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
   740 {
   741     return SDL_Unsupported();  // !!! FIXME
   742 }
   743 
   744 static int
   745 METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
   746                     Uint32 pixel_format, void * pixels, int pitch)
   747 { @autoreleasepool {
   748     METAL_ActivateRenderer(renderer);
   749     // !!! FIXME: this probably needs to commit the current command buffer, and probably waitUntilCompleted
   750     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   751     MTLRenderPassColorAttachmentDescriptor *colorAttachment = data.mtlpassdesc.colorAttachments[0];
   752     id<MTLTexture> mtltexture = colorAttachment.texture;
   753     MTLRegion mtlregion;
   754 
   755     mtlregion.origin.x = rect->x;
   756     mtlregion.origin.y = rect->y;
   757     mtlregion.origin.z = 0;
   758     mtlregion.size.width = rect->w;
   759     mtlregion.size.height = rect->w;
   760     mtlregion.size.depth = 1;
   761 
   762     // we only do BGRA8 or RGBA8 at the moment, so 4 will do.
   763     const int temp_pitch = rect->w * 4;
   764     void *temp_pixels = SDL_malloc(temp_pitch * rect->h);
   765     if (!temp_pixels) {
   766         return SDL_OutOfMemory();
   767     }
   768 
   769     [mtltexture getBytes:temp_pixels bytesPerRow:temp_pitch fromRegion:mtlregion mipmapLevel:0];
   770 
   771     const Uint32 temp_format = (mtltexture.pixelFormat == MTLPixelFormatBGRA8Unorm) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_ABGR8888;
   772     const int status = SDL_ConvertPixels(rect->w, rect->h, temp_format, temp_pixels, temp_pitch, pixel_format, pixels, pitch);
   773     SDL_free(temp_pixels);
   774     return status;
   775 }}
   776 
   777 static void
   778 METAL_RenderPresent(SDL_Renderer * renderer)
   779 { @autoreleasepool {
   780     METAL_ActivateRenderer(renderer);
   781     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   782 
   783     [data.mtlcmdencoder endEncoding];
   784     [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
   785     [data.mtlcmdbuffer commit];
   786     data.mtlcmdencoder = nil;
   787     data.mtlcmdbuffer = nil;
   788     data.mtlbackbuffer = nil;
   789     data.beginScene = YES;
   790 }}
   791 
   792 static void
   793 METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture)
   794 { @autoreleasepool {
   795     METAL_TextureData *texturedata = CFBridgingRelease(texture->driverdata);
   796 #if __has_feature(objc_arc)
   797     texturedata = nil;
   798 #else
   799     [texturedata.mtltexture release];
   800     [texturedata release];
   801 #endif
   802     texture->driverdata = NULL;
   803 }}
   804 
   805 static void
   806 METAL_DestroyRenderer(SDL_Renderer * renderer)
   807 { @autoreleasepool {
   808     if (renderer->driverdata) {
   809         METAL_RenderData *data = CFBridgingRelease(renderer->driverdata);
   810 
   811         if (data.mtlcmdencoder != nil) {
   812             [data.mtlcmdencoder endEncoding];
   813         }
   814 
   815 #if !__has_feature(objc_arc)
   816         [data.mtlbackbuffer release];
   817         [data.mtlcmdencoder release];
   818         [data.mtlcmdbuffer release];
   819         [data.mtlcmdqueue release];
   820         for (int i = 0; i < 4; i++) {
   821             [data.mtlpipelineprims[i] release];
   822             [data.mtlpipelinecopynearest[i] release];
   823             [data.mtlpipelinecopylinear[i] release];
   824         }
   825         [data.mtlpipelineprims release];
   826         [data.mtlpipelinecopynearest release];
   827         [data.mtlpipelinecopylinear release];
   828         [data.mtlbufclearverts release];
   829         [data.mtllibrary release];
   830         [data.mtldevice release];
   831         [data.mtlpassdesc release];
   832         [data.mtllayer release];
   833 #endif
   834     }
   835 
   836     SDL_free(renderer);
   837 }}
   838 
   839 void *METAL_GetMetalLayer(SDL_Renderer * renderer)
   840 { @autoreleasepool {
   841     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   842     return (__bridge void*)data.mtllayer;
   843 }}
   844 
   845 void *METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
   846 { @autoreleasepool {
   847     METAL_ActivateRenderer(renderer);
   848     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   849     return (__bridge void*)data.mtlcmdencoder;
   850 }}
   851 
   852 #endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */
   853 
   854 /* vi: set ts=4 sw=4 expandtab: */