Pārlūkot izejas kodu

render: get rid of the predeclared functions in the GL and Metal renderers.

(others to come as I continue to update render backends!)
Ryan C. Gordon 6 gadi atpakaļ
vecāks
revīzija
c01da21756
2 mainītis faili ar 853 papildinājumiem un 925 dzēšanām
  1. 642 680
      src/render/metal/SDL_render_metal.m
  2. 211 245
      src/render/opengl/SDL_render_gl.c

+ 642 - 680
src/render/metal/SDL_render_metal.m

@@ -46,62 +46,6 @@
 
 /* Apple Metal renderer implementation */
 
-static SDL_Renderer *METAL_CreateRenderer(SDL_Window * window, Uint32 flags);
-static void METAL_WindowEvent(SDL_Renderer * renderer,
-                           const SDL_WindowEvent *event);
-static int METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h);
-static SDL_bool METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode);
-static int METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture);
-static int METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
-                            const SDL_Rect * rect, const void *pixels,
-                            int pitch);
-static int METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
-                               const SDL_Rect * rect,
-                               const Uint8 *Yplane, int Ypitch,
-                               const Uint8 *Uplane, int Upitch,
-                               const Uint8 *Vplane, int Vpitch);
-static int METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
-                          const SDL_Rect * rect, void **pixels, int *pitch);
-static void METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture);
-static int METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture);
-static int METAL_QueueSetViewport(SDL_Renderer * renderer, SDL_RenderCommand *cmd);
-static int METAL_QueueSetDrawColor(SDL_Renderer * renderer, SDL_RenderCommand *cmd);
-static int METAL_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points,
-                             int count);
-static int METAL_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects,
-                            int count);
-static int METAL_QueueCopy(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
-                       const SDL_Rect * srcrect, const SDL_FRect * dstrect);
-static int METAL_QueueCopyEx(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
-                        const SDL_Rect * srcquad, const SDL_FRect * dstrect,
-                        const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip);
-static int METAL_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize);
-static int METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
-                               Uint32 pixel_format, void * pixels, int pitch);
-static void METAL_RenderPresent(SDL_Renderer * renderer);
-static void METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture);
-static void METAL_DestroyRenderer(SDL_Renderer * renderer);
-static void *METAL_GetMetalLayer(SDL_Renderer * renderer);
-static void *METAL_GetMetalCommandEncoder(SDL_Renderer * renderer);
-
-SDL_RenderDriver METAL_RenderDriver = {
-    METAL_CreateRenderer,
-    {
-        "metal",
-        (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
-        6,
-        {
-            SDL_PIXELFORMAT_ARGB8888,
-            SDL_PIXELFORMAT_ABGR8888,
-            SDL_PIXELFORMAT_YV12,
-            SDL_PIXELFORMAT_IYUV,
-            SDL_PIXELFORMAT_NV12,
-            SDL_PIXELFORMAT_NV21
-        },
-    0, 0,
-    }
-};
-
 /* macOS requires constants in a buffer to have a 256 byte alignment. */
 #ifdef __MACOSX__
 #define CONSTANT_ALIGN 256
@@ -456,716 +400,471 @@ ChoosePipelineState(METAL_RenderData *data, METAL_ShaderPipelines *pipelines, SD
     return MakePipelineState(data, cache, [NSString stringWithFormat:@" (blend=custom 0x%x)", blendmode], blendmode);
 }
 
-static SDL_Renderer *
-METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
-{ @autoreleasepool {
-    SDL_Renderer *renderer = NULL;
-    METAL_RenderData *data = NULL;
-    id<MTLDevice> mtldevice = nil;
-    SDL_SysWMinfo syswm;
+static void
+METAL_ActivateRenderCommandEncoder(SDL_Renderer * renderer, MTLLoadAction load, MTLClearColor *clear_color)
+{
+    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
 
-    SDL_VERSION(&syswm.version);
-    if (!SDL_GetWindowWMInfo(window, &syswm)) {
-        return NULL;
-    }
+    /* Our SetRenderTarget just signals that the next render operation should
+     * set up a new render pass. This is where that work happens. */
+    if (data.mtlcmdencoder == nil) {
+        id<MTLTexture> mtltexture = nil;
 
-    if (IsMetalAvailable(&syswm) == -1) {
-        return NULL;
-    }
+        if (renderer->target != NULL) {
+            METAL_TextureData *texdata = (__bridge METAL_TextureData *)renderer->target->driverdata;
+            mtltexture = texdata.mtltexture;
+        } else {
+            if (data.mtlbackbuffer == nil) {
+                /* The backbuffer's contents aren't guaranteed to persist after
+                 * presenting, so we can leave it undefined when loading it. */
+                data.mtlbackbuffer = [data.mtllayer nextDrawable];
+                if (load == MTLLoadActionLoad) {
+                    load = MTLLoadActionDontCare;
+                }
+            }
+            mtltexture = data.mtlbackbuffer.texture;
+        }
 
-    renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer));
-    if (!renderer) {
-        SDL_OutOfMemory();
-        return NULL;
-    }
+        SDL_assert(mtltexture);
 
-    // !!! FIXME: MTLCopyAllDevices() can find other GPUs on macOS...
-    mtldevice = MTLCreateSystemDefaultDevice();
+        if (load == MTLLoadActionClear) {
+            SDL_assert(clear_color != NULL);
+            data.mtlpassdesc.colorAttachments[0].clearColor = *clear_color;
+        }
 
-    if (mtldevice == nil) {
-        SDL_free(renderer);
-        SDL_SetError("Failed to obtain Metal device");
-        return NULL;
-    }
+        data.mtlpassdesc.colorAttachments[0].loadAction = load;
+        data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
 
-    // !!! FIXME: error checking on all of this.
-    data = [[METAL_RenderData alloc] init];
+        data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
+        data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
 
-    renderer->driverdata = (void*)CFBridgingRetain(data);
-    renderer->window = window;
+        if (data.mtlbackbuffer != nil && mtltexture == data.mtlbackbuffer.texture) {
+            data.mtlcmdencoder.label = @"SDL metal renderer backbuffer";
+        } else {
+            data.mtlcmdencoder.label = @"SDL metal renderer render target";
+        }
 
-#ifdef __MACOSX__
-    NSView *view = Cocoa_Mtl_AddMetalView(window);
-    CAMetalLayer *layer = (CAMetalLayer *)[view layer];
+        data.activepipelines = ChooseShaderPipelines(data, mtltexture.pixelFormat);
 
-    layer.device = mtldevice;
+        // make sure this has a definite place in the queue. This way it will
+        //  execute reliably whether the app tries to make its own command buffers
+        //  or whatever. This means we can _always_ batch rendering commands!
+        [data.mtlcmdbuffer enqueue];
+    }
+}
 
-    //layer.colorspace = nil;
+static void
+METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
+{
+    if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED) {
+        METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
+        data.mtllayer.drawableSize = CGSizeMake(event->data1, event->data2);
+    }
 
-#else
-    UIView *view = UIKit_Mtl_AddMetalView(window);
-    CAMetalLayer *layer = (CAMetalLayer *)[view layer];
-#endif
+    if (event->event == SDL_WINDOWEVENT_SHOWN ||
+        event->event == SDL_WINDOWEVENT_HIDDEN) {
+        // !!! FIXME: write me
+    }
+}
 
-    // Necessary for RenderReadPixels.
-    layer.framebufferOnly = NO;
+static int
+METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
+{ @autoreleasepool {
+    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
+    if (w) {
+        *w = (int)data.mtllayer.drawableSize.width;
+    }
+    if (h) {
+        *h = (int)data.mtllayer.drawableSize.height;
+    }
+    return 0;
+}}
 
-    data.mtldevice = layer.device;
-    data.mtllayer = layer;
-    id<MTLCommandQueue> mtlcmdqueue = [data.mtldevice newCommandQueue];
-    data.mtlcmdqueue = mtlcmdqueue;
-    data.mtlcmdqueue.label = @"SDL Metal Renderer";
-    data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
+static SDL_bool
+METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode)
+{
+    SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode);
+    SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode);
+    SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode);
+    SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode);
+    SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode);
+    SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode);
 
-    NSError *err = nil;
+    if (GetBlendFactor(srcColorFactor) == invalidBlendFactor ||
+        GetBlendFactor(srcAlphaFactor) == invalidBlendFactor ||
+        GetBlendOperation(colorOperation) == invalidBlendOperation ||
+        GetBlendFactor(dstColorFactor) == invalidBlendFactor ||
+        GetBlendFactor(dstAlphaFactor) == invalidBlendFactor ||
+        GetBlendOperation(alphaOperation) == invalidBlendOperation) {
+        return SDL_FALSE;
+    }
+    return SDL_TRUE;
+}
 
