src/render/metal/SDL_render_metal.m
author Ryan C. Gordon <icculus@icculus.org>
Sat, 09 Dec 2017 03:28:23 -0500
changeset 11750 e2a349c9dd30
parent 11749 799404c93d48
child 11751 f5a8e41ac249
permissions -rw-r--r--
metal: fixed render target support.
     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 start of 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     if (texture->access == SDL_TEXTUREACCESS_TARGET) {
   406         mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
   407     } else {
   408         mtltexdesc.usage = MTLTextureUsageShaderRead;
   409     }
   410     //mtltexdesc.resourceOptions = MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeManaged;
   411     //mtltexdesc.storageMode = MTLStorageModeManaged;
   412     
   413     id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
   414     if (mtltexture == nil) {
   415         return SDL_SetError("Texture allocation failed");
   416     }
   417 
   418     texture->driverdata = (void*)CFBridgingRetain(mtltexture);
   419 
   420     return 0;
   421 }}
   422 
   423 static int
   424 METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
   425                  const SDL_Rect * rect, const void *pixels, int pitch)
   426 { @autoreleasepool {
   427     // !!! FIXME: this is a synchronous call; it doesn't return until data is uploaded in some form.
   428     // !!! FIXME:  Maybe move this off to a thread that marks the texture as uploaded and only stall the main thread if we try to
   429     // !!! FIXME:  use this texture before the marking is done? Is it worth it? Or will we basically always be uploading a bunch of
   430     // !!! FIXME:  stuff way ahead of time and/or using it immediately after upload?
   431     id<MTLTexture> mtltexture = (__bridge id<MTLTexture>) texture->driverdata;
   432     [mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h) mipmapLevel:0 withBytes:pixels bytesPerRow:pitch];
   433     return 0;
   434 }}
   435 
   436 static int
   437 METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
   438                     const SDL_Rect * rect,
   439                     const Uint8 *Yplane, int Ypitch,
   440                     const Uint8 *Uplane, int Upitch,
   441                     const Uint8 *Vplane, int Vpitch)
   442 {
   443     return SDL_Unsupported();  // !!! FIXME
   444 }
   445 
   446 static int
   447 METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
   448                const SDL_Rect * rect, void **pixels, int *pitch)
   449 {
   450     return SDL_Unsupported();   // !!! FIXME: write me
   451 }
   452 
   453 static void
   454 METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture)
   455 {
   456     // !!! FIXME: write me
   457 }
   458 
   459 static int
   460 METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
   461 { @autoreleasepool {
   462     METAL_ActivateRenderer(renderer);
   463     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   464 
   465     // commit the current command buffer, so that any work on a render target
   466     //  will be available to the next one we're about to queue up.
   467     [data.mtlcmdencoder endEncoding];
   468     [data.mtlcmdbuffer commit];
   469 
   470     id<MTLTexture> mtltexture = texture ? (__bridge id<MTLTexture>) texture->driverdata : data.mtlbackbuffer.texture;
   471     data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
   472     // !!! FIXME: this can be MTLLoadActionDontCare for textures (not the backbuffer) if SDL doesn't guarantee the texture contents should survive.
   473     data.mtlpassdesc.colorAttachments[0].loadAction = MTLLoadActionLoad;
   474     data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
   475     data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
   476     data.mtlcmdencoder.label = texture ? @"SDL metal renderer render texture" : @"SDL metal renderer backbuffer";
   477 
   478     // The higher level will reset the viewport and scissor after this call returns.
   479 
   480     return 0;
   481 }}
   482 
   483 static int
   484 METAL_UpdateViewport(SDL_Renderer * renderer)
   485 { @autoreleasepool {
   486     METAL_ActivateRenderer(renderer);
   487     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   488     MTLViewport viewport;
   489     viewport.originX = renderer->viewport.x;
   490     viewport.originY = renderer->viewport.y;
   491     viewport.width = renderer->viewport.w;
   492     viewport.height = renderer->viewport.h;
   493     viewport.znear = 0.0;
   494     viewport.zfar = 1.0;
   495     [data.mtlcmdencoder setViewport:viewport];
   496     return 0;
   497 }}
   498 
   499 static int
   500 METAL_UpdateClipRect(SDL_Renderer * renderer)
   501 { @autoreleasepool {
   502     METAL_ActivateRenderer(renderer);
   503     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   504     MTLScissorRect mtlrect;
   505     // !!! FIXME: should this care about the viewport?
   506     if (renderer->clipping_enabled) {
   507         const SDL_Rect *rect = &renderer->clip_rect;
   508         mtlrect.x = renderer->viewport.x + rect->x;
   509         mtlrect.y = renderer->viewport.x + rect->y;
   510         mtlrect.width = rect->w;
   511         mtlrect.height = rect->h;
   512     } else {
   513         mtlrect.x = renderer->viewport.x;
   514         mtlrect.y = renderer->viewport.y;
   515         mtlrect.width = renderer->viewport.w;
   516         mtlrect.height = renderer->viewport.h;
   517     }
   518     if (mtlrect.width > 0 && mtlrect.height > 0) {
   519         [data.mtlcmdencoder setScissorRect:mtlrect];
   520     }
   521     return 0;
   522 }}
   523 
   524 static int
   525 METAL_RenderClear(SDL_Renderer * renderer)
   526 { @autoreleasepool {
   527     // We could dump the command buffer and force a clear on a new one, but this will respect the scissor state.
   528     METAL_ActivateRenderer(renderer);
   529     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   530 
   531     // !!! FIXME: render color should live in a dedicated uniform buffer.
   532     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   533 
   534     MTLViewport viewport;  // RenderClear ignores the viewport state, though, so reset that.
   535     viewport.originX = viewport.originY = 0.0;
   536     viewport.width = data.mtlpassdesc.colorAttachments[0].texture.width;
   537     viewport.height = data.mtlpassdesc.colorAttachments[0].texture.height;
   538     viewport.znear = 0.0;
   539     viewport.zfar = 1.0;
   540 
   541     // Draw as if we're doing a simple filled rect to the screen now.
   542     [data.mtlcmdencoder setViewport:viewport];
   543     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
   544     [data.mtlcmdencoder setVertexBuffer:data.mtlbufclearverts offset:0 atIndex:0];
   545     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   546     [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:5];
   547 
   548     // reset the viewport for the rest of our usual drawing work...
   549     viewport.originX = renderer->viewport.x;
   550     viewport.originY = renderer->viewport.y;
   551     viewport.width = renderer->viewport.w;
   552     viewport.height = renderer->viewport.h;
   553     viewport.znear = 0.0;
   554     viewport.zfar = 1.0;
   555     [data.mtlcmdencoder setViewport:viewport];
   556 
   557     return 0;
   558 }}
   559 
   560 // normalize a value from 0.0f to len into -1.0f to 1.0f.
   561 static inline float
   562 norm(const float _val, const float len)
   563 {
   564     const float val = (_val < 0.0f) ? 0.0f : (_val > len) ? len : _val;
   565     return ((val / len) * 2.0f) - 1.0f;  // !!! FIXME: is this right?
   566 }
   567 
   568 // normalize a value from 0.0f to len into -1.0f to 1.0f.
   569 static inline float
   570 normy(const float _val, const float len)
   571 {
   572     return norm(len - ((_val < 0.0f) ? 0.0f : (_val > len) ? len : _val), len);
   573 }
   574 
   575 // normalize a value from 0.0f to len into 0.0f to 1.0f.
   576 static inline float
   577 normtex(const float _val, const float len)
   578 {
   579     const float val = (_val < 0.0f) ? 0.0f : (_val > len) ? len : _val;
   580     return (val / len);
   581 }
   582 
   583 static int
   584 DrawVerts(SDL_Renderer * renderer, const SDL_FPoint * points, int count,
   585           const MTLPrimitiveType primtype)
   586 { @autoreleasepool {
   587     METAL_ActivateRenderer(renderer);
   588 
   589     const size_t vertlen = (sizeof (float) * 2) * count;
   590     float *verts = SDL_malloc(vertlen);
   591     if (!verts) {
   592         return SDL_OutOfMemory();
   593     }
   594 
   595     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   596 
   597     // !!! FIXME: render color should live in a dedicated uniform buffer.
   598     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   599 
   600     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
   601     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   602 
   603     const float w = (float) data.mtlpassdesc.colorAttachments[0].texture.width;
   604     const float h = (float) data.mtlpassdesc.colorAttachments[0].texture.height;
   605 
   606     // !!! FIXME: we can convert this in the shader. This will save the malloc and for-loop, but we still need to upload.
   607     float *ptr = verts;
   608     for (int i = 0; i < count; i++, points++) {
   609         *ptr = norm(points->x, w); ptr++;
   610         *ptr = normy(points->y, h); ptr++;
   611     }
   612 
   613     [data.mtlcmdencoder setVertexBytes:verts length:vertlen atIndex:0];
   614     [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
   615 
   616     SDL_free(verts);
   617     return 0;
   618 }}
   619 
   620 static int
   621 METAL_RenderDrawPoints(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
   622 {
   623     return DrawVerts(renderer, points, count, MTLPrimitiveTypePoint);
   624 }
   625 
   626 static int
   627 METAL_RenderDrawLines(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
   628 {
   629     return DrawVerts(renderer, points, count, MTLPrimitiveTypeLineStrip);
   630 }
   631 
   632 static int
   633 METAL_RenderFillRects(SDL_Renderer * renderer, const SDL_FRect * rects, int count)
   634 { @autoreleasepool {
   635     METAL_ActivateRenderer(renderer);
   636     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   637 
   638     // !!! FIXME: render color should live in a dedicated uniform buffer.
   639     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   640 
   641     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
   642     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   643 
   644     const float w = (float) data.mtlpassdesc.colorAttachments[0].texture.width;
   645     const float h = (float) data.mtlpassdesc.colorAttachments[0].texture.height;
   646 
   647     for (int i = 0; i < count; i++, rects++) {
   648         if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) continue;
   649 
   650         const float verts[] = {
   651             norm(rects->x, w), normy(rects->y + rects->h, h),
   652             norm(rects->x, w), normy(rects->y, h),
   653             norm(rects->x + rects->w, w), normy(rects->y, h),
   654             norm(rects->x, w), normy(rects->y + rects->h, h),
   655             norm(rects->x + rects->w, w), normy(rects->y + rects->h, h)
   656         };
   657 
   658         [data.mtlcmdencoder setVertexBytes:verts length:sizeof(verts) atIndex:0];
   659         [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:5];
   660     }
   661 
   662     return 0;
   663 }}
   664 
   665 static int
   666 METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
   667               const SDL_Rect * srcrect, const SDL_FRect * dstrect)
   668 { @autoreleasepool {
   669     METAL_ActivateRenderer(renderer);
   670     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   671     id<MTLTexture> mtltexture = (__bridge id<MTLTexture>) texture->driverdata;
   672     const float w = (float) data.mtlpassdesc.colorAttachments[0].texture.width;
   673     const float h = (float) data.mtlpassdesc.colorAttachments[0].texture.height;
   674     const float texw = (float) mtltexture.width;
   675     const float texh = (float) mtltexture.height;
   676 
   677     const float xy[] = {
   678         norm(dstrect->x, w), normy(dstrect->y + dstrect->h, h),
   679         norm(dstrect->x, w), normy(dstrect->y, h),
   680         norm(dstrect->x + dstrect->w, w), normy(dstrect->y, h),
   681         norm(dstrect->x, w), normy(dstrect->y + dstrect->h, h),
   682         norm(dstrect->x + dstrect->w, w), normy(dstrect->y + dstrect->h, h)
   683     };
   684 
   685     const float uv[] = {
   686         normtex(srcrect->x, texw), normtex(srcrect->y + srcrect->h, texh),
   687         normtex(srcrect->x, texw), normtex(srcrect->y, texh),
   688         normtex(srcrect->x + srcrect->w, texw), normtex(srcrect->y, texh),
   689         normtex(srcrect->x, texw), normtex(srcrect->y + srcrect->h, texh),
   690         normtex(srcrect->x + srcrect->w, texw), normtex(srcrect->y + srcrect->h, texh)
   691     };
   692 
   693     float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
   694     if (texture->modMode) {
   695         color[0] = ((float)texture->r) / 255.0f;
   696         color[1] = ((float)texture->g) / 255.0f;
   697         color[2] = ((float)texture->b) / 255.0f;
   698         color[3] = ((float)texture->a) / 255.0f;
   699     }
   700 
   701     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelinecopy, texture->blendMode)];
   702     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   703     [data.mtlcmdencoder setFragmentTexture:mtltexture atIndex:0];
   704     [data.mtlcmdencoder setVertexBytes:xy length:sizeof(xy) atIndex:0];
   705     [data.mtlcmdencoder setVertexBytes:uv length:sizeof(uv) atIndex:1];
   706     [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:5];
   707 
   708     return 0;
   709 }}
   710 
   711 static int
   712 METAL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture,
   713               const SDL_Rect * srcrect, const SDL_FRect * dstrect,
   714               const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
   715 {
   716     return SDL_Unsupported();  // !!! FIXME
   717 }
   718 
   719 static int
   720 METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
   721                     Uint32 pixel_format, void * pixels, int pitch)
   722 { @autoreleasepool {
   723     METAL_ActivateRenderer(renderer);
   724     // !!! FIXME: this probably needs to commit the current command buffer, and probably waitUntilCompleted
   725     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   726     MTLRenderPassColorAttachmentDescriptor *colorAttachment = data.mtlpassdesc.colorAttachments[0];
   727     id<MTLTexture> mtltexture = colorAttachment.texture;
   728     MTLRegion mtlregion;
   729 
   730     mtlregion.origin.x = rect->x;
   731     mtlregion.origin.y = rect->y;
   732     mtlregion.origin.z = 0;
   733     mtlregion.size.width = rect->w;
   734     mtlregion.size.height = rect->w;
   735     mtlregion.size.depth = 1;
   736 
   737     // we only do BGRA8 or RGBA8 at the moment, so 4 will do.
   738     const int temp_pitch = rect->w * 4;
   739     void *temp_pixels = SDL_malloc(temp_pitch * rect->h);
   740     if (!temp_pixels) {
   741         return SDL_OutOfMemory();
   742     }
   743 
   744     [mtltexture getBytes:temp_pixels bytesPerRow:temp_pitch fromRegion:mtlregion mipmapLevel:0];
   745 
   746     const Uint32 temp_format = (mtltexture.pixelFormat == MTLPixelFormatBGRA8Unorm) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_ABGR8888;
   747     const int status = SDL_ConvertPixels(rect->w, rect->h, temp_format, temp_pixels, temp_pitch, pixel_format, pixels, pitch);
   748     SDL_free(temp_pixels);
   749     return status;
   750 }}
   751 
   752 static void
   753 METAL_RenderPresent(SDL_Renderer * renderer)
   754 { @autoreleasepool {
   755     METAL_ActivateRenderer(renderer);
   756     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   757 
   758     [data.mtlcmdencoder endEncoding];
   759     [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
   760     [data.mtlcmdbuffer commit];
   761     data.mtlcmdencoder = nil;
   762     data.mtlcmdbuffer = nil;
   763     data.mtlbackbuffer = nil;
   764     data.beginScene = YES;
   765 }}
   766 
   767 static void
   768 METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture)
   769 { @autoreleasepool {
   770     id<MTLTexture> mtltexture = CFBridgingRelease(texture->driverdata);
   771 #if !__has_feature(objc_arc)
   772     [mtltexture release];
   773 #endif
   774     texture->driverdata = NULL;
   775 }}
   776 
   777 static void
   778 METAL_DestroyRenderer(SDL_Renderer * renderer)
   779 { @autoreleasepool {
   780     if (renderer->driverdata) {
   781         METAL_RenderData *data = CFBridgingRelease(renderer->driverdata);
   782 
   783         if (data.mtlcmdencoder != nil) {
   784             [data.mtlcmdencoder endEncoding];
   785         }
   786 
   787 #if !__has_feature(objc_arc)
   788         [data.mtlbackbuffer release];
   789         [data.mtlcmdencoder release];
   790         [data.mtlcmdbuffer release];
   791         [data.mtlcmdqueue release];
   792         for (int i = 0; i < 4; i++) {
   793             [data.mtlpipelineprims[i] release];
   794             [data.mtlpipelinecopy[i] release];
   795         }
   796         [data.mtlpipelineprims release];
   797         [data.mtlpipelinecopy release];
   798         [data.mtlbufclearverts release];
   799         [data.mtllibrary release];
   800         [data.mtldevice release];
   801         [data.mtlpassdesc release];
   802         [data.mtllayer release];
   803 #endif
   804     }
   805 
   806     SDL_free(renderer);
   807 }}
   808 
   809 void *METAL_GetMetalLayer(SDL_Renderer * renderer)
   810 { @autoreleasepool {
   811     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   812     return (__bridge void*)data.mtllayer;
   813 }}
   814 
   815 void *METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
   816 { @autoreleasepool {
   817     METAL_ActivateRenderer(renderer);
   818     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   819     return (__bridge void*)data.mtlcmdencoder;
   820 }}
   821 
   822 #endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */
   823 
   824 /* vi: set ts=4 sw=4 expandtab: */