SDL: Add load op and clear color to SDL_BlitGPUTexture (#10767)

From 668e2f82d21bfe03126c28a7067d95ba1af0072e Mon Sep 17 00:00:00 2001
From: Evan Hemsley <[EMAIL REDACTED]>
Date: Mon, 9 Sep 2024 10:19:52 -0700
Subject: [PATCH] Add load op and clear color to SDL_BlitGPUTexture (#10767)

---
 include/SDL3/SDL_gpu.h             | 32 ++++++----
 src/dynapi/SDL_dynapi_procs.h      |  2 +-
 src/gpu/SDL_gpu.c                  | 94 ++++++++++++------------------
 src/gpu/SDL_sysgpu.h               | 12 +---
 src/gpu/d3d11/SDL_gpu_d3d11.c      | 13 +----
 src/gpu/d3d12/SDL_gpu_d3d12.c      | 56 ++++++++----------
 src/gpu/metal/SDL_gpu_metal.m      | 12 +---
 src/gpu/vulkan/SDL_gpu_vulkan.c    | 68 ++++++++++++---------
 src/render/sdlgpu/SDL_render_gpu.c | 23 ++++----
 test/testgpu_spinning_cube.c       | 21 ++++---
 10 files changed, 153 insertions(+), 180 deletions(-)

diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h
index 6c61bd1b22dcd..7a1c29657a423 100644
--- a/include/SDL3/SDL_gpu.h
+++ b/include/SDL3/SDL_gpu.h
@@ -1603,6 +1603,25 @@ typedef struct SDL_GPUDepthStencilTargetInfo
     Uint8 padding3;
 } SDL_GPUDepthStencilTargetInfo;
 
+/**
+ * A structure containing parameters for a blit command.
+ *
+ * \since This struct is available since SDL 3.0.0
+ *
+ * \sa SDL_BlitGPUTexture
+ */
+typedef struct SDL_GPUBlitInfo {
+    SDL_GPUBlitRegion source;       /**< The source region for the blit. */
+    SDL_GPUBlitRegion destination;  /**< The destination region for the blit. */
+    SDL_GPULoadOp load_op;          /**< What is done with the contents of the destination before the blit. */
+    SDL_FColor clear_color;         /**< The color to clear the destination region to before the blit. Ignored if load_op is not SDL_GPU_LOADOP_CLEAR. */
+    SDL_FlipMode flip_mode;         /**< The flip mode for the source region. */
+    SDL_GPUFilter filter;           /**< The filter mode used when blitting. */
+    SDL_bool cycle;                 /**< SDL_TRUE cycles the destination texture if it is already bound. */
+    Uint8 padding;
+    Uint8 padding2;
+    Uint8 padding3;
+} SDL_GPUBlitInfo;
 /* Binding structs */
 
 /**
@@ -3053,22 +3072,13 @@ extern SDL_DECLSPEC void SDLCALL SDL_GenerateMipmapsForGPUTexture(
  * This function must not be called inside of any pass.
  *
  * \param command_buffer a command buffer.
- * \param source the texture region to copy from.
- * \param destination the texture region to copy to.
- * \param flip_mode the flip mode for the source texture region.
- * \param filter the filter mode that will be used when blitting.
- * \param cycle if SDL_TRUE, cycles the destination texture if the destination
- *              texture is bound, otherwise overwrites the data.
+ * \param info the blit info struct containing the blit parameters.
  *
  * \since This function is available since SDL 3.0.0.
  */
 extern SDL_DECLSPEC void SDLCALL SDL_BlitGPUTexture(
     SDL_GPUCommandBuffer *command_buffer,
-    const SDL_GPUBlitRegion *source,
-    const SDL_GPUBlitRegion *destination,
-    SDL_FlipMode flip_mode,
-    SDL_GPUFilter filter,
-    SDL_bool cycle);
+    const SDL_GPUBlitInfo *info);
 
 /* Submission/Presentation */
 
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index d65faa61510b9..f6c6ab22167bc 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -85,7 +85,7 @@ SDL_DYNAPI_PROC(void,SDL_BindGPUVertexBuffers,(SDL_GPURenderPass *a, Uint32 b, c
 SDL_DYNAPI_PROC(void,SDL_BindGPUVertexSamplers,(SDL_GPURenderPass *a, Uint32 b, const SDL_GPUTextureSamplerBinding *c, Uint32 d),(a,b,c,d),)
 SDL_DYNAPI_PROC(void,SDL_BindGPUVertexStorageBuffers,(SDL_GPURenderPass *a, Uint32 b, SDL_GPUBuffer *const *c, Uint32 d),(a,b,c,d),)
 SDL_DYNAPI_PROC(void,SDL_BindGPUVertexStorageTextures,(SDL_GPURenderPass *a, Uint32 b, SDL_GPUTexture *const *c, Uint32 d),(a,b,c,d),)
-SDL_DYNAPI_PROC(void,SDL_BlitGPUTexture,(SDL_GPUCommandBuffer *a, const SDL_GPUBlitRegion *b, const SDL_GPUBlitRegion *c, SDL_FlipMode d, SDL_GPUFilter e, SDL_bool f),(a,b,c,d,e,f),)
+SDL_DYNAPI_PROC(void,SDL_BlitGPUTexture,(SDL_GPUCommandBuffer *a, const SDL_GPUBlitInfo *b),(a,b),)
 SDL_DYNAPI_PROC(SDL_bool,SDL_BlitSurface,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, const SDL_Rect *d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_BlitSurface9Grid,(SDL_Surface *a, const SDL_Rect *b, int c, int d, int e, int f, float g, SDL_ScaleMode h, SDL_Surface *i, const SDL_Rect *j),(a,b,c,d,e,f,g,h,i,j),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_BlitSurfaceScaled,(SDL_Surface *a, const SDL_Rect *b, SDL_Surface *c, const SDL_Rect *d, SDL_ScaleMode e),(a,b,c,d,e),return)
diff --git a/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c
index 6e4d275b43340..bfd94b68372ac 100644
--- a/src/gpu/SDL_gpu.c
+++ b/src/gpu/SDL_gpu.c
@@ -221,11 +221,7 @@ SDL_GPUGraphicsPipeline *SDL_GPU_FetchBlitPipeline(
 
 void SDL_GPU_BlitCommon(
     SDL_GPUCommandBuffer *command_buffer,
-    const SDL_GPUBlitRegion *source,
-    const SDL_GPUBlitRegion *destination,
-    SDL_FlipMode flip_mode,
-    SDL_GPUFilter filter,
-    bool cycle,
+    const SDL_GPUBlitInfo *info,
     SDL_GPUSampler *blit_linear_sampler,
     SDL_GPUSampler *blit_nearest_sampler,
     SDL_GPUShader *blit_vertex_shader,
@@ -239,8 +235,8 @@ void SDL_GPU_BlitCommon(
 {
     CommandBufferCommonHeader *cmdbufHeader = (CommandBufferCommonHeader *)command_buffer;
     SDL_GPURenderPass *render_pass;
-    TextureCommonHeader *src_header = (TextureCommonHeader *)source->texture;
-    TextureCommonHeader *dst_header = (TextureCommonHeader *)destination->texture;
+    TextureCommonHeader *src_header = (TextureCommonHeader *)info->source.texture;
+    TextureCommonHeader *dst_header = (TextureCommonHeader *)info->destination.texture;
     SDL_GPUGraphicsPipeline *blit_pipeline;
     SDL_GPUColorTargetInfo color_target_info;
     SDL_GPUViewport viewport;
@@ -266,24 +262,14 @@ void SDL_GPU_BlitCommon(
         return;
     }
 
-    // If the entire destination is blitted, we don't have to load
-    if (
-        dst_header->info.layer_count_or_depth == 1 &&
-        dst_header->info.num_levels == 1 &&
-        dst_header->info.type != SDL_GPU_TEXTURETYPE_3D &&
-        destination->w == dst_header->info.width &&
-        destination->h == dst_header->info.height) {
-        color_target_info.load_op = SDL_GPU_LOADOP_DONT_CARE;
-    } else {
-        color_target_info.load_op = SDL_GPU_LOADOP_LOAD;
-    }
-
+    color_target_info.load_op = info->load_op;
+    color_target_info.clear_color = info->clear_color;
     color_target_info.store_op = SDL_GPU_STOREOP_STORE;
 
-    color_target_info.texture = destination->texture;
-    color_target_info.mip_level = destination->mip_level;
-    color_target_info.layer_or_depth_plane = destination->layer_or_depth_plane;
-    color_target_info.cycle = cycle;
+    color_target_info.texture = info->destination.texture;
+    color_target_info.mip_level = info->destination.mip_level;
+    color_target_info.layer_or_depth_plane = info->destination.layer_or_depth_plane;
+    color_target_info.cycle = info->cycle;
 
     render_pass = SDL_BeginGPURenderPass(
         command_buffer,
@@ -291,10 +277,10 @@ void SDL_GPU_BlitCommon(
         1,
         NULL);
 
-    viewport.x = (float)destination->x;
-    viewport.y = (float)destination->y;
-    viewport.w = (float)destination->w;
-    viewport.h = (float)destination->h;
+    viewport.x = (float)info->destination.x;
+    viewport.y = (float)info->destination.y;
+    viewport.w = (float)info->destination.w;
+    viewport.h = (float)info->destination.h;
     viewport.min_depth = 0;
     viewport.max_depth = 1;
 
@@ -306,9 +292,9 @@ void SDL_GPU_BlitCommon(
         render_pass,
         blit_pipeline);
 
-    texture_sampler_binding.texture = source->texture;
+    texture_sampler_binding.texture = info->source.texture;
     texture_sampler_binding.sampler =
-        filter == SDL_GPU_FILTER_NEAREST ? blit_nearest_sampler : blit_linear_sampler;
+        info->filter == SDL_GPU_FILTER_NEAREST ? blit_nearest_sampler : blit_linear_sampler;
 
     SDL_BindGPUFragmentSamplers(
         render_pass,
@@ -316,21 +302,21 @@ void SDL_GPU_BlitCommon(
         &texture_sampler_binding,
         1);
 
-    blit_fragment_uniforms.left = (float)source->x / (src_header->info.width >> source->mip_level);
-    blit_fragment_uniforms.top = (float)source->y / (src_header->info.height >> source->mip_level);
-    blit_fragment_uniforms.width = (float)source->w / (src_header->info.width >> source->mip_level);
-    blit_fragment_uniforms.height = (float)source->h / (src_header->info.height >> source->mip_level);
-    blit_fragment_uniforms.mip_level = source->mip_level;
+    blit_fragment_uniforms.left = (float)info->source.x / (src_header->info.width >> info->source.mip_level);
+    blit_fragment_uniforms.top = (float)info->source.y / (src_header->info.height >> info->source.mip_level);
+    blit_fragment_uniforms.width = (float)info->source.w / (src_header->info.width >> info->source.mip_level);
+    blit_fragment_uniforms.height = (float)info->source.h / (src_header->info.height >> info->source.mip_level);
+    blit_fragment_uniforms.mip_level = info->source.mip_level;
 
     layer_divisor = (src_header->info.type == SDL_GPU_TEXTURETYPE_3D) ? src_header->info.layer_count_or_depth : 1;
-    blit_fragment_uniforms.layer_or_depth = (float)source->layer_or_depth_plane / layer_divisor;
+    blit_fragment_uniforms.layer_or_depth = (float)info->source.layer_or_depth_plane / layer_divisor;
 
-    if (flip_mode & SDL_FLIP_HORIZONTAL) {
+    if (info->flip_mode & SDL_FLIP_HORIZONTAL) {
         blit_fragment_uniforms.left += blit_fragment_uniforms.width;
         blit_fragment_uniforms.width *= -1;
     }
 
-    if (flip_mode & SDL_FLIP_VERTICAL) {
+    if (info->flip_mode & SDL_FLIP_VERTICAL) {
         blit_fragment_uniforms.top += blit_fragment_uniforms.height;
         blit_fragment_uniforms.height *= -1;
     }
@@ -2120,22 +2106,14 @@ void SDL_GenerateMipmapsForGPUTexture(
 
 void SDL_BlitGPUTexture(
     SDL_GPUCommandBuffer *command_buffer,
-    const SDL_GPUBlitRegion *source,
-    const SDL_GPUBlitRegion *destination,
-    SDL_FlipMode flip_mode,
-    SDL_GPUFilter filter,
-    SDL_bool cycle)
+    const SDL_GPUBlitInfo *info)
 {
     if (command_buffer == NULL) {
         SDL_InvalidParamError("command_buffer");
         return;
     }
-    if (source == NULL) {
-        SDL_InvalidParamError("source");
-        return;
-    }
-    if (destination == NULL) {
-        SDL_InvalidParamError("destination");
+    if (info == NULL) {
+        SDL_InvalidParamError("info");
         return;
     }
 
@@ -2145,11 +2123,15 @@ void SDL_BlitGPUTexture(
 
         // Validation
         bool failed = false;
-        TextureCommonHeader *srcHeader = (TextureCommonHeader *)source->texture;
-        TextureCommonHeader *dstHeader = (TextureCommonHeader *)destination->texture;
+        TextureCommonHeader *srcHeader = (TextureCommonHeader *)info->source.texture;
+        TextureCommonHeader *dstHeader = (TextureCommonHeader *)info->destination.texture;
 
-        if (srcHeader == NULL || dstHeader == NULL) {
-            SDL_assert_release(!"Blit source and destination textures must be non-NULL");
+        if (srcHeader == NULL) {
+            SDL_assert_release(!"Blit source texture must be non-NULL");
+            return; // attempting to proceed will crash
+        }
+        if (dstHeader == NULL) {
+            SDL_assert_release(!"Blit destination texture must be non-NULL");
             return; // attempting to proceed will crash
         }
         if ((srcHeader->info.usage & SDL_GPU_TEXTUREUSAGE_SAMPLER) == 0) {
@@ -2164,7 +2146,7 @@ void SDL_BlitGPUTexture(
             SDL_assert_release(!"Blit source texture cannot have a depth format");
             failed = true;
         }
-        if (source->w == 0 || source->h == 0 || destination->w == 0 || destination->h == 0) {
+        if (info->source.w == 0 || info->source.h == 0 || info->destination.w == 0 || info->destination.h == 0) {
             SDL_assert_release(!"Blit source/destination regions must have non-zero width, height, and depth");
             failed = true;
         }
@@ -2176,11 +2158,7 @@ void SDL_BlitGPUTexture(
 
     COMMAND_BUFFER_DEVICE->Blit(
         command_buffer,
-        source,
-        destination,
-        flip_mode,
-        filter,
-        cycle);
+        info);
 }
 
 // Submission/Presentation
diff --git a/src/gpu/SDL_sysgpu.h b/src/gpu/SDL_sysgpu.h
index f419c4b1d29b0..47a9653205350 100644
--- a/src/gpu/SDL_sysgpu.h
+++ b/src/gpu/SDL_sysgpu.h
@@ -263,11 +263,7 @@ SDL_GPUGraphicsPipeline *SDL_GPU_FetchBlitPipeline(
 
 void SDL_GPU_BlitCommon(
     SDL_GPUCommandBuffer *commandBuffer,
-    const SDL_GPUBlitRegion *source,
-    const SDL_GPUBlitRegion *destination,
-    SDL_FlipMode flipMode,
-    SDL_GPUFilter filter,
-    bool cycle,
+    const SDL_GPUBlitInfo *info,
     SDL_GPUSampler *blitLinearSampler,
     SDL_GPUSampler *blitNearestSampler,
     SDL_GPUShader *blitVertexShader,
@@ -605,11 +601,7 @@ struct SDL_GPUDevice
 
     void (*Blit)(
         SDL_GPUCommandBuffer *commandBuffer,
-        const SDL_GPUBlitRegion *source,
-        const SDL_GPUBlitRegion *destination,
-        SDL_FlipMode flipMode,
-        SDL_GPUFilter filter,
-        bool cycle);
+        const SDL_GPUBlitInfo *info);
 
     // Submission/Presentation
 
diff --git a/src/gpu/d3d11/SDL_gpu_d3d11.c b/src/gpu/d3d11/SDL_gpu_d3d11.c
index f1db8ffd2e749..f657452c2d5d6 100644
--- a/src/gpu/d3d11/SDL_gpu_d3d11.c
+++ b/src/gpu/d3d11/SDL_gpu_d3d11.c
@@ -4173,11 +4173,7 @@ static void D3D11_PushFragmentUniformData(
 
 static void D3D11_Blit(
     SDL_GPUCommandBuffer *commandBuffer,
-    const SDL_GPUBlitRegion *source,
-    const SDL_GPUBlitRegion *destination,
-    SDL_FlipMode flipMode,
-    SDL_GPUFilter filter,
-    bool cycle)
+    const SDL_GPUBlitInfo *info)
 {
     D3D11CommandBuffer *d3d11CommandBuffer = (D3D11CommandBuffer *)commandBuffer;
     D3D11Renderer *renderer = (D3D11Renderer *)d3d11CommandBuffer->renderer;
@@ -4185,11 +4181,7 @@ static void D3D11_Blit(
 
     SDL_GPU_BlitCommon(
         commandBuffer,
-        source,
-        destination,
-        flipMode,
-        filter,
-        cycle,
+        info,
         renderer->blitLinearSampler,
         renderer->blitNearestSampler,
         NULL,
@@ -5281,7 +5273,6 @@ static SDL_GPUTexture *D3D11_AcquireSwapchainTexture(
     // Check for window size changes and resize the swapchain if needed.
     IDXGISwapChain_GetDesc(windowData->swapchain, &swapchainDesc);
     SDL_GetWindowSize(window, &windowW, &windowH);
-    SDL_Log("%d x %d", windowW, windowH);
 
     if ((UINT)windowW != swapchainDesc.BufferDesc.Width || (UINT)windowH != swapchainDesc.BufferDesc.Height) {
         res = D3D11_INTERNAL_ResizeSwapchain(
diff --git a/src/gpu/d3d12/SDL_gpu_d3d12.c b/src/gpu/d3d12/SDL_gpu_d3d12.c
index a51de482d499a..4f0dd720c7587 100644
--- a/src/gpu/d3d12/SDL_gpu_d3d12.c
+++ b/src/gpu/d3d12/SDL_gpu_d3d12.c
@@ -5729,7 +5729,6 @@ static void D3D12_GenerateMipmaps(
     D3D12Renderer *renderer = d3d12CommandBuffer->renderer;
     D3D12TextureContainer *container = (D3D12TextureContainer *)texture;
     SDL_GPUGraphicsPipeline *blitPipeline;
-    SDL_GPUBlitRegion srcRegion, dstRegion;
 
     blitPipeline = SDL_GPU_FetchBlitPipeline(
         renderer->sdlGPUDevice,
@@ -5752,30 +5751,31 @@ static void D3D12_GenerateMipmaps(
     // We have to do this one subresource at a time
     for (Uint32 layerOrDepthIndex = 0; layerOrDepthIndex < container->header.info.layer_count_or_depth; layerOrDepthIndex += 1) {
         for (Uint32 levelIndex = 1; levelIndex < container->header.info.num_levels; levelIndex += 1) {
-
-            srcRegion.texture = texture;
-            srcRegion.mip_level = levelIndex - 1;
-            srcRegion.layer_or_depth_plane = layerOrDepthIndex;
-            srcRegion.x = 0;
-            srcRegion.y = 0;
-            srcRegion.w = container->header.info.width >> (levelIndex - 1);
-            srcRegion.h = container->header.info.height >> (levelIndex - 1);
-
-            dstRegion.texture = texture;
-            dstRegion.mip_level = levelIndex;
-            dstRegion.layer_or_depth_plane = layerOrDepthIndex;
-            dstRegion.x = 0;
-            dstRegion.y = 0;
-            dstRegion.w = container->header.info.width >> levelIndex;
-            dstRegion.h = container->header.info.height >> levelIndex;
+            SDL_GPUBlitInfo blitInfo;
+            SDL_zero(blitInfo);
+
+            blitInfo.source.texture = texture;
+            blitInfo.source.mip_level = levelIndex - 1;
+            blitInfo.source.layer_or_depth_plane = layerOrDepthIndex;
+            blitInfo.source.x = 0;
+            blitInfo.source.y = 0;
+            blitInfo.source.w = container->header.info.width >> (levelIndex - 1);
+            blitInfo.source.h = container->header.info.height >> (levelIndex - 1);
+
+            blitInfo.destination.texture = texture;
+            blitInfo.destination.mip_level = levelIndex;
+            blitInfo.destination.layer_or_depth_plane = layerOrDepthIndex;
+            blitInfo.destination.x = 0;
+            blitInfo.destination.y = 0;
+            blitInfo.destination.w = container->header.info.width >> levelIndex;
+            blitInfo.destination.h = container->header.info.height >> levelIndex;
+
+            blitInfo.load_op = SDL_GPU_LOADOP_DONT_CARE;
+            blitInfo.filter = SDL_GPU_FILTER_LINEAR;
 
             SDL_BlitGPUTexture(
                 commandBuffer,
-                &srcRegion,
-                &dstRegion,
-                SDL_FLIP_NONE,
-                SDL_GPU_FILTER_LINEAR,
-                false);
+                &blitInfo);
         }
     }
 
@@ -5784,22 +5784,14 @@ static void D3D12_GenerateMipmaps(
 
 static void D3D12_Blit(
     SDL_GPUCommandBuffer *commandBuffer,
-    const SDL_GPUBlitRegion *source,
-    const SDL_GPUBlitRegion *destination,
-    SDL_FlipMode flipMode,
-    SDL_GPUFilter filter,
-    bool cycle)
+    const SDL_GPUBlitInfo *info)
 {
     D3D12CommandBuffer *d3d12CommandBuffer = (D3D12CommandBuffer *)commandBuffer;
     D3D12Renderer *renderer = (D3D12Renderer *)d3d12CommandBuffer->renderer;
 
     SDL_GPU_BlitCommon(
         commandBuffer,
-        source,
-        destination,
-        flipMode,
-        filter,
-        cycle,
+        info,
         renderer->blitLinearSampler,
         renderer->blitNearestSampler,
         renderer->blitVertexShader,
diff --git a/src/gpu/metal/SDL_gpu_metal.m b/src/gpu/metal/SDL_gpu_metal.m
index 3b2610e0b5ac5..851c775c39912 100644
--- a/src/gpu/metal/SDL_gpu_metal.m
+++ b/src/gpu/metal/SDL_gpu_metal.m
@@ -2903,22 +2903,14 @@ static void METAL_PushFragmentUniformData(
 
 static void METAL_Blit(
     SDL_GPUCommandBuffer *commandBuffer,
-    const SDL_GPUBlitRegion *source,
-    const SDL_GPUBlitRegion *destination,
-    SDL_FlipMode flipMode,
-    SDL_GPUFilter filter,
-    bool cycle)
+    const SDL_GPUBlitInfo *info)
 {
     MetalCommandBuffer *metalCommandBuffer = (MetalCommandBuffer *)commandBuffer;
     MetalRenderer *renderer = (MetalRenderer *)metalCommandBuffer->renderer;
 
     SDL_GPU_BlitCommon(
         commandBuffer,
-        source,
-        destination,
-        flipMode,
-        filter,
-        cycle,
+        info,
         renderer->blitLinearSampler,
         renderer->blitNearestSampler,
         renderer->blitVertexShader,
diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c
index 62d4de4c25de6..757e4789bad48 100644
--- a/src/gpu/vulkan/SDL_gpu_vulkan.c
+++ b/src/gpu/vulkan/SDL_gpu_vulkan.c
@@ -9252,35 +9252,49 @@ static void VULKAN_EndCopyPass(
 
 static void VULKAN_Blit(
     SDL_GPUCommandBuffer *commandBuffer,
-    const SDL_GPUBlitRegion *source,
-    const SDL_GPUBlitRegion *destination,
-    SDL_FlipMode flipMode,
-    SDL_GPUFilter filter,
-    bool cycle)
+    const SDL_GPUBlitInfo *info)
 {
     VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer *)commandBuffer;
     VulkanRenderer *renderer = (VulkanRenderer *)vulkanCommandBuffer->renderer;
-    TextureCommonHeader *srcHeader = (TextureCommonHeader *)source->texture;
-    TextureCommonHeader *dstHeader = (TextureCommonHeader *)destination->texture;
+    TextureCommonHeader *srcHeader = (TextureCommonHeader *)info->source.texture;
+    TextureCommonHeader *dstHeader = (TextureCommonHeader *)info->destination.texture;
     VkImageBlit region;
-    Uint32 srcLayer = srcHeader->info.type == SDL_GPU_TEXTURETYPE_3D ? 0 : source->layer_or_depth_plane;
-    Uint32 srcDepth = srcHeader->info.type == SDL_GPU_TEXTURETYPE_3D ? source->layer_or_depth_plane : 0;
-    Uint32 dstLayer = dstHeader->info.type == SDL_GPU_TEXTURETYPE_3D ? 0 : destination->layer_or_depth_plane;
-    Uint32 dstDepth = dstHeader->info.type == SDL_GPU_TEXTURETYPE_3D ? destination->layer_or_depth_plane : 0;
+    Uint32 srcLayer = srcHeader->info.type == SDL_GPU_TEXTURETYPE_3D ? 0 : info->source.layer_or_depth_plane;
+    Uint32 srcDepth = srcHeader->info.type == SDL_GPU_TEXTURETYPE_3D ? info->source.layer_or_depth_plane : 0;
+    Uint32 dstLayer = dstHeader->info.type == SDL_GPU_TEXTURETYPE_3D ? 0 : info->destination.layer_or_depth_plane;
+    Uint32 dstDepth = dstHeader->info.type == SDL_GPU_TEXTURETYPE_3D ? info->destination.layer_or_depth_plane : 0;
     int32_t swap;
 
+    // Using BeginRenderPass to clear because vkCmdClearColorImage requires barriers anyway
+    if (info->load_op == SDL_GPU_LOADOP_CLEAR) {
+        SDL_GPUColorTargetInfo targetInfo;
+        targetInfo.texture = info->destination.texture;
+        targetInfo.mip_level = info->destination.mip_level;
+        targetInfo.layer_or_depth_plane = info->destination.layer_or_depth_plane;
+        targetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
+        targetInfo.store_op = SDL_GPU_STOREOP_STORE;
+        targetInfo.clear_color = info->clear_color;
+        targetInfo.cycle = info->cycle;
+        VULKAN_BeginRenderPass(
+            commandBuffer,
+            &targetInfo,
+            1,
+            NULL);
+        VULKAN_EndRenderPass(commandBuffer);
+    }
+
     VulkanTextureSubresource *srcSubresource = VULKAN_INTERNAL_FetchTextureSubresource(
-        (VulkanTextureContainer *)source->texture,
+        (VulkanTextureContainer *)info->source.texture,
         srcLayer,
-        source->mip_level);
+        info->source.mip_level);
 
     VulkanTextureSubresource *dstSubresource = VULKAN_INTERNAL_PrepareTextureSubresourceForWrite(
         renderer,
         vulkanCommandBuffer,
-        (VulkanTextureContainer *)destination->texture,
+        (VulkanTextureContainer *)info->destination.texture,
         dstLayer,
-        destination->mip_level,
-        cycle,
+        info->destination.mip_level,
+        info->cycle,
         VULKAN_TEXTURE_USAGE_MODE_COPY_DESTINATION);
 
     VULKAN_INTERNAL_TextureSubresourceTransitionFromDefaultUsage(
@@ -9293,21 +9307,21 @@ static void VULKAN_Blit(
     region.srcSubresource.baseArrayLayer = srcSubresource->layer;
     region.srcSubresource.layerCount = 1;
     region.srcSubresource.mipLevel = srcSubresource->level;
-    region.srcOffsets[0].x = source->x;
-    region.srcOffsets[0].y = source->y;
+    region.srcOffsets[0].x = info->source.x;
+    region.srcOffsets[0].y = info->source.y;
     region.srcOffsets[0].z = srcDepth;
-    region.srcOffsets[1].x = source->x + source->w;
-    region.srcOffsets[1].y = source->y + source->h;
+    region.srcOffsets[1].x = info->source.x + info->source.w;
+    region.srcOffsets[1].y = info->source.y + info->source.h;
     region.srcOffsets[1].z = srcDepth + 1;
 
-    if (flipMode & SDL_FLIP_HORIZONTAL) {
+    if (info->flip_mode & SDL_FLIP_HORIZONTAL) {
         // flip the x positions
         swap = region.srcOffsets[0].x;
         region.srcOffsets[0].x = region.srcOffsets[1].x;
         region.srcOffsets[1].x = swap;
     }
 
-    if (flipMode & SDL_FLIP_VERTICAL) {
+    if (info->flip_mode & SDL_FLIP_VERTICAL) {
         // flip the y positions
         swap = region.srcOffsets[0].y;
         region.srcOffsets[0].y = region.srcOffsets[1].y;
@@ -9318,11 +9332,11 @@ static void VULKAN_Blit(
     region.dstSubresource.baseArrayLayer = dstSubresource->layer;
     region.dstSubresource.layerCount = 1;
     region.dstSubresource.mipLevel = dstSubresource->level;
-    region.dstOffsets[0].x = destination->x;
-    region.dstOffsets[0].y = destination->y;
+    region.dstOffsets[0].x = info->destination.x;
+    region.dstOffsets[0].y = info->destination.y;
     region.dstOffsets[0].z = dstDepth;
-    region.dstOffsets[1].x = destination->x + destination->w;
-    region.dstOffsets[1].y = destination->y + destination->h;
+    region.dstOffsets[1].x = info->destination.x + info->destination.w;
+    region.dstOffsets[1].y = info->destination.y + info->destination.h;
     region.dstOffsets[1].z = dstDepth + 1;
 
     renderer->vkCmdBlitImage(
@@ -9333,7 +9347,7 @@ static void VULKAN_Blit(
         VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
         1,
         &region,
-        SDLToVK_Filter[filter]);
+        SDLToVK_Filter[info->filter]);
 
     VULKAN_INTERNAL_TextureSubresourceTransitionToDefaultUsage(
         renderer,
diff --git a/src/render/sdlgpu/SDL_render_gpu.c b/src/render/sdlgpu/SDL_render_gpu.c
index cd286bb51b014..6fa91e58aaba8 100644
--- a/src/render/sdlgpu/SDL_render_gpu.c
+++ b/src/render/sdlgpu/SDL_render_gpu.c
@@ -965,19 +965,20 @@ static bool GPU_RenderPresent(SDL_Renderer *renderer)
     }
 
     SDL_GPUTextureFormat swapchain_fmt = SDL_GetGPUSwapchainTextureFormat(data->device, renderer->window);
-    SDL_GPUBlitRegion src;
-    SDL_zero(src);
-    src.texture = data->backbuffer.texture;
-    src.w = data->backbuffer.width;
-    src.h = data->backbuffer.height;
 
-    SDL_GPUBlitRegion dst;
-    SDL_zero(dst);
-    dst.texture = swapchain;
-    dst.w = swapchain_w;
-    dst.h = swapchain_h;
+    SDL_GPUBlitInfo blit_info;
+    SDL_zero(blit_info);
+
+    blit_info.source.texture = data->backbuffer.texture;
+    blit_info.source.w = data->backbuffer.width;
+    blit_info.source.h = data->backbuffer.height;
+    blit_info.destination.texture = swapchain;
+    blit_info.destination.w = swapchain_w;
+    blit_info.destination.h = swapchain_h;
+    blit_info.load_op = SDL_GPU_LOADOP_DONT_CARE;
+    blit_info.filter = SDL_GPU_FILTER_LINEAR;
 
-    SDL_BlitGPUTexture(data->state.command_buffer, &src, &dst, SDL_FLIP_NONE, SDL_GPU_FILTER_LINEAR, true);
+    SDL_BlitGPUTexture(data->state.command_buffer, &blit_info);
 
     if (swapchain_w != data->backbuffer.width || swapchain_h != data->backbuffer.height || swapchain_fmt != data->backbuffer.format) {
         SDL_ReleaseGPUTexture(data->device, data->backbuffer.texture);
diff --git a/test/testgpu_spinning_cube.c b/test/testgpu_spinning_cube.c
index ea8be4fc7dfe2..47705b678016f 100644
--- a/test/testgpu_spinning_cube.c
+++ b/test/testgpu_spinning_cube.c
@@ -306,8 +306,7 @@ Render(SDL_Window *window, const int windownum)
     SDL_GPUCommandBuffer *cmd;
     SDL_GPURenderPass *pass;
     SDL_GPUBufferBinding vertex_binding;
-    SDL_GPUBlitRegion src_region;
-    SDL_GPUBlitRegion dst_region;
+    SDL_GPUBlitInfo blit_info;
 
     /* Acquire the swapchain texture */
 
@@ -393,15 +392,19 @@ Render(SDL_Window *window, const int windownum)
 
     /* Blit MSAA to swapchain, if needed */
     if (render_state.sample_count > SDL_GPU_SAMPLECOUNT_1) {
-        SDL_zero(src_region);
-        src_region.texture = winstate->tex_msaa;
-        src_region.w = drawablew;
-        src_region.h = drawableh;
+        SDL_zero(blit_info);
+        blit_info.source.texture = winstate->tex_msaa;
+        blit_info.source.w = drawablew;
+        blit_info.source.h = drawableh;
 
-        dst_region = src_region;
-        dst_region.texture = swapchain;
+        blit_info.destination.texture = swapchain;
+        blit_info.destination.w = drawablew;
+        blit_info.destination.h = drawableh;
 
-        SDL_BlitGPUTexture(cmd, &src_region, &dst_region, SDL_FLIP_NONE, SDL_GPU_FILTER_LINEAR, SDL_FALSE);
+        blit_info.load_op = SDL_GPU_LOADOP_DONT_CARE;
+        blit_info.filter = SDL_GPU_FILTER_LINEAR;
+
+        SDL_BlitGPUTexture(cmd, &blit_info);
     }
 
     /* Submit the command buffer! */