-    // The compiled .metallib is embedded in a static array in a header file
-    // but the original shader source code is in SDL_shaders_metal.metal.
-    dispatch_data_t mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{});
-    id<MTLLibrary> mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
-    data.mtllibrary = mtllibrary;
-    SDL_assert(err == nil);
-#if !__has_feature(objc_arc)
-    dispatch_release(mtllibdata);
-#endif
-    data.mtllibrary.label = @"SDL Metal renderer shader library";
+static int
+METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
+{ @autoreleasepool {
+    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
+    MTLPixelFormat pixfmt;
 
-    /* Do some shader pipeline state loading up-front rather than on demand. */
-    data.pipelinescount = 0;
-    data.allpipelines = NULL;
-    ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm);
+    switch (texture->format) {
+        case SDL_PIXELFORMAT_ABGR8888:
+            pixfmt = MTLPixelFormatRGBA8Unorm;
+            break;
+        case SDL_PIXELFORMAT_ARGB8888:
+            pixfmt = MTLPixelFormatBGRA8Unorm;
+            break;
+        case SDL_PIXELFORMAT_IYUV:
+        case SDL_PIXELFORMAT_YV12:
+        case SDL_PIXELFORMAT_NV12:
+        case SDL_PIXELFORMAT_NV21:
+            pixfmt = MTLPixelFormatR8Unorm;
+            break;
+        default:
+            return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
+    }
 
-    MTLSamplerDescriptor *samplerdesc = [[MTLSamplerDescriptor alloc] init];
+    MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixfmt
+                                            width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO];
 
-    samplerdesc.minFilter = MTLSamplerMinMagFilterNearest;
-    samplerdesc.magFilter = MTLSamplerMinMagFilterNearest;
-    id<MTLSamplerState> mtlsamplernearest = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
-    data.mtlsamplernearest = mtlsamplernearest;
+    /* Not available in iOS 8. */
+    if ([mtltexdesc respondsToSelector:@selector(usage)]) {
+        if (texture->access == SDL_TEXTUREACCESS_TARGET) {
+            mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
+        } else {
+            mtltexdesc.usage = MTLTextureUsageShaderRead;
+        }
+    }
+    
+    id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
+    if (mtltexture == nil) {
+        return SDL_SetError("Texture allocation failed");
+    }
 
-    samplerdesc.minFilter = MTLSamplerMinMagFilterLinear;
-    samplerdesc.magFilter = MTLSamplerMinMagFilterLinear;
-    id<MTLSamplerState> mtlsamplerlinear = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
-    data.mtlsamplerlinear = mtlsamplerlinear;
+    id<MTLTexture> mtltexture_uv = nil;
 
-    /* Note: matrices are column major. */
-    float identitytransform[16] = {
-        1.0f, 0.0f, 0.0f, 0.0f,
-        0.0f, 1.0f, 0.0f, 0.0f,
-        0.0f, 0.0f, 1.0f, 0.0f,
-        0.0f, 0.0f, 0.0f, 1.0f,
-    };
+    BOOL yuv = (texture->format == SDL_PIXELFORMAT_IYUV) || (texture->format == SDL_PIXELFORMAT_YV12);
+    BOOL nv12 = (texture->format == SDL_PIXELFORMAT_NV12) || (texture->format == SDL_PIXELFORMAT_NV21);
 
-    float halfpixeltransform[16] = {
-        1.0f, 0.0f, 0.0f, 0.0f,
-        0.0f, 1.0f, 0.0f, 0.0f,
-        0.0f, 0.0f, 1.0f, 0.0f,
-        0.5f, 0.5f, 0.0f, 1.0f,
-    };
+    if (yuv) {
+        mtltexdesc.pixelFormat = MTLPixelFormatR8Unorm;
+        mtltexdesc.width = (texture->w + 1) / 2;
+        mtltexdesc.height = (texture->h + 1) / 2;
+        mtltexdesc.textureType = MTLTextureType2DArray;
+        mtltexdesc.arrayLength = 2;
+        mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
+    } else if (nv12) {
+        mtltexdesc.pixelFormat = MTLPixelFormatRG8Unorm;
+        mtltexdesc.width = (texture->w + 1) / 2;
+        mtltexdesc.height = (texture->h + 1) / 2;
+        mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
+    }
 
-    /* Metal pads float3s to 16 bytes. */
-    float decodetransformJPEG[4*4] = {
-        0.0, -0.501960814, -0.501960814, 0.0, /* offset */
-        1.0000,  0.0000,  1.4020, 0.0,        /* Rcoeff */
-        1.0000, -0.3441, -0.7141, 0.0,        /* Gcoeff */
-        1.0000,  1.7720,  0.0000, 0.0,        /* Bcoeff */
-    };
+    METAL_TextureData *texturedata = [[METAL_TextureData alloc] init];
+    if (texture->scaleMode == SDL_ScaleModeNearest) {
+        texturedata.mtlsampler = data.mtlsamplernearest;
+    } else {
+        texturedata.mtlsampler = data.mtlsamplerlinear;
+    }
+    texturedata.mtltexture = mtltexture;
+    texturedata.mtltexture_uv = mtltexture_uv;
 
-    float decodetransformBT601[4*4] = {
-        -0.0627451017, -0.501960814, -0.501960814, 0.0, /* offset */
-        1.1644,  0.0000,  1.5960, 0.0,                  /* Rcoeff */
-        1.1644, -0.3918, -0.8130, 0.0,                  /* Gcoeff */
-        1.1644,  2.0172,  0.0000, 0.0,                  /* Bcoeff */
-    };
+    texturedata.yuv = yuv;
+    texturedata.nv12 = nv12;
 
-    float decodetransformBT709[4*4] = {
-        0.0, -0.501960814, -0.501960814, 0.0, /* offset */
-        1.0000,  0.0000,  1.4020, 0.0,        /* Rcoeff */
-        1.0000, -0.3441, -0.7141, 0.0,        /* Gcoeff */
-        1.0000,  1.7720,  0.0000, 0.0,        /* Bcoeff */
-    };
+    if (yuv) {
+        texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV;
+    } else if (texture->format == SDL_PIXELFORMAT_NV12) {
+        texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV12;
+    } else if (texture->format == SDL_PIXELFORMAT_NV21) {
+        texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV21;
+    } else {
+        texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY;
+    }
 
-    float clearverts[6] = {0.0f, 0.0f,  0.0f, 2.0f,  2.0f, 0.0f};
+    if (yuv || nv12) {
+        size_t offset = 0;
+        SDL_YUV_CONVERSION_MODE mode = SDL_GetYUVConversionModeForResolution(texture->w, texture->h);
+        switch (mode) {
+            case SDL_YUV_CONVERSION_JPEG: offset = CONSTANTS_OFFSET_DECODE_JPEG; break;
+            case SDL_YUV_CONVERSION_BT601: offset = CONSTANTS_OFFSET_DECODE_BT601; break;
+            case SDL_YUV_CONVERSION_BT709: offset = CONSTANTS_OFFSET_DECODE_BT709; break;
+            default: offset = 0; break;
+        }
+        texturedata.conversionBufferOffset = offset;
+    }
 
-    id<MTLBuffer> mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared];
-    #if !__has_feature(objc_arc)
-    [mtlbufconstantstaging autorelease];
-    #endif
-    mtlbufconstantstaging.label = @"SDL constant staging data";
+    texture->driverdata = (void*)CFBridgingRetain(texturedata);
 
-    id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate];
-    data.mtlbufconstants = mtlbufconstants;
-    data.mtlbufconstants.label = @"SDL constant data";
+#if !__has_feature(objc_arc)
+    [texturedata release];
+    [mtltexture release];
+    [mtltexture_uv release];
+#endif
 
-    char *constantdata = [mtlbufconstantstaging contents];
-    SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform));
-    SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform));
-    SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_JPEG, decodetransformJPEG, sizeof(decodetransformJPEG));
-    SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601, decodetransformBT601, sizeof(decodetransformBT601));
-    SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709, decodetransformBT709, sizeof(decodetransformBT709));
-    SDL_memcpy(constantdata + CONSTANTS_OFFSET_CLEAR_VERTS, clearverts, sizeof(clearverts));
+    return 0;
+}}
 
-    id<MTLCommandBuffer> cmdbuffer = [data.mtlcmdqueue commandBuffer];
-    id<MTLBlitCommandEncoder> blitcmd = [cmdbuffer blitCommandEncoder];
+static int
+METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
+                 const SDL_Rect * rect, const void *pixels, int pitch)
+{ @autoreleasepool {
+    METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
 
-    [blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:data.mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH];
+    /* !!! FIXME: replaceRegion does not do any synchronization, so it might
+     * !!! FIXME: stomp on a previous frame's data that's currently being read
+     * !!! FIXME: by the GPU. */
+    [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
+                              mipmapLevel:0
+                                withBytes:pixels
+                              bytesPerRow:pitch];
 
-    [blitcmd endEncoding];
-    [cmdbuffer commit];
+    if (texturedata.yuv) {
+        int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0;
+        int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1;
 
-    // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
+        /* Skip to the correct offset into the next texture */
+        pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
+        [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
+                                     mipmapLevel:0
+                                           slice:Uslice
+                                       withBytes:pixels
+                                     bytesPerRow:(pitch + 1) / 2
+                                   bytesPerImage:0];
 
-    renderer->WindowEvent = METAL_WindowEvent;
-    renderer->GetOutputSize = METAL_GetOutputSize;
-    renderer->SupportsBlendMode = METAL_SupportsBlendMode;
-    renderer->CreateTexture = METAL_CreateTexture;
-    renderer->UpdateTexture = METAL_UpdateTexture;
-    renderer->UpdateTextureYUV = METAL_UpdateTextureYUV;
-    renderer->LockTexture = METAL_LockTexture;
-    renderer->UnlockTexture = METAL_UnlockTexture;
-    renderer->SetRenderTarget = METAL_SetRenderTarget;
-    renderer->QueueSetViewport = METAL_QueueSetViewport;
-    renderer->QueueSetDrawColor = METAL_QueueSetDrawColor;
-    renderer->QueueDrawPoints = METAL_QueueDrawPoints;
-    renderer->QueueDrawLines = METAL_QueueDrawPoints;  // lines and points queue the same way.
-    renderer->QueueFillRects = METAL_QueueFillRects;
-    renderer->QueueCopy = METAL_QueueCopy;
-    renderer->QueueCopyEx = METAL_QueueCopyEx;
-    renderer->RunCommandQueue = METAL_RunCommandQueue;
-    renderer->RenderReadPixels = METAL_RenderReadPixels;
-    renderer->RenderPresent = METAL_RenderPresent;
-    renderer->DestroyTexture = METAL_DestroyTexture;
-    renderer->DestroyRenderer = METAL_DestroyRenderer;
-    renderer->GetMetalLayer = METAL_GetMetalLayer;
-    renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
+        /* Skip to the correct offset into the next texture */
+        pixels = (const void*)((const Uint8*)pixels + ((rect->h + 1) / 2) * ((pitch + 1)/2));
+        [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
+                                     mipmapLevel:0
+                                           slice:Vslice
+                                       withBytes:pixels
+                                     bytesPerRow:(pitch + 1) / 2
+                                   bytesPerImage:0];
+    }
 
-    renderer->info = METAL_RenderDriver.info;
-    renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
+    if (texturedata.nv12) {
+        /* Skip to the correct offset into the next texture */
+        pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
+        [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
+                                     mipmapLevel:0
+                                           slice:0
+                                       withBytes:pixels
+                                     bytesPerRow:2 * ((pitch + 1) / 2)
+                                   bytesPerImage:0];
+    }
 
-    renderer->always_batch = SDL_TRUE;
+    return 0;
+}}
 
