I'm trying write a WGSL shader that reads an octree that is stored in a storage buffer. The problem is, the compiler doesn't like the dynamic index I'm calculating to access leaves in the storage buffer. wgpu produces the following validation error:
thread 'main' panicked at 'wgpu error: Validation Error
Caused by:
In Device::create_shader_module
Function [1] 'get_voxel' is invalid
Expression [68] is invalid
The expression [67] may only be indexed by a constant
The octree is structured such that I can traverse it on the GPU. The structure is outlined in this NVIDIA paper: https://developer.nvidia.com/gpugems/gpugems2/part-v-image-oriented-computing/chapter-37-octree-textures-gpu
Essentially, the octree is an array of IndirectionGrid
s, and each IndirectionGrid
has exactly 8 GridCell
s colocated in memory. A grid cell can represent either a pointer to another IndirectionGrid
, or some data.
Lets say at its deepest the octree is representing a 16x16x16 grid. I want to get the GridCell
at 7,7,7. We know that 7,7,7 is in cell 0 of the root IndirectionGrid
because each component of the coordinate is less than the midpoint. If we add up the coordinates components we can get the index of the GridCell
for the current IndirectionGrid
. Because I'm traversing a tree, I do this at each level. Below is the incomplete code demonstrating this.
The line in question that's causing problems is let cell = grid.cells[grid_index].data;
So ultimately my question is, are dynamic indices allowed somehow? Is there something I can change that will magically make it work? Or is there more background information I need to understand about the tradeoffs WebGPU makes?
struct GridCell {
data: u32;
};
struct IndirectionGrid {
cells: array<GridCell, 8>;
};
[[block]]
struct VoxelVolume {
resolution: vec3<f32>;
size: vec3<f32>;
palette: array<u32, 256>;
indirection_pool: array<IndirectionGrid>;
};
[[group(2), binding(0)]]
var<storage, read> voxel_volume: VoxelVolume;
let COLOR_RED_MASK = 0xFF000000u;
let COLOR_GREEN_MASK = 0x00FF0000u;
let COLOR_BLUE_MASK = 0x0000FF00u;
let COLOR_ALPHA_MASK = 0x000000FFu;
let CELL_TYPE_MASK: u32 = 0xFF000000u;
let CELL_DATA_MASK: u32 = 0x00FFFFFFu;
fn get_voxel(pos: vec3<f32>) -> vec4<f32> {
let max_depth: i32 = 12;
let color = vec4<f32>(0.0, 0.0, 0.0, 0.0);
var pool_index = 0u;
var grid_size = max(max(voxel_volume.size.x, voxel_volume.size.y), voxel_volume.size.z);
for (var i: i32 = 0; i < max_depth; i = i + 1) {
let grid = voxel_volume.indirection_pool[pool_index];
let grid_coord_x = select(1u, 0u, pos.x / grid_size < 0.5);
let grid_coord_y = select(1u, 0u, pos.y / grid_size < 0.5);
let grid_coord_z = select(1u, 0u, pos.z / grid_size < 0.5);
let grid_index = grid_coord_x + grid_coord_y * 2u + grid_coord_z * 2u * 2u;
let cell = grid.cells[grid_index].data;
let cell_type = cell & CELL_TYPE_MASK >> 16u;
switch (cell_type) {
case 1u: {
pool_index = cell & CELL_DATA_MASK >> 8u;
}
case 2u: {
let palette_index = cell & CELL_DATA_MASK >> 8u;
let palette_color = voxel_volume.palette[palette_index];
return vec4<f32>(
f32(palette_color & COLOR_RED_MASK >> 24u) / 255.0,
f32(palette_color & COLOR_GREEN_MASK >> 16u) / 255.0,
f32(palette_color & COLOR_BLUE_MASK >> 8u) / 255.0,
f32(palette_color & COLOR_ALPHA_MASK) / 255.0
);
}
default: {
// discard;
return vec4<f32>(pos.x / 16.0, pos.y / 16.0, pos.z / 16.0, 1.0);
}
}
}
discard;
}
Indexing into storage buffers is totally fine. What Naga doesn't like is this line:
let cell = grid.cells[grid_index].data;
... because grid
isn't a storage buffer, it's just a value "on the stack".
WGSL has recently decided to allow this, but Naga doesn't implement the necessary hacks yet. And personally, I'd not recommend anybody to rely on this. Instead, you could keep a reference to the storage buffer:
let grid = &voxel_volume.indirection_pool[pool_index];
let cell = (*grid).cells[grid_index].data;
This path doesn't require us to make any hacks/workarounds in SPIR-V output.
Also note that latest Naga (and Firefox) print out a more detailed error:
┌─ indexing.wgsl:25:39
│
25 │ let CELL_DATA_MASK: u32 = 0x00FFFFFFu;
│ ╭──────────────────────────────────────^
26 │ │
27 │ │ fn get_voxel(pos: vec3<f32>) -> vec4<f32> {
28 │ │ let max_depth: i32 = 12;
· │
40 │ │ let cell = grid.cells[grid_index].data;
│ │ ^^^^^^^^^^^^^^^^^^^^^^^ naga::Expression [73]
· │
65 │ │ discard;
66 │ │ }
│ ╰─^ naga::Function [1]
Function [1] 'get_voxel' is invalid:
Expression [73] is invalid
The expression [72] may only be indexed by a constant