Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions Sources/ObjectivelyGPU/CommandBuffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ static bool acquireSwapchainTexture(const CommandBuffer *self, SwapchainTexture
self->device->window,
&swapchain->texture,
&w, &h);

if (ok) {
swapchain->size = (SDL_Size) { (int) w, (int) h };
swapchain->format = SDL_GetGPUSwapchainTextureFormat(self->device->device, self->device->window);
if (!ok || swapchain->texture == NULL) {
*swapchain = (SwapchainTexture) { 0 };
return false;
}

return ok;

swapchain->size = (SDL_Size) { (int) w, (int) h };
swapchain->format = SDL_GetGPUSwapchainTextureFormat(self->device->device, self->device->window);

return true;
}

/**
Expand Down Expand Up @@ -305,12 +307,16 @@ static bool waitAndAcquireSwapchainTexture(const CommandBuffer *self, SwapchainT
self->device->window,
&swapchain->texture,
&w, &h);
if (ok) {
swapchain->size = (SDL_Size) { (int) w, (int) h };
swapchain->format = SDL_GetGPUSwapchainTextureFormat(self->device->device, self->device->window);

if (!ok || swapchain->texture == NULL) {
*swapchain = (SwapchainTexture) { 0 };
return false;
}

return ok;

swapchain->size = (SDL_Size) { (int) w, (int) h };
swapchain->format = SDL_GetGPUSwapchainTextureFormat(self->device->device, self->device->window);

return true;
}

#pragma mark - Class lifecycle
Expand Down
22 changes: 15 additions & 7 deletions Sources/ObjectivelyGPU/CommandBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,13 @@ struct CommandBufferInterface {
/**
* @fn bool CommandBuffer::acquireSwapchainTexture(const CommandBuffer *self, SwapchainTexture *swapchain)
* @brief Acquires the next swapchain texture for rendering.
* @details Returns `false` (without asserting) when the window is minimised
* or the swapchain is temporarily unavailable. The caller should skip
* rendering for that frame.
* @details Returns `false` (without asserting) when the window is minimised, too many
* frames are in flight, or the swapchain is temporarily unavailable; SDL reports those
* cases as success with a NULL texture, which MUST NOT be passed back into SDL, so
* they are reported here as failure and @p swapchain is zeroed. The caller should skip
* presenting for that frame.
* @param self The CommandBuffer.
* @param swapchain Output structure populated with the texture and dimensions.
* @param swapchain Output structure populated with the texture and dimensions, or zeroed.
* @return True on success, false when the swapchain is unavailable this frame.
* @memberof CommandBuffer
*/
Expand Down Expand Up @@ -322,10 +324,16 @@ struct CommandBufferInterface {
* @fn bool CommandBuffer::waitAndAcquireSwapchainTexture(const CommandBuffer *self, SwapchainTexture *swapchain)
* @brief Blocks until a swapchain texture is available, then acquires it.
* @details Prefer `acquireSwapchainTexture` unless you must guarantee a
* texture this frame (e.g. during resize).
* texture this frame (e.g. during resize). Blocking bounds how many frames may be in
* flight, but a drawable is still not guaranteed: as with `acquireSwapchainTexture`,
* an unavailable swapchain is reported as `false` with @p swapchain zeroed.
*
* Call this as late in the frame as possible. The acquired drawable is held until this
* CommandBuffer is submitted, and holding it across a frame's CPU work starves the
* drawable pool.
* @param self The CommandBuffer.
* @param swapchain Output structure populated with the texture and dimensions.
* @return True on success, false on error.
* @param swapchain Output structure populated with the texture and dimensions, or zeroed.
* @return True on success, false when the swapchain is unavailable this frame.
* @memberof CommandBuffer
*/
bool (*waitAndAcquireSwapchainTexture)(const CommandBuffer *self, SwapchainTexture *swapchain);
Expand Down
49 changes: 25 additions & 24 deletions Sources/ObjectivelyGPU/RenderDevice.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,19 +152,19 @@ static CommandBuffer *acquireCommandBuffer(const RenderDevice *self) {
*/
static CommandBuffer *beginFrame(RenderDevice *self) {

GPU_Assert(self->window, "no SDL_Window for RenderDevice");
GPU_Assert(self->framebuffer, "no framebuffer set; call setFramebuffer first");
GPU_Assert(self->commands == NULL, "beginFrame called with a frame already in flight");

self->commands = $(self, acquireCommandBuffer);

const bool ok = $(self->commands, waitAndAcquireSwapchainTexture, &self->swapchain);
if (ok) {
$(self->framebuffer, resize, &self->swapchain.size);
} else {
$(self->commands, cancel);
self->commands = release(self->commands);
int w = 0, h = 0;
if (!SDL_GetWindowSizeInPixels(self->window, &w, &h) || w <= 0 || h <= 0) {
return NULL;
}

$(self->framebuffer, resize, &(SDL_Size) { w, h });

GPU_Assert(self->commands == NULL, "beginFrame called with a frame already in flight");

self->commands = $(self, acquireCommandBuffer);
return self->commands;
}

Expand All @@ -179,25 +179,26 @@ static Fence *endFrameAndFence(RenderDevice *self) {
Texture *color = $(self->framebuffer, resolveColorTexture, 0);
GPU_Assert(color, "framebuffer has no color attachment to present");

$(self->commands, blitTexture, &(SDL_GPUBlitInfo) {
.source = {
.texture = color->texture,
.w = (Uint32) self->swapchain.size.w,
.h = (Uint32) self->swapchain.size.h,
},
.destination = {
.texture = self->swapchain.texture,
.w = (Uint32) self->swapchain.size.w,
.h = (Uint32) self->swapchain.size.h,
},
.load_op = SDL_GPU_LOADOP_DONT_CARE,
.filter = SDL_GPU_FILTER_NEAREST,
});
if ($(self->commands, waitAndAcquireSwapchainTexture, &self->swapchain)) {
$(self->commands, blitTexture, &(SDL_GPUBlitInfo) {
.source = {
.texture = color->texture,
.w = (Uint32) self->framebuffer->size.w,
.h = (Uint32) self->framebuffer->size.h,
},
.destination = {
.texture = self->swapchain.texture,
.w = (Uint32) self->swapchain.size.w,
.h = (Uint32) self->swapchain.size.h,
},
.load_op = SDL_GPU_LOADOP_DONT_CARE,
.filter = SDL_GPU_FILTER_NEAREST,
});
}

Fence *fence = $(self->commands, submitAndFence);

self->commands = release(self->commands);

self->swapchain = (SwapchainTexture) { 0 };

return fence;
Expand Down
43 changes: 26 additions & 17 deletions Sources/ObjectivelyGPU/RenderDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ struct RenderDevice {

/**
* @brief The present-target Framebuffer driven by `beginFrame`/`endFrame`, or `NULL`.
* @details Set via `setFramebuffer` (retained). `beginFrame` resizes it to the
* swapchain each frame and `endFrame` blits its resolved color to the swapchain.
* @details Set via `setFramebuffer` (retained). `beginFrame` resizes it to the window's
* pixel size each frame and `endFrame` blits its resolved color to the swapchain.
*/
Framebuffer *framebuffer;

Expand All @@ -112,8 +112,9 @@ struct RenderDevice {
CommandBuffer *commands;

/**
* @brief The swapchain texture acquired for the current frame.
* @details Valid only between `beginFrame` and `endFrame`.
* @brief The swapchain texture acquired to present the current frame.
* @details Acquired inside `endFrame`, immediately before the presenting blit, and
* cleared again before it returns. Zeroed when no drawable was available.
* @private
*/
SwapchainTexture swapchain;
Expand Down Expand Up @@ -155,16 +156,20 @@ struct RenderDeviceInterface {

/**
* @fn CommandBuffer *RenderDevice::beginFrame(RenderDevice *self)
* @brief Begins a frame: acquires a command buffer and the swapchain, and prepares the framebuffer.
* @details Convenience over the manual acquire→wait-swapchain→resize boilerplate.
* Acquires a CommandBuffer, blocks for the swapchain texture, resizes the framebuffer
* set via `setFramebuffer` to the swapchain dimensions, and returns the command buffer
* so the caller can record passes into `framebuffer`. Returns `NULL` when the swapchain
* is unavailable (e.g. the window is minimised); the command buffer is cancelled and the
* frame should be skipped. Pair every non-NULL return with `endFrame`. The returned
* CommandBuffer is owned by the device; do not release it. Applications that render
* directly to the swapchain can ignore `beginFrame`/`endFrame` and drive the command
* buffer themselves.
* @brief Begins a frame: acquires a command buffer and prepares the framebuffer.
* @details Acquires a CommandBuffer, resizes the framebuffer set via `setFramebuffer`
* to the window's pixel size, and returns the command buffer so the caller can record
* passes into `framebuffer`. Returns `NULL` when the window has no drawable area (e.g.
* it is minimised) and the frame should be skipped entirely.
*
* The swapchain is deliberately **not** acquired here. `endFrame` acquires it
* immediately before the presenting blit, so the drawable is held for that blit alone
* rather than for the whole frame. Holding a Metal drawable across a frame's CPU work
* starves `CAMetalLayer`'s drawable pool and costs presented frames.
*
* Pair every non-NULL return with `endFrame`. The returned CommandBuffer is owned by
* the device; do not release it. Applications that render directly to the swapchain can
* ignore `beginFrame`/`endFrame` and drive the command buffer themselves.
* @param self The RenderDevice.
* @return The frame's CommandBuffer (borrowed), or `NULL` to skip the frame.
* @memberof RenderDevice
Expand Down Expand Up @@ -394,10 +399,14 @@ struct RenderDeviceInterface {
/**
* @fn void RenderDevice::endFrame(RenderDevice *self)
* @brief Ends the frame begun by `beginFrame`: presents the framebuffer and submits.
* @details Blits the framebuffer's resolved color (`Framebuffer::resolveColorTexture`,
* the resolve target when multisampled) into the acquired swapchain texture, submits
* the frame's command buffer, and releases it. Must be paired with a non-NULL
* @details Acquires the swapchain texture, blits the framebuffer's resolved color
* (`Framebuffer::resolveColorTexture`, the resolve target when multisampled) into it,
* submits the frame's command buffer, and releases it. Must be paired with a non-NULL
* `beginFrame` return.
*
* When no drawable is available the blit is skipped but the command buffer is still
* submitted, so the frame's GPU work completes and resource cycling stays correct; the
* frame simply is not presented and the previous one remains on screen.
* @param self The RenderDevice.
* @memberof RenderDevice
*/
Expand Down
Loading