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