From e1015700c62d72d891e4924b500f8e23785df87c Mon Sep 17 00:00:00 2001 From: Evan Hemsley <2342303+thatcosmonaut@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:16:20 -0700 Subject: [PATCH] GPU: Allow combinations of read flags on buffers (#16111) (cherry picked from commit 5538d52430890615f5822d8fdac7a32168169a14) --- 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 dfe5994aad..603ca92a30 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -884,8 +884,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 @@ -967,8 +967,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 b5a6e50f1e..8984388c70 100644 --- a/src/gpu/d3d12/SDL_gpu_d3d12.c +++ b/src/gpu/d3d12/SDL_gpu_d3d12.c @@ -1959,22 +1959,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 ae19a13893..0ed974fd63 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -632,17 +632,16 @@ struct VulkanTextureContainer bool canBeCycled; }; -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 { @@ -2561,6 +2560,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. * @@ -2580,17 +2582,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; @@ -2607,63 +2658,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, @@ -2855,27 +2858,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( @@ -2907,7 +2921,7 @@ static VulkanTextureUsageMode VULKAN_INTERNAL_DefaultTextureUsageMode( static void VULKAN_INTERNAL_BufferTransitionFromDefaultUsage( VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer, - VulkanBufferUsageMode destinationUsageMode, + VulkanBufferUsageModeFlags destinationUsageMode, VulkanBuffer *buffer) { VULKAN_INTERNAL_BufferMemoryBarrier( @@ -2921,7 +2935,7 @@ static void VULKAN_INTERNAL_BufferTransitionFromDefaultUsage( static void VULKAN_INTERNAL_BufferTransitionToDefaultUsage( VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer, - VulkanBufferUsageMode sourceUsageMode, + VulkanBufferUsageModeFlags sourceUsageMode, VulkanBuffer *buffer) { VULKAN_INTERNAL_BufferMemoryBarrier( @@ -6053,7 +6067,7 @@ static VulkanBuffer *VULKAN_INTERNAL_PrepareBufferForWrite( VulkanCommandBuffer *commandBuffer, VulkanBufferContainer *bufferContainer, bool cycle, - VulkanBufferUsageMode destinationUsageMode) + VulkanBufferUsageModeFlags destinationUsageMode) { if ( cycle &&