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