-#if defined(__MACOSX__) && defined(MAC_OS_X_VERSION_10_13)
-    if (@available(macOS 10.13, *)) {
-        data.mtllayer.displaySyncEnabled = (flags & SDL_RENDERER_PRESENTVSYNC) != 0;
-    } else
-#endif
-    {
-        renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
-    }
+static int
+METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
+                    const SDL_Rect * rect,
+                    const Uint8 *Yplane, int Ypitch,
+                    const Uint8 *Uplane, int Upitch,
+                    const Uint8 *Vplane, int Vpitch)
+{ @autoreleasepool {
+    METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
+    const int Uslice = 0;
+    const int Vslice = 1;
 
-    /* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
-    int maxtexsize = 4096;
-#if defined(__MACOSX__)
-    maxtexsize = 16384;
-#elif defined(__TVOS__)
-    maxtexsize = 8192;
-#ifdef __TVOS_11_0
-    if (@available(tvOS 11.0, *)) {
-        if ([mtldevice supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) {
-            maxtexsize = 16384;
-        }
-    }
-#endif
-#else
-#ifdef __IPHONE_11_0
-    if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) {
-        maxtexsize = 16384;
-    } else
-#endif
-#ifdef __IPHONE_10_0
-    if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) {
-        maxtexsize = 16384;
-    } else
-#endif
-    if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2] || [mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) {
-        maxtexsize = 8192;
-    } else {
-        maxtexsize = 4096;
+    /* Bail out if we're supposed to update an empty rectangle */
+    if (rect->w <= 0 || rect->h <= 0) {
+        return 0;
     }
-#endif
 
-    renderer->info.max_texture_width = maxtexsize;
-    renderer->info.max_texture_height = maxtexsize;
+    [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
+                              mipmapLevel:0
+                                withBytes:Yplane
+                              bytesPerRow:Ypitch];
 
-#if !__has_feature(objc_arc)
-    [mtlcmdqueue release];
-    [mtllibrary release];
-    [samplerdesc release];
-    [mtlsamplernearest release];
-    [mtlsamplerlinear release];
-    [mtlbufconstants release];
-    [view release];
-    [data release];
-    [mtldevice release];
-#endif
+    [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
+                                 mipmapLevel:0
+                                       slice:Uslice
+                                   withBytes:Uplane
+                                 bytesPerRow:Upitch
+                               bytesPerImage:0];
 
-    return renderer;
+    [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
+                                 mipmapLevel:0
+                                       slice:Vslice
+                                   withBytes:Vplane
+                                 bytesPerRow:Vpitch
+                               bytesPerImage:0];
+
+    return 0;
 }}
 
+static int
+METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
+               const SDL_Rect * rect, void **pixels, int *pitch)
+{
+    return SDL_Unsupported();   // !!! FIXME: write me
+}
+
 static void
-METAL_ActivateRenderCommandEncoder(SDL_Renderer * renderer, MTLLoadAction load, MTLClearColor *clear_color)
+METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture)
 {
-    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
+    // !!! FIXME: write me
+}
 
-    /* Our SetRenderTarget just signals that the next render operation should
-     * set up a new render pass. This is where that work happens. */
-    if (data.mtlcmdencoder == nil) {
-        id<MTLTexture> mtltexture = nil;
+static int
+METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
+{ @autoreleasepool {
+    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
 
-        if (renderer->target != NULL) {
-            METAL_TextureData *texdata = (__bridge METAL_TextureData *)renderer->target->driverdata;
-            mtltexture = texdata.mtltexture;
-        } else {
-            if (data.mtlbackbuffer == nil) {
-                /* The backbuffer's contents aren't guaranteed to persist after
-                 * presenting, so we can leave it undefined when loading it. */
-                data.mtlbackbuffer = [data.mtllayer nextDrawable];
-                if (load == MTLLoadActionLoad) {
-                    load = MTLLoadActionDontCare;
-                }
-            }
-            mtltexture = data.mtlbackbuffer.texture;
-        }
+    if (data.mtlcmdencoder) {
+        /* End encoding for the previous render target so we can set up a new
+         * render pass for this one. */
+        [data.mtlcmdencoder endEncoding];
+        [data.mtlcmdbuffer commit];
 
-        SDL_assert(mtltexture);
-
-        if (load == MTLLoadActionClear) {
-            SDL_assert(clear_color != NULL);
-            data.mtlpassdesc.colorAttachments[0].clearColor = *clear_color;
-        }
-
-        data.mtlpassdesc.colorAttachments[0].loadAction = load;
-        data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
-
-        data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
-        data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
+        data.mtlcmdencoder = nil;
+        data.mtlcmdbuffer = nil;
+    }
 
-        if (data.mtlbackbuffer != nil && mtltexture == data.mtlbackbuffer.texture) {
-            data.mtlcmdencoder.label = @"SDL metal renderer backbuffer";
-        } else {
-            data.mtlcmdencoder.label = @"SDL metal renderer render target";
-        }
+    /* We don't begin a new render pass right away - we delay it until an actual
+     * draw or clear happens. That way we can use hardware clears when possible,
+     * which are only available when beginning a new render pass. */
+    return 0;
+}}
 
-        data.activepipelines = ChooseShaderPipelines(data, mtltexture.pixelFormat);
 
-        // make sure this has a definite place in the queue. This way it will
-        //  execute reliably whether the app tries to make its own command buffers
-        //  or whatever. This means we can _always_ batch rendering commands!
-        [data.mtlcmdbuffer enqueue];
-    }
+// normalize a value from 0.0f to len into 0.0f to 1.0f.
+static inline float
+normtex(const float _val, const float len)
+{
+    return _val / len;
 }
 
-static void
-METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
+static int
+METAL_QueueSetViewport(SDL_Renderer * renderer, SDL_RenderCommand *cmd)
 {
-    if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED) {
-        METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
-        data.mtllayer.drawableSize = CGSizeMake(event->data1, event->data2);
+    float projection[4][4];    /* Prepare an orthographic projection */
+    const int w = cmd->data.viewport.rect.w;
+    const int h = cmd->data.viewport.rect.h;
+    const size_t matrixlen = sizeof (projection);
+    float *matrix = (float *) SDL_AllocateRenderVertices(renderer, matrixlen, CONSTANT_ALIGN, &cmd->data.viewport.first);
+    if (!matrix) {
+        return -1;
     }
 
-    if (event->event == SDL_WINDOWEVENT_SHOWN ||
-        event->event == SDL_WINDOWEVENT_HIDDEN) {
-        // !!! FIXME: write me
+    SDL_memset(projection, '\0', matrixlen);
+    if (w && h) {
+        projection[0][0] = 2.0f / w;
+        projection[1][1] = -2.0f / h;
+        projection[3][0] = -1.0f;
+        projection[3][1] = 1.0f;
+        projection[3][3] = 1.0f;
     }
+    SDL_memcpy(matrix, projection, matrixlen);
+
+    return 0;
 }
 
 static int
-METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
-{ @autoreleasepool {
-    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
-    if (w) {
-        *w = (int)data.mtllayer.drawableSize.width;
-    }
-    if (h) {
-        *h = (int)data.mtllayer.drawableSize.height;
+METAL_QueueSetDrawColor(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
+{
+    const size_t vertlen = sizeof (float) * 4;
+    float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, CONSTANT_ALIGN, &cmd->data.color.first);
+    if (!verts) {
+        return -1;
     }
+    *(verts++) = ((float)cmd->data.color.r) / 255.0f;
+    *(verts++) = ((float)cmd->data.color.g) / 255.0f;
+    *(verts++) = ((float)cmd->data.color.b) / 255.0f;
+    *(verts++) = ((float)cmd->data.color.a) / 255.0f;
     return 0;
-}}
+}
 
-static SDL_bool
-METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode)
+static int
+METAL_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count)
 {
-    SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode);
-    SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode);
-    SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode);
-    SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode);
-    SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode);
-    SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode);
-
-    if (GetBlendFactor(srcColorFactor) == invalidBlendFactor ||
-        GetBlendFactor(srcAlphaFactor) == invalidBlendFactor ||
-        GetBlendOperation(colorOperation) == invalidBlendOperation ||
-        GetBlendFactor(dstColorFactor) == invalidBlendFactor ||
-        GetBlendFactor(dstAlphaFactor) == invalidBlendFactor ||
-        GetBlendOperation(alphaOperation) == invalidBlendOperation) {
-        return SDL_FALSE;
+    const size_t vertlen = (sizeof (float) * 2) * count;
+    float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
+    if (!verts) {
+        return -1;
     }
-    return SDL_TRUE;
+    cmd->data.draw.count = count;
+    SDL_memcpy(verts, points, vertlen);
+    return 0;
 }
 
 static int
-METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
-{ @autoreleasepool {
-    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
-    MTLPixelFormat pixfmt;
-
-    switch (texture->format) {
-        case SDL_PIXELFORMAT_ABGR8888:
-            pixfmt = MTLPixelFormatRGBA8Unorm;
-            break;
-        case SDL_PIXELFORMAT_ARGB8888:
-            pixfmt = MTLPixelFormatBGRA8Unorm;
-            break;
-        case SDL_PIXELFORMAT_IYUV:
-        case SDL_PIXELFORMAT_YV12:
-        case SDL_PIXELFORMAT_NV12:
-        case SDL_PIXELFORMAT_NV21:
-            pixfmt = MTLPixelFormatR8Unorm;
-            break;
-        default:
-            return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
+METAL_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects, int count)
+{
+    // !!! FIXME: use an index buffer
+    const size_t vertlen = (sizeof (float) * 8) * count;
+    float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
+    if (!verts) {
+        return -1;
     }
 
-    MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixfmt
-                                            width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO];
+    cmd->data.draw.count = count;
 
-    /* Not available in iOS 8. */
-    if ([mtltexdesc respondsToSelector:@selector(usage)]) {
-        if (texture->access == SDL_TEXTUREACCESS_TARGET) {
-            mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
+    for (int i = 0; i < count; i++, rects++) {
+        if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) {
+            cmd->data.draw.count--;
         } else {
-            mtltexdesc.usage = MTLTextureUsageShaderRead;
+            *(verts++) = rects->x;
+            *(verts++) = rects->y + rects->h;
+            *(verts++) = rects->x;
+            *(verts++) = rects->y;
+            *(verts++) = rects->x + rects->w;
+            *(verts++) = rects->y + rects->h;
+            *(verts++) = rects->x + rects->w;
+            *(verts++) = rects->y;
         }
     }
-    
-    id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
-    if (mtltexture == nil) {
-        return SDL_SetError("Texture allocation failed");
-    }
-
-    id<MTLTexture> mtltexture_uv = nil;
-
-    BOOL yuv = (texture->format == SDL_PIXELFORMAT_IYUV) || (texture->format == SDL_PIXELFORMAT_YV12);
-    BOOL nv12 = (texture->format == SDL_PIXELFORMAT_NV12) || (texture->format == SDL_PIXELFORMAT_NV21);
-
-    if (yuv) {
-        mtltexdesc.pixelFormat = MTLPixelFormatR8Unorm;
-        mtltexdesc.width = (texture->w + 1) / 2;
-        mtltexdesc.height = (texture->h + 1) / 2;
-        mtltexdesc.textureType = MTLTextureType2DArray;
-        mtltexdesc.arrayLength = 2;
-        mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
-    } else if (nv12) {
-        mtltexdesc.pixelFormat = MTLPixelFormatRG8Unorm;
-        mtltexdesc.width = (texture->w + 1) / 2;
-        mtltexdesc.height = (texture->h + 1) / 2;
-        mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
-    }
 
-    METAL_TextureData *texturedata = [[METAL_TextureData alloc] init];
-    if (texture->scaleMode == SDL_ScaleModeNearest) {
-        texturedata.mtlsampler = data.mtlsamplernearest;
-    } else {
-        texturedata.mtlsampler = data.mtlsamplerlinear;
+    if (cmd->data.draw.count == 0) {
+        cmd->command = SDL_RENDERCMD_NO_OP;  // nothing to do, just skip this one later.
     }
-    texturedata.mtltexture = mtltexture;
-    texturedata.mtltexture_uv = mtltexture_uv;
 
-    texturedata.yuv = yuv;
-    texturedata.nv12 = nv12;
+    return 0;
+}
 
-    if (yuv) {
-        texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV;
-    } else if (texture->format == SDL_PIXELFORMAT_NV12) {
-        texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV12;
-    } else if (texture->format == SDL_PIXELFORMAT_NV21) {
-        texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV21;
-    } else {
-        texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY;
+static int
+METAL_QueueCopy(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
+                const SDL_Rect * srcrect, const SDL_FRect * dstrect)
+{
+    METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
+    const float texw = (float) texturedata.mtltexture.width;
+    const float texh = (float) texturedata.mtltexture.height;
+    // !!! FIXME: use an index buffer
+    const size_t vertlen = (sizeof (float) * 16);
+    float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
+    if (!verts) {
+        return -1;
     }
 
-    if (yuv || nv12) {
-        size_t offset = 0;
-        SDL_YUV_CONVERSION_MODE mode = SDL_GetYUVConversionModeForResolution(texture->w, texture->h);
-        switch (mode) {
-            case SDL_YUV_CONVERSION_JPEG: offset = CONSTANTS_OFFSET_DECODE_JPEG; break;
-            case SDL_YUV_CONVERSION_BT601: offset = CONSTANTS_OFFSET_DECODE_BT601; break;
-            case SDL_YUV_CONVERSION_BT709: offset = CONSTANTS_OFFSET_DECODE_BT709; break;
-            default: offset = 0; break;
-        }
-        texturedata.conversionBufferOffset = offset;
-    }
+    cmd->data.draw.count = 1;
 
-    texture->driverdata = (void*)CFBridgingRetain(texturedata);
+    *(verts++) = dstrect->x;
+    *(verts++) = dstrect->y + dstrect->h;
+    *(verts++) = dstrect->x;
+    *(verts++) = dstrect->y;
+    *(verts++) = dstrect->x + dstrect->w;
+    *(verts++) = dstrect->y + dstrect->h;
+    *(verts++) = dstrect->x + dstrect->w;
+    *(verts++) = dstrect->y;
 
-#if !__has_feature(objc_arc)
-    [texturedata release];
-    [mtltexture release];
-    [mtltexture_uv release];
-#endif
+    *(verts++) = normtex(srcrect->x, texw);
+    *(verts++) = normtex(srcrect->y + srcrect->h, texh);
+    *(verts++) = normtex(srcrect->x, texw);
+    *(verts++) = normtex(srcrect->y, texh);
+    *(verts++) = normtex(srcrect->x + srcrect->w, texw);
+    *(verts++) = normtex(srcrect->y + srcrect->h, texh);
+    *(verts++) = normtex(srcrect->x + srcrect->w, texw);
+    *(verts++) = normtex(srcrect->y, texh);
 
     return 0;
-}}
+}
 
 static int
-METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
-                 const SDL_Rect * rect, const void *pixels, int pitch)
-{ @autoreleasepool {
-    METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
-
-    /* !!! FIXME: replaceRegion does not do any synchronization, so it might
-     * !!! FIXME: stomp on a previous frame's data that's currently being read
-     * !!! FIXME: by the GPU. */
-    [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
-                              mipmapLevel:0
-                                withBytes:pixels
-                              bytesPerRow:pitch];
-
-    if (texturedata.yuv) {
-        int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0;
-        int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1;
-
-        /* Skip to the correct offset into the next texture */
-        pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
-        [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
-                                     mipmapLevel:0
-                                           slice:Uslice
-                                       withBytes:pixels
-                                     bytesPerRow:(pitch + 1) / 2
-                                   bytesPerImage:0];
-
-        /* Skip to the correct offset into the next texture */
-        pixels = (const void*)((const Uint8*)pixels + ((rect->h + 1) / 2) * ((pitch + 1)/2));
-        [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
-                                     mipmapLevel:0
-                                           slice:Vslice
-                                       withBytes:pixels
-                                     bytesPerRow:(pitch + 1) / 2
-                                   bytesPerImage:0];
-    }
-
-    if (texturedata.nv12) {
-        /* Skip to the correct offset into the next texture */
-        pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
-        [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
-                                     mipmapLevel:0
-                                           slice:0
-                                       withBytes:pixels
-                                     bytesPerRow:2 * ((pitch + 1) / 2)
-                                   bytesPerImage:0];
-    }
-
-    return 0;
-}}
-
-static int
-METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
-                    const SDL_Rect * rect,
-                    const Uint8 *Yplane, int Ypitch,
-                    const Uint8 *Uplane, int Upitch,
-                    const Uint8 *Vplane, int Vpitch)
-{ @autoreleasepool {
-    METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
-    const int Uslice = 0;
-    const int Vslice = 1;
-
-    /* Bail out if we're supposed to update an empty rectangle */
-    if (rect->w <= 0 || rect->h <= 0) {
-        return 0;
-    }
-
-    [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
-                              mipmapLevel:0
-                                withBytes:Yplane
-                              bytesPerRow:Ypitch];
-
-    [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
-                                 mipmapLevel:0
-                                       slice:Uslice
-                                   withBytes:Uplane
-                                 bytesPerRow:Upitch
-                               bytesPerImage:0];
-
-    [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
-                                 mipmapLevel:0
-                                       slice:Vslice
-                                   withBytes:Vplane
-                                 bytesPerRow:Vpitch
-                               bytesPerImage:0];
-
-    return 0;
-}}
-
-static int
-METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
-               const SDL_Rect * rect, void **pixels, int *pitch)
-{
-    return SDL_Unsupported();   // !!! FIXME: write me
-}
-
-static void
-METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture)
-{
-    // !!! FIXME: write me
-}
-
-static int
-METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
-{ @autoreleasepool {
-    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
-
-    if (data.mtlcmdencoder) {
-        /* End encoding for the previous render target so we can set up a new
-         * render pass for this one. */
-        [data.mtlcmdencoder endEncoding];
-        [data.mtlcmdbuffer commit];
-
-        data.mtlcmdencoder = nil;
-        data.mtlcmdbuffer = nil;
-    }
-
-    /* We don't begin a new render pass right away - we delay it until an actual
-     * draw or clear happens. That way we can use hardware clears when possible,
-     * which are only available when beginning a new render pass. */
-    return 0;
-}}
-
-
-// normalize a value from 0.0f to len into 0.0f to 1.0f.
-static inline float
-normtex(const float _val, const float len)
-{
-    return _val / len;
-}
-
-static int
-METAL_QueueSetViewport(SDL_Renderer * renderer, SDL_RenderCommand *cmd)
-{
-    float projection[4][4];    /* Prepare an orthographic projection */
-    const int w = cmd->data.viewport.rect.w;
-    const int h = cmd->data.viewport.rect.h;
-    const size_t matrixlen = sizeof (projection);
-    float *matrix = (float *) SDL_AllocateRenderVertices(renderer, matrixlen, CONSTANT_ALIGN, &cmd->data.viewport.first);
-    if (!matrix) {
-        return -1;
-    }
-
-    SDL_memset(projection, '\0', matrixlen);
-    if (w && h) {
-        projection[0][0] = 2.0f / w;
-        projection[1][1] = -2.0f / h;
-        projection[3][0] = -1.0f;
-        projection[3][1] = 1.0f;
-        projection[3][3] = 1.0f;
-    }
-    SDL_memcpy(matrix, projection, matrixlen);
-
-    return 0;
-}
-
-static int
-METAL_QueueSetDrawColor(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
-{
-    const size_t vertlen = sizeof (float) * 4;
-    float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, CONSTANT_ALIGN, &cmd->data.color.first);
-    if (!verts) {
-        return -1;
-    }
-    *(verts++) = ((float)cmd->data.color.r) / 255.0f;
-    *(verts++) = ((float)cmd->data.color.g) / 255.0f;
-    *(verts++) = ((float)cmd->data.color.b) / 255.0f;
-    *(verts++) = ((float)cmd->data.color.a) / 255.0f;
-    return 0;
-}
-
-static int
-METAL_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count)
-{
-    const size_t vertlen = (sizeof (float) * 2) * count;
-    float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
-    if (!verts) {
-        return -1;
-    }
-    cmd->data.draw.count = count;
-    SDL_memcpy(verts, points, vertlen);
-    return 0;
-}
-
-static int
-METAL_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects, int count)
-{
-    // !!! FIXME: use an index buffer
-    const size_t vertlen = (sizeof (float) * 8) * count;
-    float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
-    if (!verts) {
-        return -1;
-    }
-
-    cmd->data.draw.count = count;
-
-    for (int i = 0; i < count; i++, rects++) {
-        if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) {
-            cmd->data.draw.count--;
-        } else {
-            *(verts++) = rects->x;
-            *(verts++) = rects->y + rects->h;
-            *(verts++) = rects->x;
-            *(verts++) = rects->y;
-            *(verts++) = rects->x + rects->w;
-            *(verts++) = rects->y + rects->h;
-            *(verts++) = rects->x + rects->w;
-            *(verts++) = rects->y;
-        }
-    }
-
-    if (cmd->data.draw.count == 0) {
-        cmd->command = SDL_RENDERCMD_NO_OP;  // nothing to do, just skip this one later.
-    }
-
-    return 0;
-}
-
-static int
-METAL_QueueCopy(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
-                const SDL_Rect * srcrect, const SDL_FRect * dstrect)
-{
-    METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
-    const float texw = (float) texturedata.mtltexture.width;
-    const float texh = (float) texturedata.mtltexture.height;
-    // !!! FIXME: use an index buffer
-    const size_t vertlen = (sizeof (float) * 16);
-    float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
-    if (!verts) {
-        return -1;
-    }
-
-    cmd->data.draw.count = 1;
-
-    *(verts++) = dstrect->x;
-    *(verts++) = dstrect->y + dstrect->h;
-    *(verts++) = dstrect->x;
-    *(verts++) = dstrect->y;
-    *(verts++) = dstrect->x + dstrect->w;
-    *(verts++) = dstrect->y + dstrect->h;
-    *(verts++) = dstrect->x + dstrect->w;
-    *(verts++) = dstrect->y;
-
-    *(verts++) = normtex(srcrect->x, texw);
-    *(verts++) = normtex(srcrect->y + srcrect->h, texh);
-    *(verts++) = normtex(srcrect->x, texw);
-    *(verts++) = normtex(srcrect->y, texh);
-    *(verts++) = normtex(srcrect->x + srcrect->w, texw);
-    *(verts++) = normtex(srcrect->y + srcrect->h, texh);
-    *(verts++) = normtex(srcrect->x + srcrect->w, texw);
-    *(verts++) = normtex(srcrect->y, texh);
-
-    return 0;
-}
-
-static int
-METAL_QueueCopyEx(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
-                  const SDL_Rect * srcquad, const SDL_FRect * dstrect,
-                  const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
-{
+METAL_QueueCopyEx(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
+                  const SDL_Rect * srcquad, const SDL_FRect * dstrect,
+                  const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
+{
     METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
     const float texw = (float) texturedata.mtltexture.width;
     const float texh = (float) texturedata.mtltexture.height;
@@ -1573,6 +1272,269 @@ METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
     return (__bridge void*)data.mtlcmdencoder;
 }}
 
+static SDL_Renderer *
+METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
+{ @autoreleasepool {
+    SDL_Renderer *renderer = NULL;
+    METAL_RenderData *data = NULL;
+    id<MTLDevice> mtldevice = nil;
+    SDL_SysWMinfo syswm;
+
+    SDL_VERSION(&syswm.version);
+    if (!SDL_GetWindowWMInfo(window, &syswm)) {
+        return NULL;
+    }
+
+    if (IsMetalAvailable(&syswm) == -1) {
+        return NULL;
+    }
+
+    renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer));
+    if (!renderer) {
+        SDL_OutOfMemory();
+        return NULL;
+    }
+
+    // !!! FIXME: MTLCopyAllDevices() can find other GPUs on macOS...
+    mtldevice = MTLCreateSystemDefaultDevice();
+
+    if (mtldevice == nil) {
+        SDL_free(renderer);
+        SDL_SetError("Failed to obtain Metal device");
+        return NULL;
+    }
+
+    // !!! FIXME: error checking on all of this.
+    data = [[METAL_RenderData alloc] init];
+
+    renderer->driverdata = (void*)CFBridgingRetain(data);
+    renderer->window = window;
+
+#ifdef __MACOSX__
+    NSView *view = Cocoa_Mtl_AddMetalView(window);
+    CAMetalLayer *layer = (CAMetalLayer *)[view layer];
+
+    layer.device = mtldevice;
+
+    //layer.colorspace = nil;
+
+#else
+    UIView *view = UIKit_Mtl_AddMetalView(window);
+    CAMetalLayer *layer = (CAMetalLayer *)[view layer];
+#endif
+
+    // Necessary for RenderReadPixels.
+    layer.framebufferOnly = NO;
+
+    data.mtldevice = layer.device;
+    data.mtllayer = layer;
+    id<MTLCommandQueue> mtlcmdqueue = [data.mtldevice newCommandQueue];
+    data.mtlcmdqueue = mtlcmdqueue;
+    data.mtlcmdqueue.label = @"SDL Metal Renderer";
+    data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
+
+    NSError *err = nil;
+
+    // The compiled .metallib is embedded in a static array in a header file
+    // but the original shader source code is in SDL_shaders_metal.metal.
+    dispatch_data_t mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{});
+    id<MTLLibrary> mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
+    data.mtllibrary = mtllibrary;
+    SDL_assert(err == nil);
+#if !__has_feature(objc_arc)
+    dispatch_release(mtllibdata);
+#endif
+    data.mtllibrary.label = @"SDL Metal renderer shader library";
+
+    /* Do some shader pipeline state loading up-front rather than on demand. */
+    data.pipelinescount = 0;
+    data.allpipelines = NULL;
+    ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm);
+
+    MTLSamplerDescriptor *samplerdesc = [[MTLSamplerDescriptor alloc] init];
+
+    samplerdesc.minFilter = MTLSamplerMinMagFilterNearest;
+    samplerdesc.magFilter = MTLSamplerMinMagFilterNearest;
+    id<MTLSamplerState> mtlsamplernearest = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
+    data.mtlsamplernearest = mtlsamplernearest;
+
+    samplerdesc.minFilter = MTLSamplerMinMagFilterLinear;
+    samplerdesc.magFilter = MTLSamplerMinMagFilterLinear;
+    id<MTLSamplerState> mtlsamplerlinear = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
+    data.mtlsamplerlinear = mtlsamplerlinear;
+
+    /* Note: matrices are column major. */
+    float identitytransform[16] = {
+        1.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, 1.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 1.0f, 0.0f,
+        0.0f, 0.0f, 0.0f, 1.0f,
+    };
+
+    float halfpixeltransform[16] = {
+        1.0f, 0.0f, 0.0f, 0.0f,
+        0.0f, 1.0f, 0.0f, 0.0f,
+        0.0f, 0.0f, 1.0f, 0.0f,
+        0.5f, 0.5f, 0.0f, 1.0f,
+    };
+
+    /* Metal pads float3s to 16 bytes. */
+    float decodetransformJPEG[4*4] = {
+        0.0, -0.501960814, -0.501960814, 0.0, /* offset */
+        1.0000,  0.0000,  1.4020, 0.0,        /* Rcoeff */
+        1.0000, -0.3441, -0.7141, 0.0,        /* Gcoeff */
+        1.0000,  1.7720,  0.0000, 0.0,        /* Bcoeff */
+    };
+
+    float decodetransformBT601[4*4] = {
+        -0.0627451017, -0.501960814, -0.501960814, 0.0, /* offset */
+        1.1644,  0.0000,  1.5960, 0.0,                  /* Rcoeff */
+        1.1644, -0.3918, -0.8130, 0.0,                  /* Gcoeff */
+        1.1644,  2.0172,  0.0000, 0.0,                  /* Bcoeff */
+    };
+
+    float decodetransformBT709[4*4] = {
+        0.0, -0.501960814, -0.501960814, 0.0, /* offset */
+        1.0000,  0.0000,  1.4020, 0.0,        /* Rcoeff */
+        1.0000, -0.3441, -0.7141, 0.0,        /* Gcoeff */
+        1.0000,  1.7720,  0.0000, 0.0,        /* Bcoeff */
+    };
+
+    float clearverts[6] = {0.0f, 0.0f,  0.0f, 2.0f,  2.0f, 0.0f};
+
+    id<MTLBuffer> mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared];
+    #if !__has_feature(objc_arc)
+    [mtlbufconstantstaging autorelease];
+    #endif
+    mtlbufconstantstaging.label = @"SDL constant staging data";
+
+    id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate];
+    data.mtlbufconstants = mtlbufconstants;
+    data.mtlbufconstants.label = @"SDL constant data";
+
+    char *constantdata = [mtlbufconstantstaging contents];
+    SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform));
+    SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform));
+    SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_JPEG, decodetransformJPEG, sizeof(decodetransformJPEG));
+    SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601, decodetransformBT601, sizeof(decodetransformBT601));
+    SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709, decodetransformBT709, sizeof(decodetransformBT709));
+    SDL_memcpy(constantdata + CONSTANTS_OFFSET_CLEAR_VERTS, clearverts, sizeof(clearverts));
+
+    id<MTLCommandBuffer> cmdbuffer = [data.mtlcmdqueue commandBuffer];
+    id<MTLBlitCommandEncoder> blitcmd = [cmdbuffer blitCommandEncoder];
+
+    [blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:data.mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH];
+
+    [blitcmd endEncoding];
+    [cmdbuffer commit];
+
+    // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
+
+    renderer->WindowEvent = METAL_WindowEvent;
+    renderer->GetOutputSize = METAL_GetOutputSize;
+    renderer->SupportsBlendMode = METAL_SupportsBlendMode;
+    renderer->CreateTexture = METAL_CreateTexture;
+    renderer->UpdateTexture = METAL_UpdateTexture;
+    renderer->UpdateTextureYUV = METAL_UpdateTextureYUV;
+    renderer->LockTexture = METAL_LockTexture;
+    renderer->UnlockTexture = METAL_UnlockTexture;
+    renderer->SetRenderTarget = METAL_SetRenderTarget;
+    renderer->QueueSetViewport = METAL_QueueSetViewport;
+    renderer->QueueSetDrawColor = METAL_QueueSetDrawColor;
+    renderer->QueueDrawPoints = METAL_QueueDrawPoints;
+    renderer->QueueDrawLines = METAL_QueueDrawPoints;  // lines and points queue the same way.
+    renderer->QueueFillRects = METAL_QueueFillRects;
+    renderer->QueueCopy = METAL_QueueCopy;
+    renderer->QueueCopyEx = METAL_QueueCopyEx;
+    renderer->RunCommandQueue = METAL_RunCommandQueue;
+    renderer->RenderReadPixels = METAL_RenderReadPixels;
+    renderer->RenderPresent = METAL_RenderPresent;
+    renderer->DestroyTexture = METAL_DestroyTexture;
+    renderer->DestroyRenderer = METAL_DestroyRenderer;
+    renderer->GetMetalLayer = METAL_GetMetalLayer;
+    renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
+
+    renderer->info = METAL_RenderDriver.info;
+    renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
+
+    renderer->always_batch = SDL_TRUE;
+
+#if defined(__MACOSX__) && defined(MAC_OS_X_VERSION_10_13)
+    if (@available(macOS 10.13, *)) {
+        data.mtllayer.displaySyncEnabled = (flags & SDL_RENDERER_PRESENTVSYNC) != 0;
+    } else
+#endif
+    {
+        renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
+    }
+
+    /* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
+    int maxtexsize = 4096;
+#if defined(__MACOSX__)
+    maxtexsize = 16384;
+#elif defined(__TVOS__)
+    maxtexsize = 8192;
+#ifdef __TVOS_11_0
+    if (@available(tvOS 11.0, *)) {
+        if ([mtldevice supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) {
+            maxtexsize = 16384;
+        }
+    }
+#endif
+#else
+#ifdef __IPHONE_11_0
+    if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) {
+        maxtexsize = 16384;
+    } else
+#endif
+#ifdef __IPHONE_10_0
+    if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) {
+        maxtexsize = 16384;
+    } else
+#endif
+    if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2] || [mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) {
+        maxtexsize = 8192;
+    } else {
+        maxtexsize = 4096;
+    }
+#endif
+
+    renderer->info.max_texture_width = maxtexsize;
+    renderer->info.max_texture_height = maxtexsize;
+
+#if !__has_feature(objc_arc)
+    [mtlcmdqueue release];
+    [mtllibrary release];
+    [samplerdesc release];
+    [mtlsamplernearest release];
+    [mtlsamplerlinear release];
+    [mtlbufconstants release];
+    [view release];
+    [data release];
+    [mtldevice release];
+#endif
+
+    return renderer;
+}}
+
+SDL_RenderDriver METAL_RenderDriver = {
+    METAL_CreateRenderer,
+    {
+        "metal",
+        (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
+        6,
+        {
+            SDL_PIXELFORMAT_ARGB8888,
+            SDL_PIXELFORMAT_ABGR8888,
+            SDL_PIXELFORMAT_YV12,
+            SDL_PIXELFORMAT_IYUV,
+            SDL_PIXELFORMAT_NV12,
+            SDL_PIXELFORMAT_NV21
+        },
+    0, 0,
+    }
+};
+
 #endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */
 
 /* vi: set ts=4 sw=4 expandtab: */

