Commit dc04f290a359ed0283e6703018ecb4e5427c7066

Sam Lantinga 2017-12-08T08:58:02

Defer getting the next drawable until we actually start rendering This works better for games where there may be a bunch of simulation logic that needs to be run before the next rendering pass, and prevents blocking if the next drawable is busy.

diff --git a/src/render/metal/SDL_render_metal.m b/src/render/metal/SDL_render_metal.m
index 157eac2..eb5ef5d 100644
--- a/src/render/metal/SDL_render_metal.m
+++ b/src/render/metal/SDL_render_metal.m
@@ -94,17 +94,18 @@ SDL_RenderDriver METAL_RenderDriver = {
 };
 
 @interface METAL_RenderData : NSObject
-    @property (atomic, retain) id<MTLDevice> mtldevice;
-    @property (atomic, retain) id<MTLCommandQueue> mtlcmdqueue;
-    @property (atomic, retain) id<MTLCommandBuffer> mtlcmdbuffer;
-    @property (atomic, retain) id<MTLRenderCommandEncoder> mtlcmdencoder;
-    @property (atomic, retain) id<MTLLibrary> mtllibrary;
-    @property (atomic, retain) id<CAMetalDrawable> mtlbackbuffer;
-    @property (atomic, retain) NSMutableArray *mtlpipelineprims;
-    @property (atomic, retain) NSMutableArray *mtlpipelinecopy;
-    @property (atomic, retain) id<MTLBuffer> mtlbufclearverts;
-    @property (atomic, retain) CAMetalLayer *mtllayer;
-    @property (atomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
+    @property (nonatomic, assign) BOOL beginScene;
+    @property (nonatomic, retain) id<MTLDevice> mtldevice;
+    @property (nonatomic, retain) id<MTLCommandQueue> mtlcmdqueue;
+    @property (nonatomic, retain) id<MTLCommandBuffer> mtlcmdbuffer;
+    @property (nonatomic, retain) id<MTLRenderCommandEncoder> mtlcmdencoder;
+    @property (nonatomic, retain) id<MTLLibrary> mtllibrary;
+    @property (nonatomic, retain) id<CAMetalDrawable> mtlbackbuffer;
+    @property (nonatomic, retain) NSMutableArray *mtlpipelineprims;
+    @property (nonatomic, retain) NSMutableArray *mtlpipelinecopy;
+    @property (nonatomic, retain) id<MTLBuffer> mtlbufclearverts;
+    @property (nonatomic, retain) CAMetalLayer *mtllayer;
+    @property (nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
 @end
 
 @implementation METAL_RenderData
@@ -139,7 +140,7 @@ MakePipelineState(METAL_RenderData *data, NSString *label, NSString *vertfn,
     MTLRenderPipelineDescriptor *mtlpipedesc = [[MTLRenderPipelineDescriptor alloc] init];
     mtlpipedesc.vertexFunction = mtlvertfn;
     mtlpipedesc.fragmentFunction = mtlfragfn;
-    mtlpipedesc.colorAttachments[0].pixelFormat = data.mtlbackbuffer.texture.pixelFormat;
+    mtlpipedesc.colorAttachments[0].pixelFormat = data.mtllayer.pixelFormat;
 
     switch (blendmode) {
         case SDL_BLENDMODE_BLEND:
@@ -236,6 +237,7 @@ METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
     }
 
     data = [[METAL_RenderData alloc] init];
+    data.beginScene = YES;
 
 #if __has_feature(objc_arc)
     renderer->driverdata = (void*)CFBridgingRetain(data);
@@ -281,22 +283,30 @@ METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
     data.mtllayer = layer;
     data.mtlcmdqueue = [data.mtldevice newCommandQueue];
     data.mtlcmdqueue.label = @"SDL Metal Renderer";
-
     data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];  // !!! FIXME: is this autoreleased?
 
-    // we don't specify a depth or stencil buffer because the render API doesn't currently use them.
-    MTLRenderPassColorAttachmentDescriptor *colorAttachment = data.mtlpassdesc.colorAttachments[0];
-    data.mtlbackbuffer = [data.mtllayer nextDrawable];
-    colorAttachment.texture = data.mtlbackbuffer.texture;
-    colorAttachment.loadAction = MTLLoadActionClear;
-    colorAttachment.clearColor = MTLClearColorMake(0.0f, 0.0f, 0.0f, 1.0f);
-    data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
+    NSError *err = nil;
 
-    // Just push a clear to the screen to start so we're in a good state.
-    data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
-    data.mtlcmdencoder.label = @"Initial drawable clear";
+    // 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), ^{});
+    data.mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
+    SDL_assert(err == nil);
+#if !__has_feature(objc_arc)
+    dispatch_release(mtllibdata);
+#endif
+    data.mtllibrary.label = @"SDL Metal renderer shader library";
 
-    METAL_RenderPresent(renderer);
+    data.mtlpipelineprims = [[NSMutableArray alloc] init];
+    MakePipelineStates(data, data.mtlpipelineprims, @"SDL primitives pipeline", @"SDL_Simple_vertex", @"SDL_Simple_fragment");
+    data.mtlpipelinecopy = [[NSMutableArray alloc] init];
+    MakePipelineStates(data, data.mtlpipelinecopy, @"SDL_RenderCopy pipeline", @"SDL_Copy_vertex", @"SDL_Copy_fragment");
+
+    static const float clearverts[] = { -1, -1, -1, 1, 1, 1, 1, -1, -1, -1 };
+    data.mtlbufclearverts = [data.mtldevice newBufferWithBytes:clearverts length:sizeof(clearverts) options:MTLResourceCPUCacheModeWriteCombined];
+    data.mtlbufclearverts.label = @"SDL_RenderClear vertices";
+
+    // !!! 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;
@@ -325,30 +335,27 @@ METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
     // !!! FIXME: how do you control this in Metal?
     renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
 
-    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), ^{});
-    data.mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
-    SDL_assert(err == nil);
-#if !__has_feature(objc_arc)
-    dispatch_release(mtllibdata);
-#endif
-    data.mtllibrary.label = @"SDL Metal renderer shader library";
-
-    data.mtlpipelineprims = [[NSMutableArray alloc] init];
-    MakePipelineStates(data, data.mtlpipelineprims, @"SDL primitives pipeline", @"SDL_Simple_vertex", @"SDL_Simple_fragment");
-    data.mtlpipelinecopy = [[NSMutableArray alloc] init];
-    MakePipelineStates(data, data.mtlpipelinecopy, @"SDL_RenderCopy pipeline", @"SDL_Copy_vertex", @"SDL_Copy_fragment");
-
-    static const float clearverts[] = { -1, -1, -1, 1, 1, 1, 1, -1, -1, -1 };
-    data.mtlbufclearverts = [data.mtldevice newBufferWithBytes:clearverts length:sizeof(clearverts) options:MTLResourceCPUCacheModeWriteCombined];
-    data.mtlbufclearverts.label = @"SDL_RenderClear vertices";
+    return renderer;
+}
 
