Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ public override bool OnStart()
vertexShader.Dispose();
fragmentShader.Dispose();

if (!Device.TryCreateDataBuffer<VertexPositionColor>(
3, out _vertexBuffer))
if (!Device.TryCreateDataBuffer<VertexPositionColor>(GpuBufferUsageFlags.Vertex, 3, out _vertexBuffer))
{
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions src/cs/examples/Examples.Gpu/Examples/E005_CullMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ public override bool OnStart()
fragmentShader.Dispose();

// Create the vertex buffers. They're the same except for the vertex order.
if (!Device.TryCreateDataBuffer<VertexPositionColor>(3, out _vertexBufferCw))
if (!Device.TryCreateDataBuffer<VertexPositionColor>(GpuBufferUsageFlags.Vertex, 3, out _vertexBufferCw))
{
return false;
}

if (!Device.TryCreateDataBuffer<VertexPositionColor>(3, out _vertexBufferCcw))
if (!Device.TryCreateDataBuffer<VertexPositionColor>(GpuBufferUsageFlags.Vertex, 3, out _vertexBufferCcw))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public override bool OnStart()
vertexShader.Dispose();
fragmentShader.Dispose();

if (!Device.TryCreateDataBuffer<VertexPositionColor>(6, out _vertexBuffer))
if (!Device.TryCreateDataBuffer<VertexPositionColor>(GpuBufferUsageFlags.Vertex, 6, out _vertexBuffer))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ public override bool OnStart()
vertexShader.Dispose();
fragmentShader.Dispose();

if (!Device.TryCreateDataBuffer<VertexPositionColor>(9, out _vertexBuffer))
if (!Device.TryCreateDataBuffer<VertexPositionColor>(GpuBufferUsageFlags.Vertex, 9, out _vertexBuffer))
{
return false;
}

if (!Device.TryCreateDataBuffer<ushort>(6, out _indexBuffer))
if (!Device.TryCreateDataBuffer<ushort>(GpuBufferUsageFlags.Index, 6, out _indexBuffer))
{
return false;
}
Expand Down
5 changes: 2 additions & 3 deletions src/cs/examples/Examples.Gpu/Examples/E008_TexturedQuad.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,12 @@ public override bool OnStart()
return false;
}

if (!Device.TryCreateDataBuffer<VertexPositionTexture>(
4, out _vertexBuffer, "Ravioli Vertex Buffer 🥣"))
if (!Device.TryCreateDataBuffer<VertexPositionTexture>(GpuBufferUsageFlags.Vertex, 4, out _vertexBuffer, "Ravioli Vertex Buffer 🥣"))
{
return false;
}

if (!Device.TryCreateDataBuffer<ushort>(6, out _indexBuffer))
if (!Device.TryCreateDataBuffer<ushort>(GpuBufferUsageFlags.Index, 6, out _indexBuffer))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,12 @@ public override bool OnStart()
return false;
}

if (!Device.TryCreateDataBuffer<VertexPositionTexture>(4, out _vertexBuffer))
if (!Device.TryCreateDataBuffer<VertexPositionTexture>(GpuBufferUsageFlags.Vertex, 4, out _vertexBuffer))
{
return false;
}

if (!Device.TryCreateDataBuffer<ushort>(6, out _indexBuffer))
if (!Device.TryCreateDataBuffer<ushort>(GpuBufferUsageFlags.Index, 6, out _indexBuffer))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public override bool OnStart()

var verticesCount = 6;
var verticesBytesCount = VertexPositionTexture.SizeOf * verticesCount;
if (!Device.TryCreateDataBuffer<VertexPositionTexture>(verticesCount, out _vertexBuffer!))
if (!Device.TryCreateDataBuffer<VertexPositionTexture>(GpuBufferUsageFlags.Vertex, verticesCount, out _vertexBuffer!))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ private bool TryCreateVertexBuffer()
const int vertexCount = 24;
var bufferSize = Unsafe.SizeOf<Vertex>() * vertexCount;