+ 211 - 245
src/render/opengl/SDL_render_gl.c

@@ -51,54 +51,6 @@ extern int SDL_RecreateWindow(SDL_Window * window, Uint32 flags);
 
 static const float inv255f = 1.0f / 255.0f;
 
-/* !!! FIXME: delete these predeclarations and just move the functions before their use. */
-static SDL_Renderer *GL_CreateRenderer(SDL_Window * window, Uint32 flags);
-static int GL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h);
-static SDL_bool GL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode);
-static int GL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture);
-static int GL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
-                            const SDL_Rect * rect, const void *pixels,
-                            int pitch);
-static int GL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
-                               const SDL_Rect * rect,
-                               const Uint8 *Yplane, int Ypitch,
-                               const Uint8 *Uplane, int Upitch,
-                               const Uint8 *Vplane, int Vpitch);
-static int GL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
-                          const SDL_Rect * rect, void **pixels, int *pitch);
-static void GL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture);
-static int GL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture);
-static int GL_QueueSetViewport(SDL_Renderer * renderer, SDL_RenderCommand *cmd);
-static int GL_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd,
-                              const SDL_FPoint * points, int count);
-static int GL_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd,
-                             const SDL_FRect * rects, int count);
-static int GL_QueueCopy(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture,
-                        const SDL_Rect *srcrect, const SDL_FRect *dstrect);
-static int GL_QueueCopyEx(SDL_Renderer *renderer, SDL_RenderCommand *cmd, SDL_Texture *texture,
-                          const SDL_Rect *srcquad, const SDL_FRect *dstrect,
-                          const double angle, const SDL_FPoint *center,
-                          const SDL_RendererFlip flip);
-static int GL_RunCommandQueue(SDL_Renderer *renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize);
-static int GL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
-                               Uint32 pixel_format, void * pixels, int pitch);
-static void GL_RenderPresent(SDL_Renderer * renderer);
-static void GL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture);
-static void GL_DestroyRenderer(SDL_Renderer * renderer);
-static int GL_BindTexture (SDL_Renderer * renderer, SDL_Texture *texture, float *texw, float *texh);
-static int GL_UnbindTexture (SDL_Renderer * renderer, SDL_Texture *texture);
-
-SDL_RenderDriver GL_RenderDriver = {
-    GL_CreateRenderer,
-    {
-     "opengl",
-     (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
-     1,
-     {SDL_PIXELFORMAT_ARGB8888},
-     0,
-     0}
-};
-
 typedef struct GL_FBOList GL_FBOList;
 
 struct GL_FBOList
@@ -343,203 +295,6 @@ GL_GetFBO(GL_RenderData *data, Uint32 w, Uint32 h)
     return result;
 }
 
