metal: Implement fast hardware clearing when possible, by deferring the start of a render pass until a clear or draw operation happens.
authorAlex Szpakowski <slime73@gmail.com>
Thu, 04 Jan 2018 19:29:33 -0400
changeset 118175724c41db8fd
parent 11816 dea9cf23bad5
child 11818 4f3cdd0a5768
metal: Implement fast hardware clearing when possible, by deferring the start of a render pass until a clear or draw operation happens.
src/render/metal/SDL_render_metal.m
     1.1 --- a/src/render/metal/SDL_render_metal.m	Wed Jan 03 11:31:42 2018 -0800
     1.2 +++ b/src/render/metal/SDL_render_metal.m	Thu Jan 04 19:29:33 2018 -0400
     1.3 @@ -147,7 +147,6 @@
     1.4  } METAL_PipelineCache;
     1.5  
     1.6  @interface METAL_RenderData : NSObject
     1.7 -    @property (nonatomic, assign) BOOL beginScene;
     1.8      @property (nonatomic, retain) id<MTLDevice> mtldevice;
     1.9      @property (nonatomic, retain) id<MTLCommandQueue> mtlcmdqueue;
    1.10      @property (nonatomic, retain) id<MTLCommandBuffer> mtlcmdbuffer;
    1.11 @@ -404,7 +403,6 @@
    1.12      }
    1.13  
    1.14      data = [[METAL_RenderData alloc] init];
    1.15 -    data.beginScene = YES;
    1.16  
    1.17      renderer->driverdata = (void*)CFBridgingRetain(data);
    1.18      renderer->window = window;
    1.19 @@ -562,21 +560,50 @@
    1.20  }
    1.21  
    1.22  static void
    1.23 -METAL_ActivateRenderer(SDL_Renderer * renderer)
    1.24 +METAL_ActivateRenderCommandEncoder(SDL_Renderer * renderer, MTLLoadAction load)
    1.25  {
    1.26      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
    1.27  
    1.28 -    if (data.beginScene) {
    1.29 -        data.beginScene = NO;
    1.30 -        data.mtlbackbuffer = [data.mtllayer nextDrawable];
    1.31 -        SDL_assert(data.mtlbackbuffer);
    1.32 -        data.mtlpassdesc.colorAttachments[0].texture = data.mtlbackbuffer.texture;
    1.33 -        data.mtlpassdesc.colorAttachments[0].loadAction = MTLLoadActionDontCare;
    1.34 +    /* Our SetRenderTarget just signals that the next render operation should
    1.35 +     * set up a new render pass. This is where that work happens. */
    1.36 +    if (data.mtlcmdencoder == nil) {
    1.37 +        id<MTLTexture> mtltexture = nil;
    1.38 +
    1.39 +        if (renderer->target != NULL) {
    1.40 +            METAL_TextureData *texdata = (__bridge METAL_TextureData *)renderer->target->driverdata;
    1.41 +            mtltexture = texdata.mtltexture;
    1.42 +        } else {
    1.43 +            if (data.mtlbackbuffer == nil) {
    1.44 +                /* The backbuffer's contents aren't guaranteed to persist after
    1.45 +                 * presenting, so we can leave it undefined when loading it. */
    1.46 +                data.mtlbackbuffer = [data.mtllayer nextDrawable];
    1.47 +                if (load == MTLLoadActionLoad) {
    1.48 +                    load = MTLLoadActionDontCare;
    1.49 +                }
    1.50 +            }
    1.51 +            mtltexture = data.mtlbackbuffer.texture;
    1.52 +        }
    1.53 +
    1.54 +        SDL_assert(mtltexture);
    1.55 +
    1.56 +        if (load == MTLLoadActionClear) {
    1.57 +            MTLClearColor color = MTLClearColorMake(renderer->r/255.0, renderer->g/255.0, renderer->b/255.0, renderer->a/255.0);
    1.58 +            data.mtlpassdesc.colorAttachments[0].clearColor = color;
    1.59 +        }
    1.60 +
    1.61 +        data.mtlpassdesc.colorAttachments[0].loadAction = load;
    1.62 +        data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
    1.63 +
    1.64          data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
    1.65          data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
    1.66 -        data.mtlcmdencoder.label = @"SDL metal renderer start of frame";
    1.67  
    1.68 -        // Set up our current renderer state for the next frame...
    1.69 +        if (data.mtlbackbuffer != nil && mtltexture == data.mtlbackbuffer.texture) {
    1.70 +            data.mtlcmdencoder.label = @"SDL metal renderer backbuffer";
    1.71 +        } else {
    1.72 +            data.mtlcmdencoder.label = @"SDL metal renderer render target";
    1.73 +        }
    1.74 +
    1.75 +        /* Make sure the viewport and clip rect are set on the new render pass. */
    1.76          METAL_UpdateViewport(renderer);
    1.77          METAL_UpdateClipRect(renderer);
    1.78      }
    1.79 @@ -715,24 +742,21 @@
    1.80  static int
    1.81  METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
    1.82  { @autoreleasepool {
    1.83 -    METAL_ActivateRenderer(renderer);
    1.84      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
    1.85  
    1.86 -    // commit the current command buffer, so that any work on a render target
    1.87 -    //  will be available to the next one we're about to queue up.
    1.88 -    [data.mtlcmdencoder endEncoding];
    1.89 -    [data.mtlcmdbuffer commit];
    1.90 +    if (data.mtlcmdencoder) {
    1.91 +        /* End encoding for the previous render target so we can set up a new
    1.92 +         * render pass for this one. */
    1.93 +        [data.mtlcmdencoder endEncoding];
    1.94 +        [data.mtlcmdbuffer commit];
    1.95  
    1.96 -    id<MTLTexture> mtltexture = texture ? ((__bridge METAL_TextureData *)texture->driverdata).mtltexture : data.mtlbackbuffer.texture;
    1.97 -    data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
    1.98 -    // !!! FIXME: this can be MTLLoadActionDontCare for textures (not the backbuffer) if SDL doesn't guarantee the texture contents should survive.
    1.99 -    data.mtlpassdesc.colorAttachments[0].loadAction = MTLLoadActionLoad;
   1.100 -    data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
   1.101 -    data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
   1.102 -    data.mtlcmdencoder.label = texture ? @"SDL metal renderer render texture" : @"SDL metal renderer backbuffer";
   1.103 +        data.mtlcmdencoder = nil;
   1.104 +        data.mtlcmdbuffer = nil;
   1.105 +    }
   1.106  
   1.107 -    // The higher level will reset the viewport and scissor after this call returns.
   1.108 -
   1.109 +    /* We don't begin a new render pass right away - we delay it until an actual
   1.110 +     * draw or clear happens. That way we can use hardware clears when possible,
   1.111 +     * which are only available when beginning a new render pass. */
   1.112      return 0;
   1.113  }}
   1.114  
   1.115 @@ -772,41 +796,43 @@
   1.116  static int
   1.117  METAL_UpdateViewport(SDL_Renderer * renderer)
   1.118  { @autoreleasepool {
   1.119 -    METAL_ActivateRenderer(renderer);
   1.120      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.121 -    MTLViewport viewport;
   1.122 -    viewport.originX = renderer->viewport.x;
   1.123 -    viewport.originY = renderer->viewport.y;
   1.124 -    viewport.width = renderer->viewport.w;
   1.125 -    viewport.height = renderer->viewport.h;
   1.126 -    viewport.znear = 0.0;
   1.127 -    viewport.zfar = 1.0;
   1.128 -    [data.mtlcmdencoder setViewport:viewport];
   1.129 -    METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h);
   1.130 +    if (data.mtlcmdencoder) {
   1.131 +        MTLViewport viewport;
   1.132 +        viewport.originX = renderer->viewport.x;
   1.133 +        viewport.originY = renderer->viewport.y;
   1.134 +        viewport.width = renderer->viewport.w;
   1.135 +        viewport.height = renderer->viewport.h;
   1.136 +        viewport.znear = 0.0;
   1.137 +        viewport.zfar = 1.0;
   1.138 +        [data.mtlcmdencoder setViewport:viewport];
   1.139 +        METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h);
   1.140 +    }
   1.141      return 0;
   1.142  }}
   1.143  
   1.144  static int
   1.145  METAL_UpdateClipRect(SDL_Renderer * renderer)
   1.146  { @autoreleasepool {
   1.147 -    METAL_ActivateRenderer(renderer);
   1.148      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.149 -    MTLScissorRect mtlrect;
   1.150 -    // !!! FIXME: should this care about the viewport?
   1.151 -    if (renderer->clipping_enabled) {
   1.152 -        const SDL_Rect *rect = &renderer->clip_rect;
   1.153 -        mtlrect.x = renderer->viewport.x + rect->x;
   1.154 -        mtlrect.y = renderer->viewport.x + rect->y;
   1.155 -        mtlrect.width = rect->w;
   1.156 -        mtlrect.height = rect->h;
   1.157 -    } else {
   1.158 -        mtlrect.x = renderer->viewport.x;
   1.159 -        mtlrect.y = renderer->viewport.y;
   1.160 -        mtlrect.width = renderer->viewport.w;
   1.161 -        mtlrect.height = renderer->viewport.h;
   1.162 -    }
   1.163 -    if (mtlrect.width > 0 && mtlrect.height > 0) {
   1.164 -        [data.mtlcmdencoder setScissorRect:mtlrect];
   1.165 +    if (data.mtlcmdencoder) {
   1.166 +        MTLScissorRect mtlrect;
   1.167 +        // !!! FIXME: should this care about the viewport?
   1.168 +        if (renderer->clipping_enabled) {
   1.169 +            const SDL_Rect *rect = &renderer->clip_rect;
   1.170 +            mtlrect.x = renderer->viewport.x + rect->x;
   1.171 +            mtlrect.y = renderer->viewport.x + rect->y;
   1.172 +            mtlrect.width = rect->w;
   1.173 +            mtlrect.height = rect->h;
   1.174 +        } else {
   1.175 +            mtlrect.x = renderer->viewport.x;
   1.176 +            mtlrect.y = renderer->viewport.y;
   1.177 +            mtlrect.width = renderer->viewport.w;
   1.178 +            mtlrect.height = renderer->viewport.h;
   1.179 +        }
   1.180 +        if (mtlrect.width > 0 && mtlrect.height > 0) {
   1.181 +            [data.mtlcmdencoder setScissorRect:mtlrect];
   1.182 +        }
   1.183      }
   1.184      return 0;
   1.185  }}
   1.186 @@ -814,38 +840,43 @@
   1.187  static int
   1.188  METAL_RenderClear(SDL_Renderer * renderer)
   1.189  { @autoreleasepool {
   1.190 -    // We could dump the command buffer and force a clear on a new one, but this will respect the scissor state.
   1.191 -    METAL_ActivateRenderer(renderer);
   1.192      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.193  
   1.194 -    // !!! FIXME: render color should live in a dedicated uniform buffer.
   1.195 -    const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   1.196 +    /* Since we set up the render command encoder lazily when a draw is
   1.197 +     * requested, we can do the fast path hardware clear if no draws have
   1.198 +     * happened since the last SetRenderTarget. */
   1.199 +    if (data.mtlcmdencoder == nil) {
   1.200 +        METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear);
   1.201 +    } else {
   1.202 +        // !!! FIXME: render color should live in a dedicated uniform buffer.
   1.203 +        const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   1.204  
   1.205 -    MTLViewport viewport;  // RenderClear ignores the viewport state, though, so reset that.
   1.206 -    viewport.originX = viewport.originY = 0.0;
   1.207 -    viewport.width = data.mtlpassdesc.colorAttachments[0].texture.width;
   1.208 -    viewport.height = data.mtlpassdesc.colorAttachments[0].texture.height;
   1.209 -    viewport.znear = 0.0;
   1.210 -    viewport.zfar = 1.0;
   1.211 +        MTLViewport viewport;  // RenderClear ignores the viewport state, though, so reset that.
   1.212 +        viewport.originX = viewport.originY = 0.0;
   1.213 +        viewport.width = data.mtlpassdesc.colorAttachments[0].texture.width;
   1.214 +        viewport.height = data.mtlpassdesc.colorAttachments[0].texture.height;
   1.215 +        viewport.znear = 0.0;
   1.216 +        viewport.zfar = 1.0;
   1.217  
   1.218 -    // Draw a simple filled fullscreen triangle now.
   1.219 -    METAL_SetOrthographicProjection(renderer, 1, 1);
   1.220 -    [data.mtlcmdencoder setViewport:viewport];
   1.221 -    [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelineprims, SDL_BLENDMODE_NONE)];
   1.222 -    [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_CLEAR_VERTS atIndex:0];
   1.223 -    [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3];
   1.224 -    [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   1.225 -    [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
   1.226 +        // Slow path for clearing: draw a filled fullscreen triangle.
   1.227 +        METAL_SetOrthographicProjection(renderer, 1, 1);
   1.228 +        [data.mtlcmdencoder setViewport:viewport];
   1.229 +        [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelineprims, SDL_BLENDMODE_NONE)];
   1.230 +        [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_CLEAR_VERTS atIndex:0];
   1.231 +        [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3];
   1.232 +        [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   1.233 +        [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
   1.234  
   1.235 -    // reset the viewport for the rest of our usual drawing work...
   1.236 -    viewport.originX = renderer->viewport.x;
   1.237 -    viewport.originY = renderer->viewport.y;
   1.238 -    viewport.width = renderer->viewport.w;
   1.239 -    viewport.height = renderer->viewport.h;
   1.240 -    viewport.znear = 0.0;
   1.241 -    viewport.zfar = 1.0;
   1.242 -    [data.mtlcmdencoder setViewport:viewport];
   1.243 -    METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h);
   1.244 +        // reset the viewport for the rest of our usual drawing work...
   1.245 +        viewport.originX = renderer->viewport.x;
   1.246 +        viewport.originY = renderer->viewport.y;
   1.247 +        viewport.width = renderer->viewport.w;
   1.248 +        viewport.height = renderer->viewport.h;
   1.249 +        viewport.znear = 0.0;
   1.250 +        viewport.zfar = 1.0;
   1.251 +        [data.mtlcmdencoder setViewport:viewport];
   1.252 +        METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h);
   1.253 +    }
   1.254  
   1.255      return 0;
   1.256  }}
   1.257 @@ -861,7 +892,7 @@
   1.258  DrawVerts(SDL_Renderer * renderer, const SDL_FPoint * points, int count,
   1.259            const MTLPrimitiveType primtype)
   1.260  { @autoreleasepool {
   1.261 -    METAL_ActivateRenderer(renderer);
   1.262 +    METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad);
   1.263  
   1.264      const size_t vertlen = (sizeof (float) * 2) * count;
   1.265      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.266 @@ -894,7 +925,7 @@
   1.267  static int
   1.268  METAL_RenderFillRects(SDL_Renderer * renderer, const SDL_FRect * rects, int count)
   1.269  { @autoreleasepool {
   1.270 -    METAL_ActivateRenderer(renderer);
   1.271 +    METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad);
   1.272      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.273  
   1.274      // !!! FIXME: render color should live in a dedicated uniform buffer.
   1.275 @@ -925,7 +956,7 @@
   1.276  METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
   1.277                const SDL_Rect * srcrect, const SDL_FRect * dstrect)
   1.278  { @autoreleasepool {
   1.279 -    METAL_ActivateRenderer(renderer);
   1.280 +    METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad);
   1.281      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.282      METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
   1.283      const float texw = (float) texturedata.mtltexture.width;
   1.284 @@ -970,7 +1001,7 @@
   1.285                const SDL_Rect * srcrect, const SDL_FRect * dstrect,
   1.286                const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
   1.287  { @autoreleasepool {
   1.288 -    METAL_ActivateRenderer(renderer);
   1.289 +    METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad);
   1.290      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.291      METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
   1.292      const float texw = (float) texturedata.mtltexture.width;
   1.293 @@ -1052,7 +1083,8 @@
   1.294  METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
   1.295                      Uint32 pixel_format, void * pixels, int pitch)
   1.296  { @autoreleasepool {
   1.297 -    METAL_ActivateRenderer(renderer);
   1.298 +    METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad);
   1.299 +
   1.300      // !!! FIXME: this probably needs to commit the current command buffer, and probably waitUntilCompleted
   1.301      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.302      id<MTLTexture> mtltexture = data.mtlpassdesc.colorAttachments[0].texture;
   1.303 @@ -1076,16 +1108,20 @@
   1.304  static void
   1.305  METAL_RenderPresent(SDL_Renderer * renderer)
   1.306  { @autoreleasepool {
   1.307 -    METAL_ActivateRenderer(renderer);
   1.308      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.309  
   1.310 -    [data.mtlcmdencoder endEncoding];
   1.311 -    [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
   1.312 -    [data.mtlcmdbuffer commit];
   1.313 +    if (data.mtlcmdencoder != nil) {
   1.314 +        [data.mtlcmdencoder endEncoding];
   1.315 +    }
   1.316 +    if (data.mtlbackbuffer != nil) {
   1.317 +        [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
   1.318 +    }
   1.319 +    if (data.mtlcmdbuffer != nil) {
   1.320 +        [data.mtlcmdbuffer commit];
   1.321 +    }
   1.322      data.mtlcmdencoder = nil;
   1.323      data.mtlcmdbuffer = nil;
   1.324      data.mtlbackbuffer = nil;
   1.325 -    data.beginScene = YES;
   1.326  }}
   1.327  
   1.328  static void
   1.329 @@ -1122,7 +1158,7 @@
   1.330  static void *
   1.331  METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
   1.332  { @autoreleasepool {
   1.333 -    METAL_ActivateRenderer(renderer);
   1.334 +    METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad);
   1.335      METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   1.336      return (__bridge void*)data.mtlcmdencoder;
   1.337  }}