Skip to content

Commit 4b07205

Browse files
committed
WIP compute pipeline support
1 parent d09eec7 commit 4b07205

File tree

4 files changed

+180
-7
lines changed

4 files changed

+180
-7
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Bottlenose Labs Inc. (https://github.com/bottlenoselabs). All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the Git repository root directory for full license information.
3+
4+
namespace bottlenoselabs.SDL;
5+
6+
/// <summary>
7+
/// Specifies how a buffer is intended to be used by the client.
8+
/// </summary>
9+
[Flags]
10+
public enum GpuBufferUsageFlags
11+
{
12+
/// <summary>
13+
/// Buffer is a vertex buffer.
14+
/// </summary>
15+
Vertex = 1 << 0,
16+
17+
/// <summary>
18+
/// Buffer is an index buffer.
19+
/// </summary>
20+
Index = 1 << 1,
21+
22+
/// <summary>
23+
/// Buffer is an indirect buffer.
24+
/// </summary>
25+
Indirect = 1 << 2,
26+
27+
/// <summary>
28+
/// Buffer supports storage reads in graphics stages.
29+
/// </summary>
30+
GraphicsStorageRead = 1 << 3,
31+
32+
/// <summary>
33+
/// Buffer supports storage reads in the compute stage.
34+
/// </summary>
35+
ComputeStorageRead = 1 << 4,
36+
37+
/// <summary>
38+
/// Buffer supports storage writes in the compute stage.
39+
/// </summary>
40+
ComputeStorageWrite = 1 << 5
41+
}

src/cs/production/SDL/GPU/GpuCommandBuffer.cs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,22 @@ public GpuComputePass BeginComputePass(in GpuComputePassParameters parameters)
264264
return computePass;
265265
}
266266

267+
/// <summary>
268+
/// Pushes the specified data to a vertex shader uniform slot. Subsequent draw calls will
269+
/// use this uniform data.
270+
/// </summary>
271+
/// <param name="data">TODO.</param>
272+
/// <param name="startIndex">Index of the uniform slot to push data to.</param>
273+
/// <typeparam name="T">Data type. It must respect std140 layout conventions.</typeparam>
274+
public void PushVertexShaderUniformData<T>(in T data, int startIndex = 0)
275+
where T : unmanaged
276+
{
277+
fixed (T* pointer = &data)
278+
{
279+
SDL_PushGPUVertexUniformData(HandleTyped, (uint)startIndex, pointer, (uint)sizeof(T));
280+
}
281+
}
282+
267283
/// <summary>
268284
/// Pushes the specified <see cref="Matrix4x4" /> to a vertex shader uniform slot. Subsequent draw calls will
269285
/// use this uniform data.
@@ -272,9 +288,22 @@ public GpuComputePass BeginComputePass(in GpuComputePassParameters parameters)
272288
/// <param name="slotIndex">The vertex shader uniform slot to push data to.</param>
273289
public void PushVertexShaderUniformMatrix(in Matrix4x4 matrix, int slotIndex = 0)
274290
{
275-
fixed (Matrix4x4* pointer = &matrix)
291+
PushVertexShaderUniformData(matrix, slotIndex);
292+
}
293+
294+
/// <summary>
295+
/// Pushes the specified data to a fragment shader uniform slot. Subsequent draw calls will
296+
/// use this uniform data.
297+
/// </summary>
298+
/// <param name="data">TODO.</param>
299+
/// <param name="startIndex">Index of the uniform slot to push data to.</param>
300+
/// <typeparam name="T">Data type. It must respect std140 layout conventions.</typeparam>
301+
public void PushFragmentShaderUniformData<T>(in T data, int startIndex = 0)
302+
where T : unmanaged
303+
{
304+
fixed (T* pointer = &data)
276305
{
277-
SDL_PushGPUVertexUniformData(HandleTyped, (uint)slotIndex, pointer, (uint)sizeof(Matrix4x4));
306+
SDL_PushGPUFragmentUniformData(HandleTyped, (uint)startIndex, pointer, (uint)sizeof(T));
278307
}
279308
}
280309

