Skip to content

Create storage buffers that compute shaders can read or write - #669

Merged
stanoddly merged 3 commits into
mainfrom
storage-buffer-usage
Oct 26, 2026
Merged

stanoddly merged 3 commits into
mainfrom
storage-buffer-usage

Conversation

@botoddly

@botoddly botoddly commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #652.

A compute shader could not read or write a storage buffer created through Pixely. CreateStorageBuffer<T>(ReadOnlySpan<T>) was the only way to create one, and it hardcoded SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ.

API

  • StorageBufferUsage flags enum: GraphicsStorageRead, ComputeStorageRead and ComputeStorageWrite, with SDL's flag values.
  • GpuStorageBuffer.Usage.
  • GpuMemorySystem.CreateStorageBuffer<T>(int length, StorageBufferUsage usage), also on ICopyPass. It creates the buffer without an upload, so its contents are undefined until a compute shader writes it or UpdateStorageBuffer fills it. It throws for an empty usage or one with unknown bits.
  • CreateStorageBuffer<T>(ReadOnlySpan<T>) keeps the usage GraphicsStorageRead.

Behavior

Both create methods throw ArgumentOutOfRangeException for a buffer under 4 bytes, empty data included, or over int.MaxValue bytes.

  • SDL asserts on a buffer under 4 bytes in debug mode.
  • The upper limit keeps SizeInBytes, computed as an int, from overflowing.

The GPU allocation is rounded up to a multiple of 4 bytes. D3D12 views a storage buffer as size / 4 raw 32-bit elements, so it would not reach the last 1 to 3 bytes of another size. SizeInBytes keeps the size asked for.

Binding a storage buffer whose usage lacks what the binding needs throws InvalidOperationException. The message names the binding, the slot, the buffer's usage and the needed usage.

  • RenderPass.BindVertexStorageBuffers and BindFragmentStorageBuffers need GraphicsStorageRead.
  • ComputePass.BindReadOnlyStorageBuffers needs ComputeStorageRead.
  • A StorageBufferReadWriteBinding passed to CommandBuffer.CreateComputePass needs ComputeStorageWrite.

SDL does not check buffer usage on bind, not even in debug mode (SDL_BeginGPUComputePass has // TODO: validate buffer usage?).

Breaking change

  • A buffer created from data and bound to a compute pass's read-only slots now throws instead of reaching SDL.
  • CreateStorageBuffer<T>(ReadOnlySpan<T>) now throws ArgumentOutOfRangeException for data under 4 bytes or over int.MaxValue bytes. Empty data threw a plain ArgumentException before; the new exception derives from it.

No game in stanoddly/Games binds storage buffers to a compute pass.

Not included

ComputePass.DispatchIndirect takes a GpuStorageBuffer, but SDL needs SDL_GPU_BUFFERUSAGE_INDIRECT for it, which no Pixely buffer can have.

Testing

  • Pixely.Tests: 617 passed, including new StorageBufferUsageTests for the size, length and usage checks, the rounding of the allocation, and the bind usage check.
  • Pixely.Models.Tests: 225 passed.
  • The solution builds without warnings, and src/Pixely builds for net11.0-browser.
  • Not tried on a GPU: Pixely has no buffer readback, so no test checks that a compute shader writes a buffer that a fragment shader then reads.

@stanoddly
stanoddly force-pushed the storage-buffer-usage branch from 7ed7280 to 4158681 Compare October 26, 2026 21:14
@stanoddly
stanoddly merged commit 6387370 into main Oct 26, 2026
3 checks passed
@stanoddly
stanoddly deleted the storage-buffer-usage branch October 26, 2026 21:18
@github-actions

Copy link
Copy Markdown

📦 Development package Pixely 0.0.146 is available.

Workflow run (attempt 1)

@github-actions

Copy link
Copy Markdown

✅ Development package validation passed on all platforms.

Workflow run (attempt 1)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create storage buffers that compute shaders can read or write

2 participants