From 5538d52430890615f5822d8fdac7a32168169a14 Mon Sep 17 00:00:00 2001
From: Evan Hemsley <[EMAIL REDACTED]>
Date: Mon, 3 Aug 2026 13:16:20 -0700
Subject: [PATCH] GPU: Allow combinations of read flags on buffers (#16111)
---
include/SDL3/SDL_gpu.h | 10 +-
src/gpu/d3d12/SDL_gpu_d3d12.c | 35 ++++--
src/gpu/vulkan/SDL_gpu_vulkan.c | 188 +++++++++++++++++---------------
3 files changed, 131 insertions(+), 102 deletions(-)
diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h
index eb59abc9ece21..0ab657677c719 100644
--- a/include/SDL3/SDL_gpu.h
+++ b/include/SDL3/SDL_gpu.h
@@ -883,8 +883,8 @@ typedef enum SDL_GPUTextureFormat
/**
* Specifies how a texture is intended to be used by the client.
*
- * A texture must have at least one usage flag. Note that some usage flag
- * combinations are invalid.
+ * A texture must have at least one usage flag.
+ * Note that combining SAMPLER with STORAGE_READ flags is invalid.
*
* With regards to compute storage usage, READ | WRITE means that you can have
* shader A that only writes into the texture and shader B that only reads
@@ -966,8 +966,10 @@ typedef enum SDL_GPUCubeMapFace
/**
* Specifies how a buffer is intended to be used by the client.
*
- * A buffer must have at least one usage flag. Note that some usage flag
- * combinations are invalid.
+ * A buffer must have at least one usage flag.
+ *
+ * If a buffer has multiple read usages, this may lead to a performance penalty
+ * due to more conservative memory barriers, but it also may not necessarily affect the performance.
*
* Unlike textures, READ | WRITE can be used for simultaneous read-write
* usage. The same data synchronization concerns as textures apply.
diff --git a/src/gpu/d3d12/SDL_gpu_d3d12.c b/src/gpu/d3d12/SDL_gpu_d3d12.c
index 75fe198613d8b..1a75e06443381 100644
--- a/src/gpu/d3d12/SDL_gpu_d3d12.c
+++ b/src/gpu/d3d12/SDL_gpu_d3d12.c
@@ -1996,22 +1996,35 @@ static void D3D12_INTERNAL_TextureTransitionToDefaultUsage(
static D3D12_RESOURCE_STATES D3D12_INTERNAL_DefaultBufferResourceState(
D3D12Buffer *buffer)
{
+ D3D12_RESOURCE_STATES states = 0;
+
if (buffer->container->usage & SDL_GPU_BUFFERUSAGE_VERTEX) {
- return D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
- } else if (buffer->container->usage & SDL_GPU_BUFFERUSAGE_INDEX) {
- return D3D12_RESOURCE_STATE_INDEX_BUFFER;
- } else if (buffer->container->usage & SDL_GPU_BUFFERUSAGE_INDIRECT) {
- return D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT;
- } else if (buffer->container->usage & SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ) {
- return D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE;
- } else if (buffer->container->usage & SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ) {
- return D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
- } else if (buffer->container->usage & SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_WRITE) {
+ states |= D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
+ }
+ if (buffer->container->usage & SDL_GPU_BUFFERUSAGE_INDEX) {
+ states |= D3D12_RESOURCE_STATE_INDEX_BUFFER;
+ }
+ if (buffer->container->usage & SDL_GPU_BUFFERUSAGE_INDIRECT) {
+ states |= D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT;
+ }
+ if (buffer->container->usage & SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ) {
+ states |= D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE;
+ }
+ if (buffer->container->usage & SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ) {
+ states |= D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
+ }
+
+ // If no read flags are set, read-write can be the default.
+ if (!states && buffer->container->usage & SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_WRITE) {
return D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
- } else {
+ }
+
+ if (!states) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Buffer has no default usage mode!");
return D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
}
+
+ return states;
}
static void D3D12_INTERNAL_BufferBarrier(
diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c
index 24c9160ef3a34..3ff8237b4a97c 100644
--- a/src/gpu/vulkan/SDL_gpu_vulkan.c
+++ b/src/gpu/vulkan/SDL_gpu_vulkan.c
@@ -672,17 +672,16 @@ struct VulkanTextureContainer
bool externallyManaged; // true for XR swapchain images
};
-typedef enum VulkanBufferUsageMode
-{
- VULKAN_BUFFER_USAGE_MODE_COPY_SOURCE,
- VULKAN_BUFFER_USAGE_MODE_COPY_DESTINATION,
- VULKAN_BUFFER_USAGE_MODE_VERTEX_READ,
- VULKAN_BUFFER_USAGE_MODE_INDEX_READ,
- VULKAN_BUFFER_USAGE_MODE_INDIRECT,
- VULKAN_BUFFER_USAGE_MODE_GRAPHICS_STORAGE_READ,
- VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ,
- VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE,
-} VulkanBufferUsageMode;
+typedef Uint32 VulkanBufferUsageModeFlags;
+
+#define VULKAN_BUFFER_USAGE_MODE_COPY_SOURCE (1u << 0)
+#define VULKAN_BUFFER_USAGE_MODE_COPY_DESTINATION (1u << 1)
+#define VULKAN_BUFFER_USAGE_MODE_VERTEX_READ (1u << 2)
+#define VULKAN_BUFFER_USAGE_MODE_INDEX_READ (1u << 3)
+#define VULKAN_BUFFER_USAGE_MODE_INDIRECT (1u << 4)
+#define VULKAN_BUFFER_USAGE_MODE_GRAPHICS_STORAGE_READ (1u << 5)
+#define VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ (1u << 6)
+#define VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE (1u << 7)
typedef enum VulkanTextureUsageMode
{
@@ -2609,6 +2608,9 @@ static void VULKAN_INTERNAL_TrackUniformBuffer(
* These indicate the current usage of that resource on the command buffer.
* The transition from one usage mode to another indicates how the barrier should be constructed.
*
+ * For buffer reads, read usage modes can be combined.
+ * This can be a useful shortcut in certain cases, like when reading GLTF data.
+ *
* Pipeline barriers cannot be inserted during a render pass, but they can be inserted
* during a compute or copy pass.
*
@@ -2628,17 +2630,66 @@ static void VULKAN_INTERNAL_TrackUniformBuffer(
* and transition it back to its default on EndRenderPass.
*
* This strategy imposes certain limitations on resource usage flags.
- * For example, a texture cannot have both the SAMPLER and GRAPHICS_STORAGE usage flags,
+ * For example, a texture cannot have both the SAMPLER and STORAGE_READ usage flags,
* because then it is impossible for the backend to infer which default usage mode the texture should use.
*
* Sync hazards can be detected by setting VK_KHRONOS_VALIDATION_VALIDATE_SYNC=1 when using validation layers.
*/
+static void VULKAN_INTERNAL_SetMemoryBarrierFlags(
+ VulkanBufferUsageModeFlags usageModeFlags,
+ VkPipelineStageFlags *stageFlags,
+ VkAccessFlags *accessMask)
+{
+ // Combinable read flags
+ if (usageModeFlags & VULKAN_BUFFER_USAGE_MODE_VERTEX_READ) {
+ *stageFlags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
+ *accessMask |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
+ }
+
+ if (usageModeFlags & VULKAN_BUFFER_USAGE_MODE_INDEX_READ) {
+ *stageFlags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
+ *accessMask |= VK_ACCESS_INDEX_READ_BIT;
+ }
+
+ if (usageModeFlags & VULKAN_BUFFER_USAGE_MODE_INDIRECT) {
+ *stageFlags |= VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT;
+ *accessMask |= VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
+ }
+
+ if (usageModeFlags & VULKAN_BUFFER_USAGE_MODE_GRAPHICS_STORAGE_READ) {
+ *stageFlags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+ *accessMask |= VK_ACCESS_SHADER_READ_BIT;
+ }
+
+ if (usageModeFlags & VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ) {
+ *stageFlags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+ *accessMask |= VK_ACCESS_SHADER_READ_BIT;
+ }
+
+ // Transfer flags (these will never be combined with other usages)
+ if (usageModeFlags & VULKAN_BUFFER_USAGE_MODE_COPY_SOURCE) {
+ *stageFlags |= VK_PIPELINE_STAGE_TRANSFER_BIT;
+ *accessMask |= VK_ACCESS_TRANSFER_READ_BIT;
+ }
+
+ if (usageModeFlags & VULKAN_BUFFER_USAGE_MODE_COPY_DESTINATION) {
+ *stageFlags |= VK_PIPELINE_STAGE_TRANSFER_BIT;
+ *accessMask |= VK_ACCESS_TRANSFER_WRITE_BIT;
+ }
+
+ // Read-write flag
+ if (usageModeFlags & VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE) {
+ *stageFlags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+ *accessMask |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
+ }
+}
+
static void VULKAN_INTERNAL_BufferMemoryBarrier(
VulkanRenderer *renderer,
VulkanCommandBuffer *commandBuffer,
- VulkanBufferUsageMode sourceUsageMode,
- VulkanBufferUsageMode destinationUsageMode,
+ VulkanBufferUsageModeFlags sourceUsageMode,
+ VulkanBufferUsageModeFlags destinationUsageMode,
VulkanBuffer *buffer)
{
VkPipelineStageFlags srcStages = 0;
@@ -2655,63 +2706,15 @@ static void VULKAN_INTERNAL_BufferMemoryBarrier(
memoryBarrier.offset = 0;
memoryBarrier.size = buffer->size;
- if (sourceUsageMode == VULKAN_BUFFER_USAGE_MODE_COPY_SOURCE) {
- srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT;
- memoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
- } else if (sourceUsageMode == VULKAN_BUFFER_USAGE_MODE_COPY_DESTINATION) {
- srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT;
- memoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
- } else if (sourceUsageMode == VULKAN_BUFFER_USAGE_MODE_VERTEX_READ) {
- srcStages = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
- memoryBarrier.srcAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
- } else if (sourceUsageMode == VULKAN_BUFFER_USAGE_MODE_INDEX_READ) {
- srcStages = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
- memoryBarrier.srcAccessMask = VK_ACCESS_INDEX_READ_BIT;
- } else if (sourceUsageMode == VULKAN_BUFFER_USAGE_MODE_INDIRECT) {
- srcStages = VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT;
- memoryBarrier.srcAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
- } else if (sourceUsageMode == VULKAN_BUFFER_USAGE_MODE_GRAPHICS_STORAGE_READ) {
- srcStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
- memoryBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
- } else if (sourceUsageMode == VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ) {
- srcStages = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
- memoryBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
- } else if (sourceUsageMode == VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE) {
- srcStages = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
- memoryBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
- } else {
- SDL_LogError(SDL_LOG_CATEGORY_GPU, "Unrecognized buffer source barrier type!");
- return;
- }
+ VULKAN_INTERNAL_SetMemoryBarrierFlags(
+ sourceUsageMode,
+ &srcStages,
+ &memoryBarrier.srcAccessMask);
- if (destinationUsageMode == VULKAN_BUFFER_USAGE_MODE_COPY_SOURCE) {
- dstStages = VK_PIPELINE_STAGE_TRANSFER_BIT;
- memoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
- } else if (destinationUsageMode == VULKAN_BUFFER_USAGE_MODE_COPY_DESTINATION) {
- dstStages = VK_PIPELINE_STAGE_TRANSFER_BIT;
- memoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
- } else if (destinationUsageMode == VULKAN_BUFFER_USAGE_MODE_VERTEX_READ) {
- dstStages = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
- memoryBarrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
- } else if (destinationUsageMode == VULKAN_BUFFER_USAGE_MODE_INDEX_READ) {
- dstStages = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
- memoryBarrier.dstAccessMask = VK_ACCESS_INDEX_READ_BIT;
- } else if (destinationUsageMode == VULKAN_BUFFER_USAGE_MODE_INDIRECT) {
- dstStages = VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT;
- memoryBarrier.dstAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
- } else if (destinationUsageMode == VULKAN_BUFFER_USAGE_MODE_GRAPHICS_STORAGE_READ) {
- dstStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
- memoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
- } else if (destinationUsageMode == VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ) {
- dstStages = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
- memoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
- } else if (destinationUsageMode == VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE) {
- dstStages = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
- memoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
- } else {
- SDL_LogError(SDL_LOG_CATEGORY_GPU, "Unrecognized buffer destination barrier type!");
- return;
- }
+ VULKAN_INTERNAL_SetMemoryBarrierFlags(
+ destinationUsageMode,
+ &dstStages,
+ &memoryBarrier.dstAccessMask);
renderer->vkCmdPipelineBarrier(
commandBuffer->commandBuffer,
@@ -2903,27 +2906,38 @@ static void VULKAN_INTERNAL_TextureSubresourceMemoryBarrier(
textureSubresource->parent);
}
-static VulkanBufferUsageMode VULKAN_INTERNAL_DefaultBufferUsageMode(
+static VulkanBufferUsageModeFlags VULKAN_INTERNAL_DefaultBufferUsageMode(
VulkanBuffer *buffer)
{
- // NOTE: order matters here!
+ VulkanBufferUsageModeFlags flags = 0;
if (buffer->usage & SDL_GPU_BUFFERUSAGE_VERTEX) {
- return VULKAN_BUFFER_USAGE_MODE_VERTEX_READ;
- } else if (buffer->usage & SDL_GPU_BUFFERUSAGE_INDEX) {
- return VULKAN_BUFFER_USAGE_MODE_INDEX_READ;
- } else if (buffer->usage & SDL_GPU_BUFFERUSAGE_INDIRECT) {
- return VULKAN_BUFFER_USAGE_MODE_INDIRECT;
- } else if (buffer->usage & SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ) {
- return VULKAN_BUFFER_USAGE_MODE_GRAPHICS_STORAGE_READ;
- } else if (buffer->usage & SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ) {
- return VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ;
- } else if (buffer->usage & SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_WRITE) {
- return VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE;
- } else {
+ flags |= VULKAN_BUFFER_USAGE_MODE_VERTEX_READ;
+ }
+ if (buffer->usage & SDL_GPU_BUFFERUSAGE_INDEX) {
+ flags |= VULKAN_BUFFER_USAGE_MODE_INDEX_READ;
+ }
+ if (buffer->usage & SDL_GPU_BUFFERUSAGE_INDIRECT) {
+ flags |= VULKAN_BUFFER_USAGE_MODE_INDIRECT;
+ }
+ if (buffer->usage & SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ) {
+ flags |= VULKAN_BUFFER_USAGE_MODE_GRAPHICS_STORAGE_READ;
+ }
+ if (buffer->usage & SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ) {
+ flags |= VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ;
+ }
+
+ // If no read flags are set, read-write can be the default.
+ if (!flags && buffer->usage & SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_WRITE) {
+ flags = VULKAN_BUFFER_USAGE_MODE_COMPUTE_STORAGE_READ_WRITE;
+ }
+
+ if (!flags) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Buffer has no default usage mode!");
return VULKAN_BUFFER_USAGE_MODE_VERTEX_READ;
}
+
+ return flags;
}
static VulkanTextureUsageMode VULKAN_INTERNAL_DefaultTextureUsageMode(
@@ -2955,7 +2969,7 @@ static VulkanTextureUsageMode VULKAN_INTERNAL_DefaultTextureUsageMode(
static void VULKAN_INTERNAL_BufferTransitionFromDefaultUsage(
VulkanRenderer *renderer,
VulkanCommandBuffer *commandBuffer,
- VulkanBufferUsageMode destinationUsageMode,
+ VulkanBufferUsageModeFlags destinationUsageMode,
VulkanBuffer *buffer)
{
VULKAN_INTERNAL_BufferMemoryBarrier(
@@ -2969,7 +2983,7 @@ static void VULKAN_INTERNAL_BufferTransitionFromDefaultUsage(
static void VULKAN_INTERNAL_BufferTransitionToDefaultUsage(
VulkanRenderer *renderer,
VulkanCommandBuffer *commandBuffer,
- VulkanBufferUsageMode sourceUsageMode,
+ VulkanBufferUsageModeFlags sourceUsageMode,
VulkanBuffer *buffer)
{
VULKAN_INTERNAL_BufferMemoryBarrier(
@@ -6102,7 +6116,7 @@ static VulkanBuffer *VULKAN_INTERNAL_PrepareBufferForWrite(
VulkanCommandBuffer *commandBuffer,
VulkanBufferContainer *bufferContainer,
bool cycle,
- VulkanBufferUsageMode destinationUsageMode)
+ VulkanBufferUsageModeFlags destinationUsageMode)
{
if (
cycle &&