Defer getting the next drawable until we actually start rendering
authorSam Lantinga <slouken@libsdl.org>
Fri, 08 Dec 2017 08:58:02 -0800
changeset 117351d81fcb6edd5
parent 11734 6d6dc7d2a704
child 11736 e54b6d8ffca8
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.
src/render/metal/SDL_render_metal.m
     1.1 --- a/src/render/metal/SDL_render_metal.m	Thu Dec 07 18:08:51 2017 -0800
     1.2 +++ b/src/render/metal/SDL_render_metal.m	Fri Dec 08 08:58:02 2017 -0800
     1.3 @@ -94,17 +94,18 @@
     1.4  };
     1.5  
     1.6  @interface METAL_RenderData : NSObject
     1.7 -    @property (atomic, retain) id<MTLDevice> mtldevice;
     1.8 -    @property (atomic, retain) id<MTLCommandQueue> mtlcmdqueue;
     1.9 -    @property (atomic, retain) id<MTLCommandBuffer> mtlcmdbuffer;
    1.10 -    @property (atomic, retain) id<MTLRenderCommandEncoder> mtlcmdencoder;
    1.11 -    @property (atomic, retain) id<MTLLibrary> mtllibrary;
    1.12 -    @property (atomic, retain) id<CAMetalDrawable> mtlbackbuffer;
    1.13 -    @property (atomic, retain) NSMutableArray *mtlpipelineprims;
    1.14 -    @property (atomic, retain) NSMutableArray *mtlpipelinecopy;
    1.15 -    @property (atomic, retain) id<MTLBuffer> mtlbufclearverts;
    1.16 -    @property (atomic, retain) CAMetalLayer *mtllayer;
    1.17 -    @property (atomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
    1.18 +    @property (nonatomic, assign) BOOL beginScene;
    1.19 +    @property (nonatomic, retain) id<MTLDevice> mtldevice;
    1.20 +    @property (nonatomic, retain) id<MTLCommandQueue> mtlcmdqueue;
    1.21 +    @property (nonatomic, retain) id<MTLCommandBuffer> mtlcmdbuffer;
    1.22 +    @property (nonatomic, retain) id<MTLRenderCommandEncoder> mtlcmdencoder;
    1.23 +    @property (nonatomic, retain) id<MTLLibrary> mtllibrary;
    1.24 +    @property (nonatomic, retain) id<CAMetalDrawable> mtlbackbuffer;
    1.25 +    @property (nonatomic, retain) NSMutableArray *mtlpipelineprims;
    1.26 +    @property (nonatomic, retain) NSMutableArray *mtlpipelinecopy;
    1.27 +    @property (nonatomic, retain) id<MTLBuffer> mtlbufclearverts;
    1.28 +    @property (nonatomic, retain) CAMetalLayer *mtllayer;
    1.29 +    @property (nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
    1.30  @end
    1.31  
    1.32  @implementation METAL_RenderData
    1.33 @@ -139,7 +140,7 @@
    1.34      MTLRenderPipelineDescriptor *mtlpipedesc = [[MTLRenderPipelineDescriptor alloc] init];
    1.35      mtlpipedesc.vertexFunction = mtlvertfn;
    1.36      mtlpipedesc.fragmentFunction = mtlfragfn;
    1.37 -    mtlpipedesc.colorAttachments[0].pixelFormat = data.mtlbackbuffer.texture.pixelFormat;
    1.38 +    mtlpipedesc.colorAttachments[0].pixelFormat = data.mtllayer.pixelFormat;
    1.39  
    1.40      switch (blendmode) {
    1.41          case SDL_BLENDMODE_BLEND:
    1.42 @@ -236,6 +237,7 @@
    1.43      }
    1.44  
    1.45      data = [[METAL_RenderData alloc] init];
    1.46 +    data.beginScene = YES;
    1.47  
    1.48  #if __has_feature(objc_arc)
    1.49      renderer->driverdata = (void*)CFBridgingRetain(data);
    1.50 @@ -281,22 +283,30 @@
    1.51      data.mtllayer = layer;
    1.52      data.mtlcmdqueue = [data.mtldevice newCommandQueue];
    1.53      data.mtlcmdqueue.label = @"SDL Metal Renderer";
    1.54 -
    1.55      data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];  // !!! FIXME: is this autoreleased?
    1.56  
    1.57 -    // we don't specify a depth or stencil buffer because the render API doesn't currently use them.
    1.58 -    MTLRenderPassColorAttachmentDescriptor *colorAttachment = data.mtlpassdesc.colorAttachments[0];
    1.59 -    data.mtlbackbuffer = [data.mtllayer nextDrawable];
    1.60 -    colorAttachment.texture = data.mtlbackbuffer.texture;
    1.61 -    colorAttachment.loadAction = MTLLoadActionClear;
    1.62 -    colorAttachment.clearColor = MTLClearColorMake(0.0f, 0.0f, 0.0f, 1.0f);
    1.63 -    data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
    1.64 +    NSError *err = nil;
    1.65  
    1.66 -    // Just push a clear to the screen to start so we're in a good state.
    1.67 -    data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
    1.68 -    data.mtlcmdencoder.label = @"Initial drawable clear";
    1.69 +    // The compiled .metallib is embedded in a static array in a header file
    1.70 +    // but the original shader source code is in SDL_shaders_metal.metal.
    1.71 +    dispatch_data_t mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{});
    1.72 +    data.mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
    1.73 +    SDL_assert(err == nil);
    1.74 +#if !__has_feature(objc_arc)
    1.75 +    dispatch_release(mtllibdata);
    1.76 +#endif
    1.77 +    data.mtllibrary.label = @"SDL Metal renderer shader library";
    1.78  
    1.79 -    METAL_RenderPresent(renderer);
    1.80 +    data.mtlpipelineprims = [[NSMutableArray alloc] init];
    1.81 +    MakePipelineStates(data, data.mtlpipelineprims, @"SDL primitives pipeline", @"SDL_Simple_vertex", @"SDL_Simple_fragment");
    1.82 +    data.mtlpipelinecopy = [[NSMutableArray alloc] init];
    1.83 +    MakePipelineStates(data, data.mtlpipelinecopy, @"SDL_RenderCopy pipeline", @"SDL_Copy_vertex", @"SDL_Copy_fragment");
    1.84 +
    1.85 +    static const float clearverts[] = { -1, -1, -1, 1, 1, 1, 1, -1, -1, -1 };
    1.86 +    data.mtlbufclearverts = [data.mtldevice newBufferWithBytes:clearverts length:sizeof(clearverts) options:MTLResourceCPUCacheModeWriteCombined];
    1.87 +    data.mtlbufclearverts.label = @"SDL_RenderClear vertices";
    1.88 +
    1.89 +    // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
    1.90  
    1.91      renderer->WindowEvent = METAL_WindowEvent;
    1.92      renderer->GetOutputSize = METAL_GetOutputSize;
    1.93 @@ -325,30 +335,27 @@
    1.94      // !!! FIXME: how do you control this in Metal?
    1.95      renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
    1.96  
    1.97 -    NSError *err = nil;
    1.98 +    return renderer;
    1.99 +}
   1.100  
   1.101 -    // The compiled .metallib is embedded in a static array in a header file
   1.102 -    // but the original shader source code is in SDL_shaders_metal.metal.
   1.103 -    dispatch_data_t mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{});
   1.104 -    data.mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
   1.105 -    SDL_assert(err == nil);
   1.106 -#if !__has_feature(objc_arc)
   1.107 -    dispatch_release(mtllibdata);
   1.108 -#endif
   1.109 -    data.mtllibrary.label = @"SDL Metal renderer shader library";
   1.110 +static void METAL_ActivateRenderer(SDL_Renderer * renderer)
   1.111 +{
   1.112 +    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.113  
   1.114 -    data.mtlpipelineprims = [[NSMutableArray alloc] init];
   1.115 -    MakePipelineStates(data, data.mtlpipelineprims, @"SDL primitives pipeline", @"SDL_Simple_vertex", @"SDL_Simple_fragment");
   1.116 -    data.mtlpipelinecopy = [[NSMutableArray alloc] init];
   1.117 -    MakePipelineStates(data, data.mtlpipelinecopy, @"SDL_RenderCopy pipeline", @"SDL_Copy_vertex", @"SDL_Copy_fragment");
   1.118 +    if (data.beginScene) {
   1.119 +        data.beginScene = NO;
   1.120 +        data.mtlbackbuffer = [data.mtllayer nextDrawable];
   1.121 +        SDL_assert(data.mtlbackbuffer);
   1.122 +        data.mtlpassdesc.colorAttachments[0].texture = data.mtlbackbuffer.texture;
   1.123 +        data.mtlpassdesc.colorAttachments[0].loadAction = MTLLoadActionDontCare;
   1.124 +        data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
   1.125 +        data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
   1.126 +        data.mtlcmdencoder.label = @"SDL metal renderer frame";
   1.127  
   1.128 -    static const float clearverts[] = { -1, -1, -1, 1, 1, 1, 1, -1, -1, -1 };
   1.129 -    data.mtlbufclearverts = [data.mtldevice newBufferWithBytes:clearverts length:sizeof(clearverts) options:MTLResourceCPUCacheModeWriteCombined];
   1.130 -    data.mtlbufclearverts.label = @"SDL_RenderClear vertices";
   1.131 -
   1.132 -    // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
   1.133 -
   1.134 -    return renderer;
   1.135 +        // Set up our current renderer state for the next frame...
   1.136 +        METAL_UpdateViewport(renderer);
   1.137 +        METAL_UpdateClipRect(renderer);
   1.138 +    }
   1.139  }
   1.140  
   1.141  static void
   1.142 @@ -364,6 +371,7 @@
   1.143  static int
   1.144  METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
   1.145  {
   1.146 +    METAL_ActivateRenderer(renderer);
   1.147      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.148      *w = (int) data.mtlbackbuffer.texture.width;
   1.149      *h = (int) data.mtlbackbuffer.texture.height;
   1.150 @@ -438,6 +446,7 @@
   1.151  static int
   1.152  METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
   1.153  {
   1.154 +    METAL_ActivateRenderer(renderer);
   1.155      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.156      id<MTLTexture> mtltexture = texture ? (__bridge id<MTLTexture>) texture->driverdata : nil;
   1.157      data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
   1.158 @@ -447,17 +456,16 @@
   1.159  static int
   1.160  METAL_UpdateViewport(SDL_Renderer * renderer)
   1.161  {
   1.162 +    METAL_ActivateRenderer(renderer);
   1.163      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.164 -    if (data.mtlcmdencoder != nil) {
   1.165 -        MTLViewport viewport;
   1.166 -        viewport.originX = renderer->viewport.x;
   1.167 -        viewport.originY = renderer->viewport.y;
   1.168 -        viewport.width = renderer->viewport.w;
   1.169 -        viewport.height = renderer->viewport.h;
   1.170 -        viewport.znear = 0.0;
   1.171 -        viewport.zfar = 1.0;
   1.172 -        [data.mtlcmdencoder setViewport:viewport];
   1.173 -    }
   1.174 +    MTLViewport viewport;
   1.175 +    viewport.originX = renderer->viewport.x;
   1.176 +    viewport.originY = renderer->viewport.y;
   1.177 +    viewport.width = renderer->viewport.w;
   1.178 +    viewport.height = renderer->viewport.h;
   1.179 +    viewport.znear = 0.0;
   1.180 +    viewport.zfar = 1.0;
   1.181 +    [data.mtlcmdencoder setViewport:viewport];
   1.182      return 0;
   1.183  }
   1.184  
   1.185 @@ -465,24 +473,23 @@
   1.186  METAL_UpdateClipRect(SDL_Renderer * renderer)
   1.187  {
   1.188      // !!! FIXME: should this care about the viewport?
   1.189 +    METAL_ActivateRenderer(renderer);
   1.190      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.191 -    if (data.mtlcmdencoder != nil) {
   1.192 -        MTLScissorRect mtlrect;
   1.193 -        if (renderer->clipping_enabled) {
   1.194 -            const SDL_Rect *rect = &renderer->clip_rect;
   1.195 -            mtlrect.x = renderer->viewport.x + rect->x;
   1.196 -            mtlrect.y = renderer->viewport.x + rect->y;
   1.197 -            mtlrect.width = rect->w;
   1.198 -            mtlrect.height = rect->h;
   1.199 -        } else {
   1.200 -            mtlrect.x = renderer->viewport.x;
   1.201 -            mtlrect.y = renderer->viewport.y;
   1.202 -            mtlrect.width = renderer->viewport.w;
   1.203 -            mtlrect.height = renderer->viewport.h;
   1.204 -        }
   1.205 -        if (mtlrect.width > 0 && mtlrect.height > 0) {
   1.206 -            [data.mtlcmdencoder setScissorRect:mtlrect];
   1.207 -        }
   1.208 +    MTLScissorRect mtlrect;
   1.209 +    if (renderer->clipping_enabled) {
   1.210 +        const SDL_Rect *rect = &renderer->clip_rect;
   1.211 +        mtlrect.x = renderer->viewport.x + rect->x;
   1.212 +        mtlrect.y = renderer->viewport.x + rect->y;
   1.213 +        mtlrect.width = rect->w;
   1.214 +        mtlrect.height = rect->h;
   1.215 +    } else {
   1.216 +        mtlrect.x = renderer->viewport.x;
   1.217 +        mtlrect.y = renderer->viewport.y;
   1.218 +        mtlrect.width = renderer->viewport.w;
   1.219 +        mtlrect.height = renderer->viewport.h;
   1.220 +    }
   1.221 +    if (mtlrect.width > 0 && mtlrect.height > 0) {
   1.222 +        [data.mtlcmdencoder setScissorRect:mtlrect];
   1.223      }
   1.224      return 0;
   1.225  }
   1.226 @@ -491,6 +498,7 @@
   1.227  METAL_RenderClear(SDL_Renderer * renderer)
   1.228  {
   1.229      // We could dump the command buffer and force a clear on a new one, but this will respect the scissor state.
   1.230 +    METAL_ActivateRenderer(renderer);
   1.231      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.232  
   1.233      // !!! FIXME: render color should live in a dedicated uniform buffer.
   1.234 @@ -549,6 +557,8 @@
   1.235  DrawVerts(SDL_Renderer * renderer, const SDL_FPoint * points, int count,
   1.236            const MTLPrimitiveType primtype)
   1.237  {
   1.238 +    METAL_ActivateRenderer(renderer);
   1.239 +
   1.240      const size_t vertlen = (sizeof (float) * 2) * count;
   1.241      float *verts = SDL_malloc(vertlen);
   1.242      if (!verts) {
   1.243 @@ -595,6 +605,7 @@
   1.244  static int
   1.245  METAL_RenderFillRects(SDL_Renderer * renderer, const SDL_FRect * rects, int count)
   1.246  {
   1.247 +    METAL_ActivateRenderer(renderer);
   1.248      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.249  
   1.250      // !!! FIXME: render color should live in a dedicated uniform buffer.
   1.251 @@ -628,6 +639,7 @@
   1.252  METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
   1.253                const SDL_Rect * srcrect, const SDL_FRect * dstrect)
   1.254  {
   1.255 +    METAL_ActivateRenderer(renderer);
   1.256      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.257      id<MTLTexture> mtltexture = (__bridge id<MTLTexture>) texture->driverdata;
   1.258      const float w = (float) data.mtlpassdesc.colorAttachments[0].texture.width;
   1.259 @@ -681,6 +693,7 @@
   1.260  METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
   1.261                      Uint32 pixel_format, void * pixels, int pitch)
   1.262  {
   1.263 +    METAL_ActivateRenderer(renderer);
   1.264      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.265      MTLRenderPassColorAttachmentDescriptor *colorAttachment = data.mtlpassdesc.colorAttachments[0];
   1.266      id<MTLTexture> mtltexture = colorAttachment.texture;
   1.267 @@ -711,34 +724,19 @@
   1.268  static void
   1.269  METAL_RenderPresent(SDL_Renderer * renderer)
   1.270  {
   1.271 +    METAL_ActivateRenderer(renderer);
   1.272      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.273 -    MTLRenderPassColorAttachmentDescriptor *colorAttachment = data.mtlpassdesc.colorAttachments[0];
   1.274      id<CAMetalDrawable> mtlbackbuffer = data.mtlbackbuffer;
   1.275  
   1.276      [data.mtlcmdencoder endEncoding];
   1.277      [data.mtlcmdbuffer presentDrawable:mtlbackbuffer];
   1.278 -
   1.279 +#if !__has_feature(objc_arc)
   1.280      [data.mtlcmdbuffer addCompletedHandler:^(id <MTLCommandBuffer> mtlcmdbuffer) {
   1.281 -#if !__has_feature(objc_arc)
   1.282          [mtlbackbuffer release];
   1.283 +    }];
   1.284  #endif
   1.285 -    }];
   1.286 -
   1.287      [data.mtlcmdbuffer commit];
   1.288 -
   1.289 -    // Start next frame, once we can.
   1.290 -    // we don't specify a depth or stencil buffer because the render API doesn't currently use them.
   1.291 -    data.mtlbackbuffer = [data.mtllayer nextDrawable];
   1.292 -    SDL_assert(data.mtlbackbuffer);
   1.293 -    colorAttachment.texture = data.mtlbackbuffer.texture;
   1.294 -    colorAttachment.loadAction = MTLLoadActionDontCare;
   1.295 -    data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
   1.296 -    data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
   1.297 -    data.mtlcmdencoder.label = @"SDL metal renderer frame";
   1.298 -
   1.299 -    // Set up our current renderer state for the next frame...
   1.300 -    METAL_UpdateViewport(renderer);
   1.301 -    METAL_UpdateClipRect(renderer);
   1.302 +    data.beginScene = YES;
   1.303  }
   1.304  
   1.305  static void