GSoC 2025: GPU-accelerated raster ops #2658
Replies: 15 comments
-
PreparationAnything before week 1
|
Beta Was this translation helpful? Give feedback.
-
Week 1: being sickAm sick this week, likely caught something last week in the (amazing) rustweek conference. Still made some progress:
|
Beta Was this translation helpful? Give feedback.
-
Week 2: cargo-gpu naga transpile vertical sliceDemoA vertical slice of using cargo gpu to compile rust-gpu shaders and transpile them to wgsl with naga, so they can be passed to wgpu. Based on a GameJam game of mine with just 4 shaders, small enough to allow quick iteration and API exploration. Progress
|
Beta Was this translation helpful? Give feedback.
-
Week 3: invert_gpuDemo
Progress
|
Beta Was this translation helpful? Give feedback.
-
Week 4:
|
Beta Was this translation helpful? Give feedback.
-
Week 5: refactoring
|
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Week 8: rust-gpu edition 2024Progress
|
Beta Was this translation helpful? Give feedback.
-
Week 9: clippy and miri with cargo-gpuProgress
|
Beta Was this translation helpful? Give feedback.
-
Week 10: prep graster-nodes for shadersProgress
|
Beta Was this translation helpful? Give feedback.
-
Week 11: first wgsl compilationDemo
Progress
|
Beta Was this translation helpful? Give feedback.
-
2 week breakNot advancing the week counter, just taking 2 weeks off |
Beta Was this translation helpful? Give feedback.
-
Week 12Demo: invert nodeThe invert node works again. The rest has non-functioning nodes, as the runtime does not yet support non-texture arguments. (The "Extract Executor" is copied from within the "Upload Texture" node) ![]() Demo: posterize nodeWe can now pass parameters to shaders, allowing nodes like posterize to work! Though any node using floating point numbers is still broken, since I had to switch them from f64 to f32* and neither graphite's UI or node graph seems to support that properly. ![]() *f64 on GPUs: Not only is it typically 64 times slower on consumer level GPUs, but WebGPU spec doesn't expose f64 either. The wgpu impl exposes a Progress
|
Beta Was this translation helpful? Give feedback.
-
Week 13DemoProgress
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Problem
Graphite used not have any hardware acceleration and compose the final image entirely on the CPU,
but in the past year, the vello renderer was implemented to accelerate the image composition on the
GPU. However, vello is only well suited for composing vector elements and plain raster images. It
does not implement any operations for (post-)processing raster images, like adjusting hue,
saturation, blending images, a posterization effect, etc. Currently, these “raster ops” are still
evaluated on the CPU, given that Graphite runs within the Web Assembly (wasm) environment of
the browser, limited to a single thread processing one pixel at a time. This significantly slows editing
with any raster-based content being present, in practice 2-3 fps on a typical desktop system.
Benefit
I want to accelerate the processing of rasterization ops using the GPU to achieve acceptable levels of
performance.
Instead of porting the existing raster ops written in Rust to a shader language, I want to use the
rust-gpu shader compiler to compile the existing Rust-based raster ops into shaders. It allows
Graphite to continue using the existing CPU path if
the new WebGPU APIWebGL is not available, withoutduplicating the code across several languages.
Final Report
The technical bits
We want to use the rust-gpu shader compiler to not have duplicate code between shader and cpu nodes. A typical shader compiler like glslc (or shaderc) takes input files written in the C-like glsl language and turns them into SPIRV, a binary intermediary representations (IR) for shaders. Think of it like Java or C# bytecode, that the graphics driver accepts and compiles down to the machine code needed for your graphics card. rust-gpu works quite similar, just that it's input language is ordinary rust and not some C-like custom language.
Unlike a typical shader compiler, rust-gpu is not a full compiler but merely a "codegen backend" for the rustc compiler. This allows rust-gpu to reuse all the tokenization, parsing, type system, borrow checking etc. of the standard rustc compiler, and thus parse all the contructs of the rust programming language. At the very end of the rustc compiler pipeline, the Middle IR (MIR) is passed into a codegen backend to generate machine code. This codegen backend is typically LLVM, but is replaceable by a dynamically loaded library, like the
rustc_codegen_spirv
crate of rust-gpu.Codegen backends are build is an unstable internal interface that can change on a whim. This has a few important implications:
rust-gpu edition 2024
With the release of rustc 1.85.0 the rust edition 2024 was stablilized, but also came with significant changes to the codegen backend interface. This proved to be a significant challenge, so while edition 2024 released in February, it took us until July to port to newer toolchain versions. This was a huge blocker for graphite, as they and the entire rust ecosystem had already ported over to edition 2024, which the older compiler could not support. We would have to wait for rust-gpu to support edition 2024, as backporting all of the crates to edition 2021 was not an option.
The porting has primarly been done by @eddyb in Rust-GPU/rust-gpu#249 while I was mostly testing the branch against various projects and debugging issues as they poped up. Notably, this update also required changing the "target specs", a set of json specification files the rustc compiler requires of every codegen backend.
cargo-gpu
When I first looked at cargo-gpu, it was a command line application to compile a shader crate to SPIRV, the shader IR. Notably, it did not care for what toolchain you used in your project and automated the entire process of setting up rust-gpu: It would download rust-gpu and the required toolchain, compile rust-gpu with that toolchain and cache the build, and finally compile the shaders using the selected rust-gpu and toolchain version.
However, it had a few issues that needed to be resolved before we could use it:
rustc_backend_spirv
dylibs Rust-GPU/cargo-gpu#69While integrating these needed changes, I effectively refactored the entire codebase in 12 PRs and 3.005++ 4.610-- lines.
naga-wgsl transpiling
SPIRV is an open standard for shader IR for exchanging shaders between various compilers and graphics APIs. It was originally invented for the OpenCL compute API, but has evolved into the shader IR of the Vulkan API, which is the main graphics API on both Linux and Android systems. Even Windows is replacing their DXIL with SPIRV, making it the primary cross-platform shader IR to use. It was even set to be the main shader IR for the WebGPU API, but some parties were strictly against using that open IR standard. And so we got wgsl, a new C-like shading language with a rust-like syntax that's not anything like rust itself. And as is typical of web, it is sent to the browser as source code, so we may see some platform differences between Firefox, Chrome and Safari.
But since rust-gpu emits SPIRV, how to we convert from SPIRV to wgsl? Firefox implements the WebGPU API with their
wgpu
crate, which has a shader transpiler callednaga
. It was primarily build to convert wgsl into the different output shader languages needed for Firefox to run on all platforms: SPIRV for Vulkan on Linux and Windows, MSL on Mac and HLSL for Windows. But it also supports SPIRV input and WGSL output, so we can chain the compilers to go from rust to SPIRV to wgsl. This isn't anything new, schell's renderling has been doing this quite successfully and has contributed many fixes to the SPIRV input module.But I didn't just want to setup wgsl transpiling for graphite, it would be much nicer if rust-gpu could handle that internally. This would also allow us to specialize the SPIRV we're emitting for naga, if necessary. So I got to implementing a
spirv-unknown-naga-wgsl
target, both of which need a bit more work before they can be merged:enum SpirvTargetEnv
containing all available targets Rust-GPU/rust-gpu#311spirv-unknown-naga-wgsl
target via naga Rust-GPU/rust-gpu#280To test this entire stack without having to integrate this directly into graphite, I choose to reuse a GameJam Game of mine called Colorbubble. It uses wgpu, hand-written wgsl and can be deployed to the web, just like graphite should. And with only a single hand-written wgsl file of 75 lines, it is the perfect small project to test the entire tech stack of cargo-gpu and naga-wgsl transpiling. The result can be found on the branch
cargo-gpu
, which can be compiled on stable and deployed to the web. I also build a vertical slice in week 3 to compile graphite's invert node into a shader, as it's the simplest node without any parameters.graphite integration
The proper integration into graphite was more difficult than initially expected due to rust-gpu's
#[no-std]
requirement. From rustc's perspective, rust-gpu is essentially an embedded target that does not have access to the standard library or a global memory allocator, thus it requires every crate to beno-std
(and not need thealloc
crate). This makes symbols likeVec
,Box
,Arc
unavailable, so all code using them must be masked out by a cargo feature. I was told that graphite had anstd
feature that when turned off, allowed it to compile a core set of symbols asno-std
from a previous but quite different rust-gpu integration attempt. But alas, that support has withered away so badly, I decided to pursue a different approach tono-std
compatibility: Instead of masking out all the std symbols, which is usually quite error prone, we instead have a separateshader
crate that is (almost) completely no-std, which is reexported in the std crate. This allows me to move certain no-std symbols from the std crate over without breaking the paths of upstream crates.runtime
Constraints
Deliverables
nightly-2025-05-09
(~1.88) and Rust 2024 edition. Rust-GPU/rust-gpu#249rustc_backend_spirv
dylibs Rust-GPU/cargo-gpu#69Beta Was this translation helpful? Give feedback.
All reactions