@@ -286,9 +315,21 @@ public void PushVertexShaderUniformMatrix(in Matrix4x4 matrix, int slotIndex = 0
286315
/// <param name="slotIndex">The fragment shader uniform slot to push data to.</param>
287316
public void PushFragmentShaderUniformColor(in Rgba32F color, int slotIndex = 0)
288317
{
289-
fixed (Rgba32F* pointer = &color)
318+
PushFragmentShaderUniformData(color, slotIndex);
319+
}
320+
321+
/// <summary>
322+
/// Pushes data to a uniform slot on the command buffer.
323+
/// </summary>
324+
/// <param name="data">TODO.</param>
325+
/// <param name="startIndex">Index of the uniform slot to push data to.</param>
326+
/// <typeparam name="T">Data type. It must respect std140 layout conventions.</typeparam>
327+
public void PushComputeShaderUniformData<T>(in T data, int startIndex = 0)
328+
where T : unmanaged
329+
{
330+
fixed (T* pointer = &data)
290331
{
291-
SDL_PushGPUFragmentUniformData(HandleTyped, (uint)slotIndex, pointer, (uint)sizeof(Rgba32F));
332+
SDL_PushGPUComputeUniformData(HandleTyped, (uint)startIndex, pointer, (uint)sizeof(T));
292333
}
293334
}
294335

src/cs/production/SDL/GPU/GpuComputePass.cs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public sealed unsafe class GpuComputePass : Poolable<GpuComputePass>
1313
internal SDL_GPUComputePass* Handle;
1414
#pragma warning restore SA1401
1515

16+
private bool _isPipelineBound;
17+
1618
/// <summary>
1719
/// Gets the <see cref="GpuDevice" /> instance associated with the compute pass.
1820
/// </summary>
@@ -37,6 +39,7 @@ internal GpuComputePass(GpuDevice device)
3739
public void BindShader(GpuComputeShader computeShader)
3840
{
3941
SDL_BindGPUComputePipeline(Handle, computeShader.HandleTyped);
42+
_isPipelineBound = true;
4043
}
4144

4245
/// <summary>
@@ -50,12 +53,97 @@ public void Dispatch(
5053
int workGroupsCountY,
5154
int workGroupsCountZ)
5255
{
56+
if (!_isPipelineBound)
57+
{
58+
throw new InvalidOperationException("A compute shader must be bound before dispatching.");
59+
}
60+
5361
SDL_DispatchGPUCompute(
5462
Handle, (uint)workGroupsCountX, (uint)workGroupsCountY, (uint)workGroupsCountZ);
5563
}
5664

5765
/// <summary>
58-
/// Ends the compute pass.
66+
/// Dispatches compute work with parameters set from a buffer.
67+
/// </summary>
68+
/// <param name="buffer">The buffer containing dispatch parameters.</param>
69+
/// <param name="offset">the offset to start reading from the dispatch buffer.</param>
70+
public void DispatchIndirect(GpuDataBuffer buffer, int offset)
71+
{
72+
if (!_isPipelineBound)
73+
{
74+
throw new InvalidOperationException("A compute shader must be bound before dispatching.");
75+
}
76+
77+
SDL_DispatchGPUComputeIndirect(Handle, buffer.HandleTyped, (uint)offset);
78+
}
79+
80+
/// <summary>
81+
/// Binds storage textures as readonly for use on the compute pipeline.
82+
/// </summary>
83+
/// <param name="startIndex">Index of the slot to begin binding from.</param>
84+
/// <param name="textures">An array of <see cref="GpuTexture"/>s to bind.</param>
85+
public void BindStorageTextures(int startIndex, params ReadOnlySpan<GpuTexture> textures)
86+
{
87+
var handles = stackalloc SDL_GPUTexture*[textures.Length];
88+
for (var i = 0; i < textures.Length; i++)
89+
{
90+
handles[i] = (SDL_GPUTexture*)textures[i].Handle;
91+
}
92+
93+
SDL_BindGPUComputeStorageTextures(
94+
Handle,
95+
(uint)startIndex,
96+
handles,
97+
(uint)textures.Length);
98+
}
99+
100+
/// <summary>
101+
/// Binds storage buffers as readonly for use on the compute pipeline.
102+
/// </summary>
103+
/// <param name="startIndex">Index of the slot to begin binding from.</param>
104+
/// <param name="buffers">An array of <see cref="GpuDataBuffer"/>s to bind.</param>
105+
public void BindStorageBuffers(int startIndex, params ReadOnlySpan<GpuDataBuffer> buffers)
106+
{
107+
var handles = stackalloc SDL_GPUBuffer*[buffers.Length];
108+
for (var i = 0; i < buffers.Length; i++)
109+
{
110+
handles[i] = (SDL_GPUBuffer*)buffers[i].Handle;
111+
}
112+
113+
SDL_BindGPUComputeStorageBuffers(
114+
Handle,
115+
(uint)startIndex,
116+
handles,
117+
(uint)buffers.Length);
118+
}
119+
120+
/// <summary>
121+
/// Binds texture-sampler pairs for use on the compute shader pipeline.
122+
/// </summary>
123+
/// <param name="startIndex">Index of the slot to begin binding from.</param>
124+
/// <param name="samplers">An array of <see cref="GpuTexture"/> and <see cref="GpuSampler"/> pairs to bind.</param>
125+
public void BindSamplers(
126+
int startIndex,
127+
params ReadOnlySpan<(GpuTexture Texture, GpuSampler Sampler)> samplers)
128+
{
129+
var bindings = stackalloc SDL_GPUTextureSamplerBinding[samplers.Length];
130+
for (var i = 0; i < samplers.Length; i++)
131+
{
132+
var src = samplers[i];
133+
ref var dst = ref bindings[i];
134+
dst.texture = (SDL_GPUTexture*)src.Texture.Handle;
135+
dst.sampler = (SDL_GPUSampler*)src.Sampler.Handle;
136+
}
137+
138+
SDL_BindGPUComputeSamplers(
139+
Handle,
140+
(uint)startIndex,
141+
bindings,
142+
(uint)samplers.Length);
143+
}
144+
145+
/// <summary>
146+
/// Ends a render pass.
59147
/// </summary>
60148
/// <exception cref="InvalidOperationException">The associated command buffer was submitted.</exception>
61149
public void End()
@@ -68,5 +156,6 @@ public void End()
68156
protected override void Reset()
69157
{
70158
Device.EndComputePassTryInternal(this);
159+
_isPipelineBound = false;
71160
}
72161
}

