GPU: Allow combinations of read flags on buffers (#16111)

(cherry picked from commit 5538d52430)
This commit is contained in:
Evan Hemsley
2026-08-03 13:16:20 -07:00
committed by Sam Lantinga
parent 1ef4551c6d
commit e1015700c6
3 changed files with 131 additions and 102 deletions

View File

@@ -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.

View File

@@ -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(

View File

@@ -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 &&