if (!_device.TryCreateDataBuffer<Vertex>(vertexCount, out _vertexBuffer))
if (!_device.TryCreateDataBuffer<Vertex>(GpuBufferUsageFlags.Vertex, vertexCount, out _vertexBuffer))
{
return false;
}
Expand Down Expand Up @@ -344,7 +344,7 @@ private bool TryCreateIndexBuffer()
const int indexCount = 36;
var bufferSize = Unsafe.SizeOf<ushort>() * indexCount;

if (!_device.TryCreateDataBuffer<ushort>(indexCount, out _indexBuffer))
if (!_device.TryCreateDataBuffer<ushort>(GpuBufferUsageFlags.Index, indexCount, out _indexBuffer))
{
return false;
}
Expand Down
41 changes: 41 additions & 0 deletions src/cs/production/SDL/GPU/GpuBufferUsageFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved.
// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information.

namespace bottlenoselabs.SDL;

/// <summary>
/// Specifies how a buffer is intended to be used by the client.
/// </summary>
[Flags]
public enum GpuBufferUsageFlags
{
/// <summary>
/// Buffer is a vertex buffer.
/// </summary>
Vertex = 1 << 0,

/// <summary>
/// Buffer is an index buffer.
/// </summary>
Index = 1 << 1,

/// <summary>
/// Buffer is an indirect buffer.
/// </summary>
Indirect = 1 << 2,

/// <summary>
/// Buffer supports storage reads in graphics stages.
/// </summary>
GraphicsStorageRead = 1 << 3,

/// <summary>
/// Buffer supports storage reads in the compute stage.
/// </summary>
ComputeStorageRead = 1 << 4,

/// <summary>
/// Buffer supports storage writes in the compute stage.
/// </summary>
ComputeStorageWrite = 1 << 5
}
49 changes: 45 additions & 4 deletions src/cs/production/SDL/GPU/GpuCommandBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,22 @@ public GpuComputePass BeginComputePass(in GpuComputePassParameters parameters)
return computePass;
}

/// <summary>
/// Pushes the specified data to a vertex shader uniform slot. Subsequent draw calls will
/// use this uniform data.
/// </summary>
/// <param name="data">TODO.</param>
/// <param name="startIndex">Index of the uniform slot to push data to.</param>
/// <typeparam name="T">Data type. It must respect std140 layout conventions.</typeparam>
public void PushVertexShaderUniformData<T>(in T data, int startIndex = 0)
where T : unmanaged
{
fixed (T* pointer = &data)
{
SDL_PushGPUVertexUniformData(HandleTyped, (uint)startIndex, pointer, (uint)sizeof(T));
}
}

/// <summary>
/// Pushes the specified <see cref="Matrix4x4" /> to a vertex shader uniform slot. Subsequent draw calls will
/// use this uniform data.
Expand All @@ -272,9 +288,22 @@ public GpuComputePass BeginComputePass(in GpuComputePassParameters parameters)
/// <param name="slotIndex">The vertex shader uniform slot to push data to.</param>
public void PushVertexShaderUniformMatrix(in Matrix4x4 matrix, int slotIndex = 0)
{
fixed (Matrix4x4* pointer = &matrix)
PushVertexShaderUniformData(matrix, slotIndex);
}

/// <summary>
/// Pushes the specified data to a fragment shader uniform slot. Subsequent draw calls will
/// use this uniform data.
/// </summary>
/// <param name="data">TODO.</param>
/// <param name="startIndex">Index of the uniform slot to push data to.</param>
/// <typeparam name="T">Data type. It must respect std140 layout conventions.</typeparam>
public void PushFragmentShaderUniformData<T>(in T data, int startIndex = 0)
where T : unmanaged
{
fixed (T* pointer = &data)
{
SDL_PushGPUVertexUniformData(HandleTyped, (uint)slotIndex, pointer, (uint)sizeof(Matrix4x4));
SDL_PushGPUFragmentUniformData(HandleTyped, (uint)startIndex, pointer, (uint)sizeof(T));
}
}