src/cs/production/SDL/GPU/GpuDevice.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ internal GpuDevice(ILogger<GpuDevice> logger, GpuDeviceOptions? options)
152152
PoolRenderPass = new Pool<GpuRenderPass>(
153153
_logger, () => new GpuRenderPass(this), "GpuRenderPasses");
154154
PoolComputePass = new Pool<GpuComputePass>(
155-
_logger, () => new GpuComputePass(this), "GpuComputePass");
155+
_logger, () => new GpuComputePass(this), "GpuComputePasses");
156156

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

@@ -442,6 +442,7 @@ public bool TryCreateGraphicsPipeline(
442442
/// <summary>
443443
/// Attempts to create a new <see cref="GpuDataBuffer" /> instance.
444444
/// </summary>
445+
/// <param name="usage">How the buffer is intended to be used by the client.</param>
445446
/// <param name="elementCount">
446447
/// The maximum number of <typeparamref name="TElement" /> elements the buffer can hold.
447448
/// </param>
@@ -452,13 +453,14 @@ public bool TryCreateGraphicsPipeline(
452453
/// <typeparam name="TElement">The type of data buffer element.</typeparam>
453454
/// <returns><c>true</c> if the data buffer was successfully created; otherwise, <c>false</c>.</returns>
454455
public bool TryCreateDataBuffer<TElement>(
456+
GpuBufferUsageFlags usage,
455457
int elementCount,
456458
[NotNullWhen(true)] out GpuDataBuffer? buffer,
457459
string? name = null)
458460
where TElement : unmanaged
459461
{
460462
var bufferCreateInfo = default(SDL_GPUBufferCreateInfo);
461-
bufferCreateInfo.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
463+
bufferCreateInfo.usage = (uint)usage;
462464
bufferCreateInfo.size = (uint)(sizeof(TElement) * elementCount);
463465
var handle = SDL_CreateGPUBuffer(HandleTyped, &bufferCreateInfo);
464466
if (handle == null)

0 commit comments

Comments
 (0)