diff --git a/Cargo.lock b/Cargo.lock index ef14850..747a4f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -664,7 +664,9 @@ dependencies = [ "guillotiere", "half", "image", + "ktx2", "rectangle-pack", + "ruzstd", "serde", "thiserror 2.0.12", "tracing", @@ -962,6 +964,7 @@ dependencies = [ "image", "indexmap", "js-sys", + "ktx2", "naga", "naga_oil", "nonmax", @@ -2441,6 +2444,15 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "ktx2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87d65e08a9ec02e409d27a0139eaa6b9756b4d81fe7cde71f6941a83730ce838" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "lalrpop" version = "0.22.1" @@ -3593,6 +3605,15 @@ dependencies = [ "unicode-script", ] +[[package]] +name = "ruzstd" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640bec8aad418d7d03c72ea2de10d5c646a598f9883c7babc160d91e3c1b26c" +dependencies = [ + "twox-hash", +] + [[package]] name = "ryu" version = "1.0.20" @@ -4063,6 +4084,12 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + [[package]] name = "typeid" version = "1.0.3" diff --git a/Cargo.toml b/Cargo.toml index 9c445b0..8cf5be3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,6 +68,9 @@ features = [ "x11", "bevy_winit", "bevy_window", + "tonemapping_luts", + "ktx2", + "zstd", ] [[example]] @@ -81,3 +84,6 @@ name = "one_shot" [[example]] name = "boids" + +[[example]] +name = "shared_storage" diff --git a/assets/shaders/shared_storage_compute.wgsl b/assets/shaders/shared_storage_compute.wgsl new file mode 100644 index 0000000..906b712 --- /dev/null +++ b/assets/shaders/shared_storage_compute.wgsl @@ -0,0 +1,17 @@ +@group(0) @binding(0) +var time: f32; + +@group(0) @binding(1) +var heights: array; + +@compute @workgroup_size(1) +fn main(@builtin(global_invocation_id) invocation_id: vec3) { + let idx = invocation_id.x; + if (idx < 121u) { + let grid_size = 11u; + let x = f32(idx % grid_size); + let z = f32(idx / grid_size); + + heights[idx] = sin(x * 0.5 + time) * cos(z * 0.5 + time) * 0.1; + } +} diff --git a/assets/shaders/shared_storage_vertex.wgsl b/assets/shaders/shared_storage_vertex.wgsl new file mode 100644 index 0000000..bc99939 --- /dev/null +++ b/assets/shaders/shared_storage_vertex.wgsl @@ -0,0 +1,58 @@ +#import bevy_pbr::{ + mesh_functions, + view_transformations::position_world_to_clip, + forward_io::{Vertex, VertexOutput}, +} + +@group(2) @binding(100) +var heights: array; + +@vertex +fn vertex(vertex: Vertex) -> VertexOutput { + var out: VertexOutput; + + let local_pos = vertex.position; + + let grid_size = 11u; + let x_index = u32(vertex.uv.x * f32(grid_size - 1u)); + let z_index = u32(vertex.uv.y * f32(grid_size - 1u)); + let height_index = z_index * grid_size + x_index; + + let height = heights[height_index]; + + var modified_position = local_pos; + modified_position.y += height; + + let world_from_local = mesh_functions::get_world_from_local(vertex.instance_index); + out.world_position = mesh_functions::mesh_position_local_to_world( + world_from_local, + vec4(modified_position, 1.0) + ); + out.position = position_world_to_clip(out.world_position.xyz); + + // compute normals from adjacent heights + let hL = heights[max(0u, height_index - 1u)]; + let hR = heights[min(120u, height_index + 1u)]; + let hD = heights[max(0u, height_index - grid_size)]; + let hU = heights[min(120u, height_index + grid_size)]; + + let normal = normalize(vec3(hL - hR, 2.0, hD - hU)); + + out.world_normal = mesh_functions::mesh_normal_local_to_world( + normal, + vertex.instance_index + ); + + out.uv = vertex.uv; + out.instance_index = vertex.instance_index; + + #ifdef VERTEX_TANGENTS + out.world_tangent = mesh_functions::mesh_tangent_local_to_world( + world_from_local, + vertex.tangent, + vertex.instance_index + ); + #endif + + return out; +} diff --git a/examples/boids/worker.rs b/examples/boids/worker.rs index 77d11e0..9bb55ba 100644 --- a/examples/boids/worker.rs +++ b/examples/boids/worker.rs @@ -70,6 +70,7 @@ impl ComputeWorker for BoidWorker { .add_pass::( [NUM_BOIDS / 64, 1, 1], &["params", "boids_src", "boids_dst"], + &[], ) .add_swap("boids_src", "boids_dst") .build() diff --git a/examples/game_of_life/worker.rs b/examples/game_of_life/worker.rs index 8f3a5a4..936fc55 100644 --- a/examples/game_of_life/worker.rs +++ b/examples/game_of_life/worker.rs @@ -68,6 +68,7 @@ impl ComputeWorker for GameOfLifeWorker { 1, ], &[SETTINGS_BUFFER, CELLS_IN_BUFFER, CELLS_OUT_BUFFER], + &[] ) .add_swap(CELLS_IN_BUFFER, CELLS_OUT_BUFFER) .build() diff --git a/examples/multi_pass.rs b/examples/multi_pass.rs index b051665..0d9d320 100644 --- a/examples/multi_pass.rs +++ b/examples/multi_pass.rs @@ -31,8 +31,8 @@ impl ComputeWorker for SimpleComputeWorker { .add_uniform("value", &3.) .add_storage("input", &[1., 2., 3., 4.]) .add_staging("output", &[0f32; 4]) - .add_pass::([4, 1, 1], &["value", "input", "output"]) // add each item + `value` from `input` to `output` - .add_pass::([4, 1, 1], &["output"]) // multiply each element of `output` by itself + .add_pass::([4, 1, 1], &["value", "input", "output"], &[]) // add each item + `value` from `input` to `output` + .add_pass::([4, 1, 1], &["output"], &[]) // multiply each element of `output` by itself .build(); // [1. + 3., 2. + 3., 3. + 3., 4. + 3.] = [4., 5., 6., 7.] diff --git a/examples/one_shot.rs b/examples/one_shot.rs index bc46bd4..6a058eb 100644 --- a/examples/one_shot.rs +++ b/examples/one_shot.rs @@ -20,7 +20,7 @@ impl ComputeWorker for SimpleComputeWorker { let worker = AppComputeWorkerBuilder::new(world) .add_uniform("uni", &5.) .add_staging("values", &[1., 2., 3., 4.]) - .add_pass::([4, 1, 1], &["uni", "values"]) + .add_pass::([4, 1, 1], &["uni", "values"], &[]) .one_shot() .build(); diff --git a/examples/shared_storage.rs b/examples/shared_storage.rs new file mode 100644 index 0000000..0cf8c8b --- /dev/null +++ b/examples/shared_storage.rs @@ -0,0 +1,116 @@ +use bevy::{ + pbr::{ExtendedMaterial, MaterialExtension, StandardMaterial}, + prelude::*, + render::{render_resource::AsBindGroup, storage::ShaderStorageBuffer}, +}; +use bevy_app_compute::prelude::*; +use tracing::info; + +#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)] +struct SharedStorageMaterial { + #[storage(100, read_only)] + heights: Handle, +} + +impl MaterialExtension for SharedStorageMaterial { + fn vertex_shader() -> ShaderRef { + "shaders/shared_storage_vertex.wgsl".into() + } +} + +#[derive(TypePath)] +struct SharedStorageComputeShader; + +impl ComputeShader for SharedStorageComputeShader { + fn shader() -> ShaderRef { + "shaders/shared_storage_compute.wgsl".into() + } +} + +#[derive(Resource)] +struct SimpleSharedStorageComputeWorker; + +impl ComputeWorker for SimpleSharedStorageComputeWorker { + fn build(world: &mut World) -> AppComputeWorker { + let heights = vec![0.0f32; 121]; + let worker = AppComputeWorkerBuilder::new(world) + .add_uniform("time", &0.0f32) + // This allows us to retrieve a `Handle` which gets shared between the compute shader and the material on the GPU. + .add_rw_storage_asset("heights", &heights) + .add_pass::([121, 1, 1], &["time"], &["heights"]) + .build(); + worker + } +} + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugins(MaterialPlugin::< + ExtendedMaterial, + >::default()) + .add_plugins(AppComputePlugin) + .add_plugins(AppComputeWorkerPlugin::::default()) + .add_systems(Startup, startup_system) + .add_systems( + Update, + ( + spawn_mesh_system + .run_if(resource_added::>), + update_shared_storage_time_system + .run_if(resource_exists::>), + ), + ) + .run(); +} + +fn startup_system(mut commands: Commands) { + commands.spawn(( + Camera3d::default(), + Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + )); + + commands.spawn(( + DirectionalLight { + shadows_enabled: true, + ..default() + }, + Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, -0.5, -0.5, 0.0)), + )); +} + +fn spawn_mesh_system( + mut commands: Commands, + worker: Res>, + mut meshes: ResMut>, + mut materials: ResMut>>, +) { + 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), + )); + + info!("Terrain mesh spawned"); + } +} + +fn update_shared_storage_time_system( + time: Res