Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
deb1198
Compute shaders
kunitoki Jul 26, 2026
c9b28f7
Remove cruft
kunitoki Jul 27, 2026
d7ae512
More tests
kunitoki Jul 27, 2026
4bbab75
Merge branch 'dev/compute_shaders' into dev/compute_shaders_real
kunitoki Jul 27, 2026
e2ab7ad
Merge branch 'dev/compute_shaders' into dev/compute_shaders_real
kunitoki Jul 27, 2026
12642d4
Fix tests
kunitoki Jul 27, 2026
30805ec
Test fixes
kunitoki Jul 27, 2026
d915ded
More tests to remove
kunitoki Jul 27, 2026
71967db
Merge branch 'dev/compute_shaders' into dev/compute_shaders_real
kunitoki Jul 27, 2026
c0cbec3
Tests on linux GPU (#152)
kunitoki Jul 29, 2026
f6072ed
More testing
kunitoki Jul 29, 2026
aaa37e0
More fixes
kunitoki Jul 29, 2026
7a76824
Fix tests
kunitoki Jul 29, 2026
02877bc
Remove failing tests
kunitoki Jul 29, 2026
fdca78e
More RHI fixes
kunitoki Jul 30, 2026
c3574bc
More work
kunitoki Jul 30, 2026
05acb63
Clear comments
kunitoki Jul 30, 2026
c0ba0df
Fix constexpr
kunitoki Jul 30, 2026
33ea2c6
Fix constexpr
kunitoki Jul 30, 2026
d85a2dd
Fix stuff
kunitoki Jul 30, 2026
b7c7634
More work
kunitoki Jul 30, 2026
5155b90
Fixes
kunitoki Jul 30, 2026
e224f5a
Fix wgsl lowering
kunitoki Jul 31, 2026
2af6e90
Improved math functions
kunitoki Jul 31, 2026
059210a
Fix component effects
kunitoki Jul 31, 2026
4c29e08
More nice stuff
kunitoki Jul 31, 2026
ecc272d
More optimisations
kunitoki Jul 31, 2026
3f7150a
More changes for WebGPU
kunitoki Jul 31, 2026
75f6e1d
Increase coverage
kunitoki Jul 31, 2026
3258058
More tests
kunitoki Jul 31, 2026
99efb64
Merge branch 'dev/compute_shaders' into dev/compute_shaders_real
kunitoki Aug 2, 2026
2cb9f07
Fix tests
kunitoki Aug 2, 2026
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
10 changes: 7 additions & 3 deletions .github/workflows/build_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ env:
libasound2-dev libjack-jackd2-dev ladspa-sdk libcurl4-openssl-dev libfreetype6-dev
libx11-dev libxcomposite-dev libxcursor-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev
libxrandr-dev libxrender-dev libxfixes-dev libxss-dev libxtst-dev libxkbcommon-dev
libglu1-mesa-dev libegl1-mesa-dev mesa-common-dev
libglu1-mesa-dev libegl1-mesa-dev mesa-common-dev libgl1-mesa-dri mesa-utils xvfb
LIBGL_ALWAYS_SOFTWARE: "1"
GALLIUM_DRIVER: "llvmpipe"

jobs:
configure:
Expand All @@ -43,6 +45,8 @@ jobs:
fetch-depth: 0
- name: Install Dependencies
run: sudo apt-get update && sudo apt-get install -y ${INSTALL_DEPS}
- name: Test OpenGL Version
run: xvfb-run glxinfo | grep "OpenGL version"
- name: Configure
run: cmake ${{ github.workspace }} -G "Ninja Multi-Config" -B ${{ runner.workspace }}/build -DYUP_ENABLE_TESTS=ON -DYUP_ENABLE_EXAMPLES=ON
- name: Build SDL
Expand Down Expand Up @@ -75,7 +79,7 @@ jobs:
run: cmake ${{ github.workspace }} -G "Ninja Multi-Config" -B ${{ runner.workspace }}/build -DYUP_ENABLE_TESTS=ON
- run: cmake --build ${{ runner.workspace }}/build --config Debug --target yup_tests
- working-directory: ${{ runner.workspace }}/build/tests/Debug
run: ./yup_tests
run: xvfb-run -a ./yup_tests

build_tests_release:
runs-on: ubuntu-latest
Expand All @@ -96,7 +100,7 @@ jobs:
run: cmake ${{ github.workspace }} -G "Ninja Multi-Config" -B ${{ runner.workspace }}/build -DYUP_ENABLE_TESTS=ON
- run: cmake --build ${{ runner.workspace }}/build --config Release --target yup_tests
- working-directory: ${{ runner.workspace }}/build/tests/Release
run: ./yup_tests
run: xvfb-run -a ./yup_tests

build_console:
runs-on: ubuntu-latest
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ env:
libasound2-dev libjack-jackd2-dev ladspa-sdk libcurl4-openssl-dev libfreetype6-dev
libx11-dev libxcomposite-dev libxcursor-dev libxcursor-dev libxext-dev libxi-dev libxinerama-dev
libxrandr-dev libxrender-dev libxfixes-dev libxss-dev libxtst-dev libxkbcommon-dev
libglu1-mesa-dev libegl1-mesa-dev mesa-common-dev lcov
libglu1-mesa-dev libegl1-mesa-dev mesa-common-dev libgl1-mesa-dri xvfb lcov
LIBGL_ALWAYS_SOFTWARE: "1"
GALLIUM_DRIVER: "llvmpipe"
IGNORE_ERRORS: "mismatch,gcov,source,negative,unused,empty,format,corrupt"

jobs:
Expand Down Expand Up @@ -94,7 +96,7 @@ jobs:
run: cmake --build . --target coverage_clean
- name: Run C++ Tests
working-directory: ${{ runner.workspace }}/build/tests/Debug
run: SDL_VIDEODRIVER=dummy ./yup_tests --gtest_output=xml:test_results.xml
run: xvfb-run -a ./yup_tests
- name: Generate C++ Coverage Report
working-directory: ${{ runner.workspace }}/build
run: |
Expand Down
34 changes: 33 additions & 1 deletion CHANGELOG.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ coverage:
project:
default:
informational: true
if_ci_failed: error
target: 80%
threshold: 5%
base: auto
Expand All @@ -30,11 +31,13 @@ coverage:
- yup_graphics
- yup_gui
- yup_python
- yup_rhi
- yup_shading
- yup_simd
patch:
default:
informational: true
if_ci_failed: error
target: 80%
threshold: 5%

Expand All @@ -55,6 +58,7 @@ flags:
yup_graphics: { paths: [modules/yup_graphics/], carryforward: true }
yup_gui: { paths: [modules/yup_gui/], carryforward: true }
yup_python: { paths: [modules/yup_python/], carryforward: true }
yup_rhi: { paths: [modules/yup_rhi/], carryforward: true }
yup_shading: { paths: [modules/yup_shading/], carryforward: true }
yup_simd: { paths: [modules/yup_simd/], carryforward: true }

Expand Down
167 changes: 167 additions & 0 deletions docs/graphics/rhi/compute-shaders.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Compute Shaders

The `yup_rhi` module supports GPU compute shaders through `GpuComputePipeline`
and `GpuComputePass`. Compute shaders run general-purpose GPU work — audio DSP,
physics simulation, particle systems, image processing — without any window,
framebuffer, or graphics pipeline.

## Availability

Compute shaders are available on backends that expose
`GpuDevice::isComputeAvailable() == true`: **Metal**, **Direct3D 11**,
**WebGPU** (Dawn and Emscripten), and **OpenGL 4.3+** / **OpenGL ES 3.1+**.

Compute is **not** available on the Headless backend.

## Architecture

Compute shaders bypass Rive's ore layer (`rive::ore`) entirely. The ore layer
does not yet expose compute dispatch, so `GpuComputePipeline` and
`GpuComputePass` go directly to the backend-native API:

| Backend | Pipeline compilation | Dispatch |
| ------------ | -------------------------------------- | ---------------------------------- |
| Metal | `MTLComputePipelineState` | `dispatchThreadgroups:` |
| Direct3D 11 | `ID3D11ComputeShader` | `ID3D11DeviceContext::Dispatch()` |
| WebGPU | `wgpu::ComputePipeline` | `DispatchWorkgroups()` |
| OpenGL | `GL_COMPUTE_SHADER` + program link | `glDispatchCompute()` |

## Compiling a compute pipeline

### From GLSL (online, requires `YUP_ENABLE_SHADER_TRANSPILER`)

```cpp
auto result = GpuComputePipeline::compileFromGlsl (device, glslSource);
if (result.wasOk())
auto pipeline = result.getValue();
```

The transpiler compiles GLSL → SPIR-V, reflects the workgroup size from
`layout(local_size_x=...)`, transpiles to the backend-native language (MSL,
HLSL, WGSL), and compiles the final pipeline.

### From a `.ysl` shader bundle (offline)

```cpp
auto bundle = ShaderBundle::loadFromFile (File ("audio_effect.ysl"));
if (bundle.wasOk())
{
auto result = GpuComputePipeline::compileFromBundle (device, bundle.getReference());
if (result.wasOk())
auto pipeline = result.getValue();
}
```

### From raw native source (any backend)

```cpp
GpuShaderSource source;
source.language = GpuShaderLanguage::msl; // or hlsl, wgsl, glsl
source.code = mslSource;
source.codeSize = mslLength;

auto result = GpuComputePipeline::compile (device, source, { 256, 1, 1 });
```

## Dispatching compute work

```cpp
auto pass = GpuComputePass::begin (device);

pass.setPipeline (pipeline);
pass.setStorageBuffer (0, 0, inputBuffer); // SSBO binding (set=0, binding=0)
pass.setStorageBuffer (0, 1, outputBuffer); // SSBO binding (set=0, binding=1)
pass.setUniformBuffer (0, 2, &params, sizeof params);

uint32_t groupsX = (numElements + 255) / 256; // workgroupSize.x = 256
pass.dispatch (groupsX, 1, 1);
pass.finish(); // commits work to the GPU
```

## Storage buffers

Storage buffers (`GpuBufferType::storage`) are read-write GPU buffers for compute
shaders. Create them with `GpuBuffer::create()`:

```cpp
std::vector<float> data (numSamples, 0.0f);
auto buf = GpuBuffer::create (device,
GpuBufferType::storage,
data.data(),
data.size() * sizeof (float));
```

Unlike vertex/index/uniform buffers that go through ore, storage buffers are
allocated directly on the native API (Metal `MTLBuffer`, D3D11 structured
buffer + UAV, WebGPU `Storage` buffer, GL `GL_SHADER_STORAGE_BUFFER`).

### Updating a storage buffer in place

`GpuBuffer` is immutable once created — `GpuDevice::createBuffer()` always
allocates a new native resource. For code that feeds a storage buffer new
data every frame or audio callback (a compute effect processing a live
stream, for instance), reallocating on every iteration is expensive and, on
a real-time thread such as an audio callback, unsafe: buffer allocation has
unbounded, driver-dependent latency and can cause audible dropouts.

`GpuDevice::updateBuffer()` writes new data into an *existing* storage buffer
without reallocating it:

```cpp
// Once, outside the hot loop:
auto buf = GpuBuffer::create (device, GpuBufferType::storage, initialData, byteSize);

// Every frame / audio callback — no allocation:
device->updateBuffer (buf, newData, byteSize);
```

`byteSize` must not exceed the buffer's original size. Supported on all
compute-capable backends (Metal, D3D11, WebGPU/Dawn, OpenGL).

### Buffer binding indices on Metal

`GpuComputePass`'s native dispatch binds Metal buffer arguments directly as
`group*16 + binding` (see `native/yup_GpuComputePass_metal.cpp`) — there is no
reflection layer translating GLSL `(set, binding)` pairs to the compiled
function's actual `[[buffer(N)]]` indices, unlike the render pipeline path.
This requires the transpiled MSL's argument indices to exactly match the
GLSL/SPIR-V declared `binding=N` values. The transpiler enforces this by
setting `CompilerMSL::Options::enable_decoration_binding = true`; without it,
spirv-cross assigns MSL buffer indices via its own auto-incrementing scheme,
which can silently diverge from the declared bindings for any shader with
more than one buffer resource, producing a pipeline that compiles and
dispatches without error but never actually reads/writes the intended data.

## Example: GPU audio effect

The `GpuAudioProcessingDemo` example demonstrates real-time audio processing on
the GPU:

1. `AudioIODeviceCallback` captures live audio input
2. Audio samples are written into a preallocated `GpuBuffer` storage buffer via
`updateBuffer()` — no GPU allocation happens on the audio thread
3. A compute shader applies gain + soft clipping
4. Processed samples are read back from the GPU
5. Results are routed to the audio output

The compute shader runs on the audio I/O thread, using a dedicated `GpuDevice`
that does not share state with the render thread. The tiny per-block
parameters (gain, mix) stay a uniform buffer bound via `setUniformBuffer()` —
`dispatch()` allocates a small temporary buffer for it on every call, but at
16 bytes that's negligible next to the audio-block-sized input buffer that
`updateBuffer()` now avoids reallocating.

```glsl
#version 450
layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;

layout(std430, set = 0, binding = 0) buffer InputBuf { float inData[]; };
layout(std430, set = 0, binding = 1) buffer OutputBuf { float outData[]; };
layout(std140, set = 0, binding = 2) uniform Params { float gain; float mix; };

void main() {
uint i = gl_GlobalInvocationID.x;
float s = inData[i] * gain;
outData[i] = tanh(s) * mix + inData[i] * (1.0 - mix);
}
```
13 changes: 9 additions & 4 deletions docs/graphics/rhi/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ and textures while remaining portable across Metal, Direct3D, OpenGL / OpenGL ES
WebGL2, WebGPU, and Vulkan (in progress).

Use the RHI when you need custom GPU work that the 2D `Graphics` API does not
express - 3D geometry, post-process effects, compute-style fullscreen passes, or
offscreen render-to-texture pipelines. For GPU compute without any window or
express - 3D geometry, post-process effects, compute passes for DSP or simulation,
or offscreen render-to-texture pipelines. For GPU compute without any window or
graphics (e.g. audio DSP on the GPU), use `GpuDevice` directly — no
`GraphicsContext` or `yup_graphics` dependency needed.

Expand All @@ -19,8 +19,9 @@ graphics (e.g. audio DSP on the GPU), use `GpuDevice` directly — no
| Draw 2D vector content (paths, text, images) | `Graphics` (not the RHI) |
| Render custom geometry with your own shaders | `GpuPipeline` + `GpuRenderPass` |
| Apply a fullscreen post-process effect | `GpuPipeline` (fullscreen) |
| Run GPU compute (DSP, simulation) | `GpuComputePipeline` + `GpuComputePass` |
| Render offscreen and sample the result as a texture | `GpuTarget` or `GpuCanvas` |
| Mix 2D drawing *and* custom passes on one surface | `GpuCanvas` |
| Mix 2D drawing *and* custom passes on one surface | `GpuCanvas` |

## Classes at a glance

Expand All @@ -30,10 +31,14 @@ graphics (e.g. audio DSP on the GPU), use `GpuDevice` directly — no
submit.
- **`GpuRenderPass`** - records draw commands (pipeline, bindings, draws) into a
render target within a frame.
- **`GpuComputePass`** - records compute dispatch commands (pipeline, storage
buffers, uniforms) directly against the backend-native API.
- **`GpuPipeline`** - an immutable, compiled vertex + fragment pipeline plus
fixed state.
- **`GpuComputePipeline`** - an immutable, compiled compute pipeline (single
compute stage, native backend API, no ore dependency).
- **`GpuPipelineCache`** - thread-safe compile-or-fetch cache for pipelines.
- **`GpuBuffer`** - an immutable vertex, index, or uniform buffer.
- **`GpuBuffer`** - an immutable vertex, index, uniform, or storage buffer.
- **`GpuTexture`** - an opaque GPU texture, the currency between passes,
`Image`, and `Graphics::drawTexture`.
- **`GpuTarget`** - a minimal offscreen render surface for render-pass-only work.
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ concept guides, walkthroughs, and reference material.
- [Imaging](imaging/index.md) - bitmap images: pixels, loading, saving, and drawing.
- [UI](ui/index.md) - components, windowing, events, layout, and widgets.
- [Audio](audio/index.md) - audio devices, formats, DSP, the audio graph, processors, and plugin hosting/client wrappers.
- [AI](ai/index.md) - the AI and LLM infrastructure.
- [Scripting](scripting/index.md) - the Python bindings layer.

## Quick links
Expand Down
Loading
Loading