From dc04f290a359ed0283e6703018ecb4e5427c7066 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Fri, 8 Dec 2017 08:58:02 -0800 Subject: [PATCH] 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 | 184 ++++++++++++++-------------- 1 file changed, 91 insertions(+), 93 deletions(-) diff --git a/src/render/metal/SDL_render_metal.m b/src/render/metal/SDL_render_metal.m index 157eac289..eb5ef5df0 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; - @property (atomic, retain) id mtlcmdqueue; - @property (atomic, retain) id mtlcmdbuffer; - @property (atomic, retain) id mtlcmdencoder; - @property (atomic, retain) id mtllibrary; - @property (atomic, retain) id mtlbackbuffer; - @property (atomic, retain) NSMutableArray *mtlpipelineprims; - @property (atomic, retain) NSMutableArray *mtlpipelinecopy; - @property (atomic, retain) id mtlbufclearverts; - @property (atomic, retain) CAMetalLayer *mtllayer; - @property (atomic, retain) MTLRenderPassDescriptor *mtlpassdesc; + @property (nonatomic, assign) BOOL beginScene; + @property (nonatomic, retain) id mtldevice; + @property (nonatomic, retain) id mtlcmdqueue; + @property (nonatomic, retain) id mtlcmdbuffer; + @property (nonatomic, retain) id mtlcmdencoder; + @property (nonatomic, retain) id mtllibrary; + @property (nonatomic, retain) id mtlbackbuffer; + @property (nonatomic, retain) NSMutableArray *mtlpipelineprims; + @property (nonatomic, retain) NSMutableArray *mtlpipelinecopy; + @property (nonatomic, retain) id 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,32 +335,29 @@ 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"; - - // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed. - return renderer; } +static void METAL_ActivateRenderer(SDL_Renderer * renderer) +{ + METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; + + 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 METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event) { @@ -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 = texture ? (__bridge id) 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 = (__bridge id) 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 = 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 mtlbackbuffer = data.mtlbackbuffer; [data.mtlcmdencoder endEncoding]; [data.mtlcmdbuffer presentDrawable:mtlbackbuffer]; - - [data.mtlcmdbuffer addCompletedHandler:^(id mtlcmdbuffer) { #if !__has_feature(objc_arc) + [data.mtlcmdbuffer addCompletedHandler:^(id 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