Expand All @@ -286,9 +315,21 @@ public void PushVertexShaderUniformMatrix(in Matrix4x4 matrix, int slotIndex = 0
/// <param name="slotIndex">The fragment shader uniform slot to push data to.</param>
public void PushFragmentShaderUniformColor(in Rgba32F color, int slotIndex = 0)
{
fixed (Rgba32F* pointer = &color)
PushFragmentShaderUniformData(color, slotIndex);
}

/// <summary>
/// Pushes data to a uniform slot on the command buffer.
/// </summary>
/// <param name="data">TODO.</param>
/// <param name="startIndex">Index of the uniform slot to push data to.</param>
/// <typeparam name="T">Data type. It must respect std140 layout conventions.</typeparam>
public void PushComputeShaderUniformData<T>(in T data, int startIndex = 0)
where T : unmanaged
{
fixed (T* pointer = &data)
{
SDL_PushGPUFragmentUniformData(HandleTyped, (uint)slotIndex, pointer, (uint)sizeof(Rgba32F));
SDL_PushGPUComputeUniformData(HandleTyped, (uint)startIndex, pointer, (uint)sizeof(T));
}
}

Expand Down
91 changes: 90 additions & 1 deletion src/cs/production/SDL/GPU/GpuComputePass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public sealed unsafe class GpuComputePass : Poolable<GpuComputePass>
internal SDL_GPUComputePass* Handle;
#pragma warning restore SA1401

private bool _isPipelineBound;

/// <summary>
/// Gets the <see cref="GpuDevice" /> instance associated with the compute pass.
/// </summary>
Expand All @@ -37,6 +39,7 @@ internal GpuComputePass(GpuDevice device)
public void BindShader(GpuComputeShader computeShader)
{
SDL_BindGPUComputePipeline(Handle, computeShader.HandleTyped);
_isPipelineBound = true;
}

/// <summary>
Expand All @@ -50,12 +53,97 @@ public void Dispatch(
int workGroupsCountY,
int workGroupsCountZ)
{
if (!_isPipelineBound)
{
throw new InvalidOperationException("A compute shader must be bound before dispatching.");
}

SDL_DispatchGPUCompute(
Handle, (uint)workGroupsCountX, (uint)workGroupsCountY, (uint)workGroupsCountZ);
}

/// <summary>
/// Ends the compute pass.
/// Dispatches compute work with parameters set from a buffer.
/// </summary>
/// <param name="buffer">The buffer containing dispatch parameters.</param>
/// <param name="offset">the offset to start reading from the dispatch buffer.</param>
public void DispatchIndirect(GpuDataBuffer buffer, int offset)
{
if (!_isPipelineBound)
{
throw new InvalidOperationException("A compute shader must be bound before dispatching.");
}

SDL_DispatchGPUComputeIndirect(Handle, buffer.HandleTyped, (uint)offset);
}

/// <summary>
/// Binds storage textures as readonly for use on the compute pipeline.
/// </summary>
/// <param name="startIndex">Index of the slot to begin binding from.</param>
/// <param name="textures">An array of <see cref="GpuTexture"/>s to bind.</param>
public void BindStorageTextures(int startIndex, params ReadOnlySpan<GpuTexture> textures)
{
var handles = stackalloc SDL_GPUTexture*[textures.Length];
for (var i = 0; i < textures.Length; i++)
{
handles[i] = (SDL_GPUTexture*)textures[i].Handle;
}

SDL_BindGPUComputeStorageTextures(
Handle,
(uint)startIndex,
handles,
(uint)textures.Length);
}

/// <summary>
/// Binds storage buffers as readonly for use on the compute pipeline.
/// </summary>
/// <param name="startIndex">Index of the slot to begin binding from.</param>
/// <param name="buffers">An array of <see cref="GpuDataBuffer"/>s to bind.</param>
public void BindStorageBuffers(int startIndex, params ReadOnlySpan<GpuDataBuffer> buffers)
{
var handles = stackalloc SDL_GPUBuffer*[buffers.Length];
for (var i = 0; i < buffers.Length; i++)
{
handles[i] = (SDL_GPUBuffer*)buffers[i].Handle;
}

SDL_BindGPUComputeStorageBuffers(
Handle,
(uint)startIndex,
handles,
(uint)buffers.Length);
}

/// <summary>
/// Binds texture-sampler pairs for use on the compute shader pipeline.
/// </summary>
/// <param name="startIndex">Index of the slot to begin binding from.</param>
/// <param name="samplers">An array of <see cref="GpuTexture"/> and <see cref="GpuSampler"/> pairs to bind.</param>
public void BindSamplers(
int startIndex,
params ReadOnlySpan<(GpuTexture Texture, GpuSampler Sampler)> samplers)
{
var bindings = stackalloc SDL_GPUTextureSamplerBinding[samplers.Length];
for (var i = 0; i < samplers.Length; i++)
{
var src = samplers[i];
ref var dst = ref bindings[i];
dst.texture = (SDL_GPUTexture*)src.Texture.Handle;
dst.sampler = (SDL_GPUSampler*)src.Sampler.Handle;
}

SDL_BindGPUComputeSamplers(
Handle,
(uint)startIndex,
bindings,
(uint)samplers.Length);
}

/// <summary>
/// Ends a render pass.
/// </summary>
/// <exception cref="InvalidOperationException">The associated command buffer was submitted.</exception>
public void End()
Expand All @@ -68,5 +156,6 @@ public void End()
protected override void Reset()
{
Device.EndComputePassTryInternal(this);
_isPipelineBound = false;
}
}
6 changes: 4 additions & 2 deletions src/cs/production/SDL/GPU/GpuDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ internal GpuDevice(ILogger<GpuDevice> logger, GpuDeviceOptions? options)
PoolRenderPass = new Pool<GpuRenderPass>(
_logger, () => new GpuRenderPass(this), "GpuRenderPasses");
PoolComputePass = new Pool<GpuComputePass>(
_logger, () => new GpuComputePass(this), "GpuComputePass");
_logger, () => new GpuComputePass(this), "GpuComputePasses");

