Skip to content

Conversation

@BrianWiz
Copy link

@BrianWiz BrianWiz commented Oct 8, 2025

Challenge: Allow a custom material to read from the same storage buffer a compute shader writes to. #21

Adds a new example shared_storage. It runs a compute pass every frame and writes heightmap values to a storage buffer. A vertex shader reads from that and sets vertex heights (mesh is a plane). Result is an animated wavy terrain surface. On the CPU, it writes the "time" uniform every frame for animation purposes.

Adds a new function to the worker builder that allows you to retrieve a Handle<ShaderStorageBuffer> which can then be used on a custom material like so:

#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)]
struct SharedStorageMaterial {
    #[storage(100, read_only)]
    heights: Handle<ShaderStorageBuffer>,
}

impl MaterialExtension for SharedStorageMaterial {
    fn vertex_shader() -> ShaderRef {
        "shaders/shared_storage_vertex.wgsl".into()
    }
}

You can retrieve the handle from the worker and clone it when creating the material.

fn spawn_mesh_system(
    mut commands: Commands,
    worker: Res<AppComputeWorker<SimpleSharedStorageComputeWorker>>,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, SharedStorageMaterial>>>,
) {
    if let Some(heights_handle) = worker.get_storage_buffer_asset_handle("heights") {
        let mesh = meshes.add(Plane3d::default().mesh().size(4.0, 4.0).subdivisions(10));

        let material = materials.add(ExtendedMaterial {
            base: StandardMaterial {
                base_color: Color::srgb(0.3, 0.7, 0.3),
                ..default()
            },
            extension: SharedStorageMaterial {
                heights: heights_handle.clone(),
            },
        });

        commands.spawn((
            Mesh3d(mesh),
            MeshMaterial3d(material),
            Transform::from_xyz(0.0, 0.0, 0.0),
        ));
    }
}

Notes:

  • For the #[storage] attribute to work, it needs an asset handle like the code above. Bevy creates the buffer in the render subapp when the asset is first created. But, bevy_app_compute seems to have been designed to run on the main subapp. So I had to extract them back to the main subapp, but since the actual data is being stored on the GPU we're just cloning wgpu handles around anyway.
  • The more I think about it, if we're going to have this feature I think all storage buffers should just use the same thing, I don't think it makes sense to leave it up to the user to decide how it's stored and accessed internally, it just adds an unnecessary burden to make a decision. So, I'll likely fix that before going out of draft mode.
  • I think I added some code smell, so keeping in draft atm

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.

1 participant