-    // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
+static void METAL_ActivateRenderer(SDL_Renderer * renderer)
+{
+    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
 
-    return renderer;
+    if (data.beginScene) {
+        data.beginScene = NO;
+        data.mtlbackbuffer = [data.mtllayer nextDrawable];
+        SDL_assert(data.mtlbackbuffer);
+        data.mtlpassdesc.colorAttachments[0].texture = data.mtlbackbuffer.texture;
+        data.mtlpassdesc.colorAttachments[0].loadAction = MTLLoadActionDontCare;
+        data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
+        data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
+        data.mtlcmdencoder.label = @"SDL metal renderer frame";
+
+        // Set up our current renderer state for the next frame...
+        METAL_UpdateViewport(renderer);
+        METAL_UpdateClipRect(renderer);
+    }
 }
 
 static void
@@ -364,6 +371,7 @@ METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
 static int
 METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
 {
+    METAL_ActivateRenderer(renderer);
     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
     *w = (int) data.mtlbackbuffer.texture.width;
     *h = (int) data.mtlbackbuffer.texture.height;
@@ -438,6 +446,7 @@ METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture)
 static int
 METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
 {
+    METAL_ActivateRenderer(renderer);
     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
     id<MTLTexture> mtltexture = texture ? (__bridge id<MTLTexture>) texture->driverdata : nil;
     data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
@@ -447,17 +456,16 @@ METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
 static int
 METAL_UpdateViewport(SDL_Renderer * renderer)
 {
+    METAL_ActivateRenderer(renderer);
     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
-    if (data.mtlcmdencoder != nil) {
-        MTLViewport viewport;
-        viewport.originX = renderer->viewport.x;
-        viewport.originY = renderer->viewport.y;
-        viewport.width = renderer->viewport.w;
-        viewport.height = renderer->viewport.h;
-        viewport.znear = 0.0;
-        viewport.zfar = 1.0;
-        [data.mtlcmdencoder setViewport:viewport];
-    }
+    MTLViewport viewport;
+    viewport.originX = renderer->viewport.x;
+    viewport.originY = renderer->viewport.y;
+    viewport.width = renderer->viewport.w;
+    viewport.height = renderer->viewport.h;
+    viewport.znear = 0.0;
+    viewport.zfar = 1.0;
+    [data.mtlcmdencoder setViewport:viewport];
     return 0;
 }
 
@@ -465,24 +473,23 @@ static int
 METAL_UpdateClipRect(SDL_Renderer * renderer)
 {
     // !!! FIXME: should this care about the viewport?
+    METAL_ActivateRenderer(renderer);
     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
-    if (data.mtlcmdencoder != nil) {
-        MTLScissorRect mtlrect;
-        if (renderer->clipping_enabled) {
-            const SDL_Rect *rect = &renderer->clip_rect;
-            mtlrect.x = renderer->viewport.x + rect->x;
-            mtlrect.y = renderer->viewport.x + rect->y;
-            mtlrect.width = rect->w;
-            mtlrect.height = rect->h;
-        } else {
-            mtlrect.x = renderer->viewport.x;
-            mtlrect.y = renderer->viewport.y;
-            mtlrect.width = renderer->viewport.w;
-            mtlrect.height = renderer->viewport.h;
-        }
-        if (mtlrect.width > 0 && mtlrect.height > 0) {
-            [data.mtlcmdencoder setScissorRect:mtlrect];
-        }
+    MTLScissorRect mtlrect;
+    if (renderer->clipping_enabled) {
+        const SDL_Rect *rect = &renderer->clip_rect;
+        mtlrect.x = renderer->viewport.x + rect->x;
+        mtlrect.y = renderer->viewport.x + rect->y;
+        mtlrect.width = rect->w;
+        mtlrect.height = rect->h;
+    } else {
+        mtlrect.x = renderer->viewport.x;
+        mtlrect.y = renderer->viewport.y;
+        mtlrect.width = renderer->viewport.w;
+        mtlrect.height = renderer->viewport.h;
+    }
+    if (mtlrect.width > 0 && mtlrect.height > 0) {
+        [data.mtlcmdencoder setScissorRect:mtlrect];
     }
     return 0;
 }
@@ -491,6 +498,7 @@ static int
 METAL_RenderClear(SDL_Renderer * renderer)
 {
     // We could dump the command buffer and force a clear on a new one, but this will respect the scissor state.
+    METAL_ActivateRenderer(renderer);
     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
 
     // !!! FIXME: render color should live in a dedicated uniform buffer.
@@ -549,6 +557,8 @@ static int
 DrawVerts(SDL_Renderer * renderer, const SDL_FPoint * points, int count,
           const MTLPrimitiveType primtype)
 {
+    METAL_ActivateRenderer(renderer);
+
     const size_t vertlen = (sizeof (float) * 2) * count;
     float *verts = SDL_malloc(vertlen);
     if (!verts) {
@@ -595,6 +605,7 @@ METAL_RenderDrawLines(SDL_Renderer * renderer, const SDL_FPoint * points, int co
 static int
 METAL_RenderFillRects(SDL_Renderer * renderer, const SDL_FRect * rects, int count)
 {
+    METAL_ActivateRenderer(renderer);
     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
 
     // !!! FIXME: render color should live in a dedicated uniform buffer.
@@ -628,6 +639,7 @@ static int
 METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
               const SDL_Rect * srcrect, const SDL_FRect * dstrect)
 {
+    METAL_ActivateRenderer(renderer);
     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
     id<MTLTexture> mtltexture = (__bridge id<MTLTexture>) texture->driverdata;
     const float w = (float) data.mtlpassdesc.colorAttachments[0].texture.width;
@@ -681,6 +693,7 @@ static int
 METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
                     Uint32 pixel_format, void * pixels, int pitch)
 {
+    METAL_ActivateRenderer(renderer);
     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
     MTLRenderPassColorAttachmentDescriptor *colorAttachment = data.mtlpassdesc.colorAttachments[0];
     id<MTLTexture> mtltexture = colorAttachment.texture;
@@ -711,34 +724,19 @@ METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
 static void
 METAL_RenderPresent(SDL_Renderer * renderer)
 {
+    METAL_ActivateRenderer(renderer);
     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
-    MTLRenderPassColorAttachmentDescriptor *colorAttachment = data.mtlpassdesc.colorAttachments[0];
     id<CAMetalDrawable> mtlbackbuffer = data.mtlbackbuffer;
 
     [data.mtlcmdencoder endEncoding];
     [data.mtlcmdbuffer presentDrawable:mtlbackbuffer];
-
-    [data.mtlcmdbuffer addCompletedHandler:^(id <MTLCommandBuffer> mtlcmdbuffer) {
 #if !__has_feature(objc_arc)
+    [data.mtlcmdbuffer addCompletedHandler:^(id <MTLCommandBuffer> mtlcmdbuffer) {
         [mtlbackbuffer release];
-#endif
     }];
-
+#endif
     [data.mtlcmdbuffer commit];
-
-    // Start next frame, once we can.
-    // we don't specify a depth or stencil buffer because the render API doesn't currently use them.
-    data.mtlbackbuffer = [data.mtllayer nextDrawable];
-    SDL_assert(data.mtlbackbuffer);
-    colorAttachment.texture = data.mtlbackbuffer.texture;
-    colorAttachment.loadAction = MTLLoadActionDontCare;
-    data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
-    data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
-    data.mtlcmdencoder.label = @"SDL metal renderer frame";
-
-    // Set up our current renderer state for the next frame...
-    METAL_UpdateViewport(renderer);
-    METAL_UpdateClipRect(renderer);
+    data.beginScene = YES;
 }
 
 static void