|
| 1 | +--- |
| 2 | +title: "Shadertoys ported to Rust GPU" |
| 3 | +authors: [LegNeato] |
| 4 | +tags: ["demo"] |
| 5 | +--- |
| 6 | + |
| 7 | +# Porting Shadertoys to Rust with Rust GPU |
| 8 | + |
| 9 | +We ported a few popular [Shadertoy](https://www.shadertoy.com/) shaders over to Rust |
| 10 | +using [Rust GPU](https://github.com/Rust-GPU/rust-gpu/). The process was straightforward |
| 11 | +and we want to share some highlights. |
| 12 | + |
| 13 | +<!-- truncate --> |
| 14 | + |
| 15 | +The code is available on [GitHub](https://github.com/Rust-GPU/rust-gpu-shadertoys). |
| 16 | + |
| 17 | + |
| 18 | + |
| 19 | +## What is Rust GPU? |
| 20 | + |
| 21 | +[Rust GPU](https://rust-gpu.github.io/) is a project that allows you to write code for |
| 22 | +GPUs using the Rust programming language. GPUs are typically programmed using |
| 23 | +specialized languages like [WGSL](https://www.w3.org/TR/WGSL/), |
| 24 | +[GLSL](https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders), |
| 25 | +[MSL](https://developer.apple.com/documentation/metal/performing_calculations_on_a_gpu), |
| 26 | +or |
| 27 | +[HLSL](https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl). |
| 28 | +Rust GPU changes this by letting you use Rust to write GPU programs (often called |
| 29 | +"shaders" or "kernels"). |
| 30 | + |
| 31 | +These Rust GPU programs are then compiled into [SPIR-V](https://www.khronos.org/spir/), |
| 32 | +a low-level format that [most GPUs understand](https://vulkan.gpuinfo.org/). Since |
| 33 | +SPIR-V is the format [Vulkan](https://www.vulkan.org/) uses, Rust GPU makes it possible |
| 34 | +to integrate Rust-based GPU programs into any Vulkan-compatible workflow. |
| 35 | + |
| 36 | +For more details, check out the [Rust GPU website](http://rust-gpu.github.io/) or the |
| 37 | +[GitHub repository](https://github.com/rust-gpu/rust-gpu). |
| 38 | + |
| 39 | +## Shared code between CPU and GPU |
| 40 | + |
| 41 | +Sharing data between the CPU and GPU is common in shader programming. This often |
| 42 | +requires special tooling or manual effort. Using Rust on both sides made this seamless: |
| 43 | + |
| 44 | +```rust |
| 45 | +#[repr(C)] |
| 46 | +#[derive(Copy, Clone, Pod, Zeroable)] |
| 47 | +pub struct ShaderConstants { |
| 48 | + pub width: u32, |
| 49 | + pub height: u32, |
| 50 | + pub time: f32, |
| 51 | + pub cursor_x: f32, |
| 52 | + pub cursor_y: f32, |
| 53 | + pub drag_start_x: f32, |
| 54 | + pub drag_start_y: f32, |
| 55 | + pub drag_end_x: f32, |
| 56 | + pub drag_end_y: f32, |
| 57 | + pub mouse_left_pressed: u32, |
| 58 | + pub mouse_left_clicked: u32, |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +Note that on both the CPU and the GPU we are using the |
| 63 | +[`bytemuck`](https://github.com/Lokathor/bytemuck) crate for the `Pod` and `Zeroable` |
| 64 | +derives. This crate is unmodified and integrated directly from |
| 65 | +[crates.io](https://crates.io/crates/bytemuck). Many `no_std` + no `alloc` Rust crates |
| 66 | +work on the GPU! |
| 67 | + |
| 68 | +## Traits, generics, and macros |
| 69 | + |
| 70 | +Rust GPU supports traits. We used traits to encapsulate shader-specific operations in |
| 71 | +reusable ergonomic abstractions: |
| 72 | + |
| 73 | +```rust |
| 74 | +pub trait FloatExt { |
| 75 | + fn gl_fract(self) -> Self; |
| 76 | + fn rem_euclid(self, rhs: Self) -> Self; |
| 77 | + fn gl_sign(self) -> Self; |
| 78 | + fn deg_to_radians(self) -> Self; |
| 79 | + fn step(self, x: Self) -> Self; |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +While there are still some rough edges, generics mostly work as expected. We used them |
| 84 | +to support multiple channel types without duplicating logic: |
| 85 | + |
| 86 | +```rust |
| 87 | +pub struct State<C0, C1> { |
| 88 | + inputs: Inputs<C0, C1>, |
| 89 | + cam_point_at: Vec3, |
| 90 | + cam_origin: Vec3, |
| 91 | + time: f32, |
| 92 | + ldir: Vec3, |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +Rust macros also function normally. Using macros allowed us to reduce repetitive code |
| 97 | +further. |
| 98 | + |
| 99 | +```rust |
| 100 | +macro_rules! deriv_impl { |
| 101 | + ($ty:ty) => { |
| 102 | + impl Derivative for $ty { |
| 103 | + deriv_fn!(ddx, OpDPdx, false); |
| 104 | + deriv_fn!(ddx_fine, OpDPdxFine, true); |
| 105 | + deriv_fn!(ddx_coarse, OpDPdxCoarse, true); |
| 106 | + deriv_fn!(ddy, OpDPdy, false); |
| 107 | + deriv_fn!(ddy_fine, OpDPdyFine, true); |
| 108 | + deriv_fn!(ddy_coarse, OpDPdyCoarse, true); |
| 109 | + deriv_fn!(fwidth, OpFwidth, false); |
| 110 | + deriv_fn!(fwidth_fine, OpFwidthFine, true); |
| 111 | + deriv_fn!(fwidth_coarse, OpFwidthCoarse, true); |
| 112 | + } |
| 113 | + }; |
| 114 | +} |
| 115 | + |
| 116 | +// Applied easily to multiple types: |
| 117 | +deriv_impl!(f32); |
| 118 | +deriv_impl!(Vec2); |
| 119 | +deriv_impl!(Vec3A); |
| 120 | +deriv_impl!(Vec4); |
| 121 | +``` |
| 122 | + |
| 123 | +## Standard Rust tools |
| 124 | + |
| 125 | +Want to typecheck the shaders? `cargo check`. Build them? `cargo build`. Run in release |
| 126 | +mode? `cargo run --release`. Gate code at compile time? Use |
| 127 | +[features](https://doc.rust-lang.org/cargo/reference/features.html). |
| 128 | + |
| 129 | +If you run `clippy` on the shaders, you'll see it complains about many things as we |
| 130 | +intentionally kept the Rust versions of shaders similar to their original GLSL versions. |
| 131 | + |
| 132 | +This is one of Rust GPU's big advantages: you can use all the Rust tools you're already |
| 133 | +familiar with. |
| 134 | + |
| 135 | +## Improving the Rust ecosystem |
| 136 | + |
| 137 | +While porting shaders, we also contributed back to the ecosystem by identifying and |
| 138 | +fixing several issues in [`wgpu` and `naga`](https://github.com/gfx-rs/wgpu): |
| 139 | + |
| 140 | +- [Fixed a panic while processing SPIR-V](https://github.com/gfx-rs/wgpu/pull/7397) |
| 141 | +- [Fixed incorrect translation of certain shader literals when targeting |
| 142 | + Metal](https://github.com/gfx-rs/wgpu/pull/7437) |
| 143 | +- [Fixed a regression making it impossible to include raw |
| 144 | + SPIR-V](https://github.com/gfx-rs/wgpu/pull/7503) |
| 145 | + |
| 146 | +These fixes help everyone using `wgpu` and `naga`, not just users of Rust GPU. |
| 147 | + |
| 148 | +## Come join us! |
| 149 | + |
| 150 | +While we hit some sharp edges, porting Shadertoy shaders to Rust with Rust GPU was |
| 151 | +reasonably straightforward. Rust GPU is definitely ready for shader experimentation. |
| 152 | + |
| 153 | +We're eager to add more users and contributors! We will be working on revamping the |
| 154 | +onboarding and documentation soon. To follow along or get involved, check out the |
| 155 | +[`rust-gpu` repo on GitHub](https://github.com/rust-gpu/rust-gpu). |
0 commit comments