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