-SDL_Renderer *
-GL_CreateRenderer(SDL_Window * window, Uint32 flags)
-{
-    SDL_Renderer *renderer;
-    GL_RenderData *data;
-    GLint value;
-    Uint32 window_flags;
-    int profile_mask = 0, major = 0, minor = 0;
-    SDL_bool changed_window = SDL_FALSE;
-
-    SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profile_mask);
-    SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major);
-    SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor);
-    
-    window_flags = SDL_GetWindowFlags(window);
-    if (!(window_flags & SDL_WINDOW_OPENGL) ||
-        profile_mask == SDL_GL_CONTEXT_PROFILE_ES || major != RENDERER_CONTEXT_MAJOR || minor != RENDERER_CONTEXT_MINOR) {
-
-        changed_window = SDL_TRUE;
-        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
-        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, RENDERER_CONTEXT_MAJOR);
-        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, RENDERER_CONTEXT_MINOR);
-
-        if (SDL_RecreateWindow(window, window_flags | SDL_WINDOW_OPENGL) < 0) {
-            goto error;
-        }
-    }
-
-    renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer));
-    if (!renderer) {
-        SDL_OutOfMemory();
-        goto error;
-    }
-
-    data = (GL_RenderData *) SDL_calloc(1, sizeof(*data));
-    if (!data) {
-        GL_DestroyRenderer(renderer);
-        SDL_OutOfMemory();
-        goto error;
-    }
-
-    renderer->GetOutputSize = GL_GetOutputSize;
-    renderer->SupportsBlendMode = GL_SupportsBlendMode;
-    renderer->CreateTexture = GL_CreateTexture;
-    renderer->UpdateTexture = GL_UpdateTexture;
-    renderer->UpdateTextureYUV = GL_UpdateTextureYUV;
-    renderer->LockTexture = GL_LockTexture;
-    renderer->UnlockTexture = GL_UnlockTexture;
-    renderer->SetRenderTarget = GL_SetRenderTarget;
-    renderer->QueueSetViewport = GL_QueueSetViewport;
-    renderer->QueueSetDrawColor = GL_QueueSetViewport;  /* SetViewport and SetDrawColor are (currently) no-ops. */
-    renderer->QueueDrawPoints = GL_QueueDrawPoints;
-    renderer->QueueDrawLines = GL_QueueDrawPoints;  /* lines and points queue vertices the same way. */
-    renderer->QueueFillRects = GL_QueueFillRects;
-    renderer->QueueCopy = GL_QueueCopy;
-    renderer->QueueCopyEx = GL_QueueCopyEx;
-    renderer->RunCommandQueue = GL_RunCommandQueue;
-    renderer->RenderReadPixels = GL_RenderReadPixels;
-    renderer->RenderPresent = GL_RenderPresent;
-    renderer->DestroyTexture = GL_DestroyTexture;
-    renderer->DestroyRenderer = GL_DestroyRenderer;
-    renderer->GL_BindTexture = GL_BindTexture;
-    renderer->GL_UnbindTexture = GL_UnbindTexture;
-    renderer->info = GL_RenderDriver.info;
-    renderer->info.flags = SDL_RENDERER_ACCELERATED;
-    renderer->driverdata = data;
-    renderer->window = window;
-
-    data->context = SDL_GL_CreateContext(window);
-    if (!data->context) {
-        GL_DestroyRenderer(renderer);
-        goto error;
-    }
-    if (SDL_GL_MakeCurrent(window, data->context) < 0) {
-        GL_DestroyRenderer(renderer);
-        goto error;
-    }
-
-    if (GL_LoadFunctions(data) < 0) {
-        GL_DestroyRenderer(renderer);
-        goto error;
-    }
-
-#ifdef __MACOSX__
-    /* Enable multi-threaded rendering */
-    /* Disabled until Ryan finishes his VBO/PBO code...
-       CGLEnable(CGLGetCurrentContext(), kCGLCEMPEngine);
-     */
-#endif
-
-    if (flags & SDL_RENDERER_PRESENTVSYNC) {
-        SDL_GL_SetSwapInterval(1);
-    } else {
-        SDL_GL_SetSwapInterval(0);
-    }
-    if (SDL_GL_GetSwapInterval() > 0) {
-        renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
-    }
-
-    /* Check for debug output support */
-    if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_FLAGS, &value) == 0 &&
-        (value & SDL_GL_CONTEXT_DEBUG_FLAG)) {
-        data->debug_enabled = SDL_TRUE;
-    }
-    if (data->debug_enabled && SDL_GL_ExtensionSupported("GL_ARB_debug_output")) {
-        PFNGLDEBUGMESSAGECALLBACKARBPROC glDebugMessageCallbackARBFunc = (PFNGLDEBUGMESSAGECALLBACKARBPROC) SDL_GL_GetProcAddress("glDebugMessageCallbackARB");
-
-        data->GL_ARB_debug_output_supported = SDL_TRUE;
-        data->glGetPointerv(GL_DEBUG_CALLBACK_FUNCTION_ARB, (GLvoid **)(char *)&data->next_error_callback);
-        data->glGetPointerv(GL_DEBUG_CALLBACK_USER_PARAM_ARB, &data->next_error_userparam);
-        glDebugMessageCallbackARBFunc(GL_HandleDebugMessage, renderer);
-
-        /* Make sure our callback is called when errors actually happen */
-        data->glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
-    }
-
-    data->textype = GL_TEXTURE_2D;
-    if (SDL_GL_ExtensionSupported("GL_ARB_texture_non_power_of_two")) {
-        data->GL_ARB_texture_non_power_of_two_supported = SDL_TRUE;
-    } else if (SDL_GL_ExtensionSupported("GL_ARB_texture_rectangle") ||
-               SDL_GL_ExtensionSupported("GL_EXT_texture_rectangle")) {
-        data->GL_ARB_texture_rectangle_supported = SDL_TRUE;
-        data->textype = GL_TEXTURE_RECTANGLE_ARB;
-    }
-    if (data->GL_ARB_texture_rectangle_supported) {
-        data->glGetIntegerv(GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB, &value);
-        renderer->info.max_texture_width = value;
-        renderer->info.max_texture_height = value;
-    } else {
-        data->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value);
-        renderer->info.max_texture_width = value;
-        renderer->info.max_texture_height = value;
-    }
-
-    /* Check for multitexture support */
-    if (SDL_GL_ExtensionSupported("GL_ARB_multitexture")) {
-        data->glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) SDL_GL_GetProcAddress("glActiveTextureARB");
-        if (data->glActiveTextureARB) {
-            data->GL_ARB_multitexture_supported = SDL_TRUE;
-            data->glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &data->num_texture_units);
-        }
-    }
-
-    /* Check for shader support */
-    if (SDL_GetHintBoolean(SDL_HINT_RENDER_OPENGL_SHADERS, SDL_TRUE)) {
-        data->shaders = GL_CreateShaderContext();
-    }
-    SDL_LogInfo(SDL_LOG_CATEGORY_RENDER, "OpenGL shaders: %s",
-                data->shaders ? "ENABLED" : "DISABLED");
-
-    /* We support YV12 textures using 3 textures and a shader */
-    if (data->shaders && data->num_texture_units >= 3) {
-        renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_YV12;
-        renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_IYUV;
-        renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_NV12;
-        renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_NV21;
-    }
-
-#ifdef __MACOSX__
-    renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_UYVY;
-#endif
-
-    if (SDL_GL_ExtensionSupported("GL_EXT_framebuffer_object")) {
-        data->GL_EXT_framebuffer_object_supported = SDL_TRUE;
-        data->glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC)
-            SDL_GL_GetProcAddress("glGenFramebuffersEXT");
-        data->glDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC)
-            SDL_GL_GetProcAddress("glDeleteFramebuffersEXT");
-        data->glFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)
-            SDL_GL_GetProcAddress("glFramebufferTexture2DEXT");
-        data->glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC)
-            SDL_GL_GetProcAddress("glBindFramebufferEXT");
-        data->glCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)
-            SDL_GL_GetProcAddress("glCheckFramebufferStatusEXT");
-        renderer->info.flags |= SDL_RENDERER_TARGETTEXTURE;
-    }
-    data->framebuffers = NULL;
-
-    /* Set up parameters for rendering */
-    data->glDisable(GL_DEPTH_TEST);
-    data->glDisable(GL_CULL_FACE);
-    /* This ended up causing video discrepancies between OpenGL and Direct3D */
-    /* data->glEnable(GL_LINE_SMOOTH); */
-
-    return renderer;
-
-error:
-    if (changed_window) {
-        /* Uh oh, better try to put it back... */
-        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile_mask);
-        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major);
-        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor);
-        SDL_RecreateWindow(window, window_flags);
-    }
-    return NULL;
-}
-
 static int
 GL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
 {
@@ -1692,6 +1447,217 @@ GL_UnbindTexture (SDL_Renderer * renderer, SDL_Texture *texture)
     return 0;
 }
 