SupportedShaderFormats = (GpuShaderFormats)(uint)SDL_GetGPUShaderFormats(HandleTyped);

Expand Down Expand Up @@ -442,6 +442,7 @@ public bool TryCreateGraphicsPipeline(
/// <summary>
/// Attempts to create a new <see cref="GpuDataBuffer" /> instance.
/// </summary>
/// <param name="usage">How the buffer is intended to be used by the client.</param>
/// <param name="elementCount">
/// The maximum number of <typeparamref name="TElement" /> elements the buffer can hold.
/// </param>
Expand All @@ -452,13 +453,14 @@ public bool TryCreateGraphicsPipeline(
/// <typeparam name="TElement">The type of data buffer element.</typeparam>
/// <returns><c>true</c> if the data buffer was successfully created; otherwise, <c>false</c>.</returns>
public bool TryCreateDataBuffer<TElement>(
GpuBufferUsageFlags usage,
int elementCount,
[NotNullWhen(true)] out GpuDataBuffer? buffer,
string? name = null)
where TElement : unmanaged
{
var bufferCreateInfo = default(SDL_GPUBufferCreateInfo);
bufferCreateInfo.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
bufferCreateInfo.usage = (uint)usage;
bufferCreateInfo.size = (uint)(sizeof(TElement) * elementCount);
var handle = SDL_CreateGPUBuffer(HandleTyped, &bufferCreateInfo);
if (handle == null)
Expand Down