+
+SDL_Renderer *
+GL_CreateRenderer(SDL_Window * window, Uint32 flags)
+{
+    SDL_Renderer *renderer;
+    GL_RenderData *data;
+    GLint value;
+    Uint32 window_flags;
+    int profile_mask = 0, major = 0, minor = 0;
+    SDL_bool changed_window = SDL_FALSE;
+
+    SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profile_mask);
+    SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major);
+    SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor);
+    
+    window_flags = SDL_GetWindowFlags(window);
+    if (!(window_flags & SDL_WINDOW_OPENGL) ||
+        profile_mask == SDL_GL_CONTEXT_PROFILE_ES || major != RENDERER_CONTEXT_MAJOR || minor != RENDERER_CONTEXT_MINOR) {
+
+        changed_window = SDL_TRUE;
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, RENDERER_CONTEXT_MAJOR);
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, RENDERER_CONTEXT_MINOR);
+
+        if (SDL_RecreateWindow(window, window_flags | SDL_WINDOW_OPENGL) < 0) {
+            goto error;
+        }
+    }
+
+    renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer));
+    if (!renderer) {
+        SDL_OutOfMemory();
+        goto error;
+    }
+
+    data = (GL_RenderData *) SDL_calloc(1, sizeof(*data));
+    if (!data) {
+        GL_DestroyRenderer(renderer);
+        SDL_OutOfMemory();
+        goto error;
+    }
+
+    renderer->GetOutputSize = GL_GetOutputSize;
+    renderer->SupportsBlendMode = GL_SupportsBlendMode;
+    renderer->CreateTexture = GL_CreateTexture;
+    renderer->UpdateTexture = GL_UpdateTexture;
+    renderer->UpdateTextureYUV = GL_UpdateTextureYUV;
+    renderer->LockTexture = GL_LockTexture;
+    renderer->UnlockTexture = GL_UnlockTexture;
+    renderer->SetRenderTarget = GL_SetRenderTarget;
+    renderer->QueueSetViewport = GL_QueueSetViewport;
+    renderer->QueueSetDrawColor = GL_QueueSetViewport;  /* SetViewport and SetDrawColor are (currently) no-ops. */
+    renderer->QueueDrawPoints = GL_QueueDrawPoints;
+    renderer->QueueDrawLines = GL_QueueDrawPoints;  /* lines and points queue vertices the same way. */
+    renderer->QueueFillRects = GL_QueueFillRects;
+    renderer->QueueCopy = GL_QueueCopy;
+    renderer->QueueCopyEx = GL_QueueCopyEx;
+    renderer->RunCommandQueue = GL_RunCommandQueue;
+    renderer->RenderReadPixels = GL_RenderReadPixels;
+    renderer->RenderPresent = GL_RenderPresent;
+    renderer->DestroyTexture = GL_DestroyTexture;
+    renderer->DestroyRenderer = GL_DestroyRenderer;
+    renderer->GL_BindTexture = GL_BindTexture;
+    renderer->GL_UnbindTexture = GL_UnbindTexture;
+    renderer->info = GL_RenderDriver.info;
+    renderer->info.flags = SDL_RENDERER_ACCELERATED;
+    renderer->driverdata = data;
+    renderer->window = window;
+
+    data->context = SDL_GL_CreateContext(window);
+    if (!data->context) {
+        GL_DestroyRenderer(renderer);
+        goto error;
+    }
+    if (SDL_GL_MakeCurrent(window, data->context) < 0) {
+        GL_DestroyRenderer(renderer);
+        goto error;
+    }
+
+    if (GL_LoadFunctions(data) < 0) {
+        GL_DestroyRenderer(renderer);
+        goto error;
+    }
+
+#ifdef __MACOSX__
+    /* Enable multi-threaded rendering */
+    /* Disabled until Ryan finishes his VBO/PBO code...
+       CGLEnable(CGLGetCurrentContext(), kCGLCEMPEngine);
+     */
+#endif
+
+    if (flags & SDL_RENDERER_PRESENTVSYNC) {
+        SDL_GL_SetSwapInterval(1);
+    } else {
+        SDL_GL_SetSwapInterval(0);
+    }
+    if (SDL_GL_GetSwapInterval() > 0) {
+        renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
+    }
+
+    /* Check for debug output support */
+    if (SDL_GL_GetAttribute(SDL_GL_CONTEXT_FLAGS, &value) == 0 &&
+        (value & SDL_GL_CONTEXT_DEBUG_FLAG)) {
+        data->debug_enabled = SDL_TRUE;
+    }
+    if (data->debug_enabled && SDL_GL_ExtensionSupported("GL_ARB_debug_output")) {
+        PFNGLDEBUGMESSAGECALLBACKARBPROC glDebugMessageCallbackARBFunc = (PFNGLDEBUGMESSAGECALLBACKARBPROC) SDL_GL_GetProcAddress("glDebugMessageCallbackARB");
+
+        data->GL_ARB_debug_output_supported = SDL_TRUE;
+        data->glGetPointerv(GL_DEBUG_CALLBACK_FUNCTION_ARB, (GLvoid **)(char *)&data->next_error_callback);
+        data->glGetPointerv(GL_DEBUG_CALLBACK_USER_PARAM_ARB, &data->next_error_userparam);
+        glDebugMessageCallbackARBFunc(GL_HandleDebugMessage, renderer);
+
+        /* Make sure our callback is called when errors actually happen */
+        data->glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
+    }
+
+    data->textype = GL_TEXTURE_2D;
+    if (SDL_GL_ExtensionSupported("GL_ARB_texture_non_power_of_two")) {
+        data->GL_ARB_texture_non_power_of_two_supported = SDL_TRUE;
+    } else if (SDL_GL_ExtensionSupported("GL_ARB_texture_rectangle") ||
+               SDL_GL_ExtensionSupported("GL_EXT_texture_rectangle")) {
+        data->GL_ARB_texture_rectangle_supported = SDL_TRUE;
+        data->textype = GL_TEXTURE_RECTANGLE_ARB;
+    }
+    if (data->GL_ARB_texture_rectangle_supported) {
+        data->glGetIntegerv(GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB, &value);
+        renderer->info.max_texture_width = value;
+        renderer->info.max_texture_height = value;
+    } else {
+        data->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value);
+        renderer->info.max_texture_width = value;
+        renderer->info.max_texture_height = value;
+    }
+
+    /* Check for multitexture support */
+    if (SDL_GL_ExtensionSupported("GL_ARB_multitexture")) {
+        data->glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) SDL_GL_GetProcAddress("glActiveTextureARB");
+        if (data->glActiveTextureARB) {
+            data->GL_ARB_multitexture_supported = SDL_TRUE;
+            data->glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &data->num_texture_units);
+        }
+    }
+
+    /* Check for shader support */
+    if (SDL_GetHintBoolean(SDL_HINT_RENDER_OPENGL_SHADERS, SDL_TRUE)) {
+        data->shaders = GL_CreateShaderContext();
+    }
+    SDL_LogInfo(SDL_LOG_CATEGORY_RENDER, "OpenGL shaders: %s",
+                data->shaders ? "ENABLED" : "DISABLED");
+
+    /* We support YV12 textures using 3 textures and a shader */
+    if (data->shaders && data->num_texture_units >= 3) {
+        renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_YV12;
+        renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_IYUV;
+        renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_NV12;
+        renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_NV21;
+    }
+
+#ifdef __MACOSX__
+    renderer->info.texture_formats[renderer->info.num_texture_formats++] = SDL_PIXELFORMAT_UYVY;
+#endif
+
+    if (SDL_GL_ExtensionSupported("GL_EXT_framebuffer_object")) {
+        data->GL_EXT_framebuffer_object_supported = SDL_TRUE;
+        data->glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC)
+            SDL_GL_GetProcAddress("glGenFramebuffersEXT");
+        data->glDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC)
+            SDL_GL_GetProcAddress("glDeleteFramebuffersEXT");
+        data->glFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)
+            SDL_GL_GetProcAddress("glFramebufferTexture2DEXT");
+        data->glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC)
+            SDL_GL_GetProcAddress("glBindFramebufferEXT");
+        data->glCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)
+            SDL_GL_GetProcAddress("glCheckFramebufferStatusEXT");
+        renderer->info.flags |= SDL_RENDERER_TARGETTEXTURE;
+    }
+    data->framebuffers = NULL;
+
+    /* Set up parameters for rendering */
+    data->glDisable(GL_DEPTH_TEST);
+    data->glDisable(GL_CULL_FACE);
+    /* This ended up causing video discrepancies between OpenGL and Direct3D */
+    /* data->glEnable(GL_LINE_SMOOTH); */
+
+    return renderer;
+
+error:
+    if (changed_window) {
+        /* Uh oh, better try to put it back... */
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile_mask);
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, major);
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minor);
+        SDL_RecreateWindow(window, window_flags);
+    }
+    return NULL;
+}
+
+
+SDL_RenderDriver GL_RenderDriver = {
+    GL_CreateRenderer,
+    {
+     "opengl",
+     (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
+     1,
+     {SDL_PIXELFORMAT_ARGB8888},
+     0,
+     0}
+};
+
+
 #endif /* SDL_VIDEO_RENDER_OGL && !SDL_RENDER_DISABLED */
 
 /* vi: set ts=4 sw=4 expandtab: */