diff --git a/client/Cargo.toml b/client/Cargo.toml index 57e4c92..a373ba7 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -21,4 +21,6 @@ smallvec = "1.14.0" once_cell = "1.21.3" rayon = "1.10.0" bincode = "1.3" +bevy_app_compute = "0.16" +bytemuck = { version = "1.14", features = ["derive"] } diff --git a/client/src/plugins/environment/environment_plugin.rs b/client/src/plugins/environment/environment_plugin.rs index e78a1b4..0dff8bd 100644 --- a/client/src/plugins/environment/environment_plugin.rs +++ b/client/src/plugins/environment/environment_plugin.rs @@ -1,6 +1,9 @@ use crate::plugins::environment::systems::voxels::debug::{draw_grid, visualize_octree_system}; use crate::plugins::environment::systems::voxels::lod::update_chunk_lods; -use crate::plugins::environment::systems::voxels::meshing_gpu::GpuMeshingPlugin; +use crate::plugins::environment::systems::voxels::meshing_gpu::{ + GpuMeshingWorker, queue_gpu_meshing, +}; +use bevy_app_compute::prelude::{AppComputePlugin, AppComputeWorkerPlugin}; use crate::plugins::environment::systems::voxels::queue_systems; use crate::plugins::environment::systems::voxels::queue_systems::{ enqueue_visible_chunks, process_chunk_queue, @@ -25,7 +28,8 @@ impl Plugin for EnvironmentPlugin { crate::plugins::environment::systems::voxel_system::setup, ), ); - app.add_plugins(GpuMeshingPlugin); + app.add_plugins(AppComputePlugin); + app.add_plugins(AppComputeWorkerPlugin::::default()); let view_distance_chunks = 100; app.insert_resource(ChunkCullingCfg { @@ -52,6 +56,7 @@ impl Plugin for EnvironmentPlugin { process_chunk_queue.after(enqueue_visible_chunks), update_chunk_lods.after(process_chunk_queue), rebuild_dirty_chunks.after(process_chunk_queue), // 4. (re)mesh dirty chunks + queue_gpu_meshing.after(rebuild_dirty_chunks), /* ---------- optional debug drawing ------- */ visualize_octree_system .run_if(should_visualize_octree) diff --git a/client/src/plugins/environment/systems/voxels/meshing_gpu.rs b/client/src/plugins/environment/systems/voxels/meshing_gpu.rs index c0c2f65..5435131 100644 --- a/client/src/plugins/environment/systems/voxels/meshing_gpu.rs +++ b/client/src/plugins/environment/systems/voxels/meshing_gpu.rs @@ -1,107 +1,67 @@ use bevy::prelude::*; -use bevy::render::render_resource::*; -use bevy::render::renderer::RenderDevice; -use bevy::render::RenderApp; +use bevy_app_compute::prelude::*; +use bytemuck::{Pod, Zeroable}; use super::structure::{MeshBufferPool, SparseVoxelOctree}; -/// Runs greedy meshing on the GPU. -pub struct GpuMeshingPlugin; +#[repr(C)] +#[derive(ShaderType, Copy, Clone, Pod, Zeroable, Default)] +pub struct Params { + pub origin: Vec3, + pub step: f32, + pub axis: u32, + pub dir: i32, + pub slice: u32, + pub _pad: u32, +} -impl Plugin for GpuMeshingPlugin { - fn build(&self, app: &mut App) { - let render_app = app.sub_app_mut(RenderApp); - render_app - .init_resource::() - .add_systems(Render, queue_gpu_meshing); +#[repr(C)] +#[derive(ShaderType, Copy, Clone, Pod, Zeroable, Default)] +pub struct VertexGpu { + pub pos: Vec3, + pub normal: Vec3, + pub uv: Vec2, +} + +#[derive(TypePath)] +struct GreedyMeshingShader; + +impl ComputeShader for GreedyMeshingShader { + fn shader() -> ShaderRef { + "shaders/greedy_meshing.wgsl".into() } } +/// GPU worker executing greedy meshing for chunks. #[derive(Resource)] -pub struct GpuMeshingPipeline { - pub pipeline: CachedComputePipelineId, - pub layout: BindGroupLayout, -} +pub struct GpuMeshingWorker; -impl FromWorld for GpuMeshingPipeline { - fn from_world(world: &mut World) -> Self { - let asset_server = world.resource::(); - let shader: Handle = asset_server.load("shaders/greedy_meshing.wgsl"); - let render_device = world.resource::(); - let layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: Some("meshing_layout"), - entries: &[ - BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::COMPUTE, - ty: BindingType::Buffer { - ty: BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - BindGroupLayoutEntry { - binding: 1, - visibility: ShaderStages::COMPUTE, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - BindGroupLayoutEntry { - binding: 2, - visibility: ShaderStages::COMPUTE, - ty: BindingType::Buffer { - ty: BufferBindingType::Storage { read_only: false }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - BindGroupLayoutEntry { - binding: 3, - visibility: ShaderStages::COMPUTE, - ty: BindingType::Buffer { - ty: BufferBindingType::Storage { read_only: false }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - BindGroupLayoutEntry { - binding: 4, - visibility: ShaderStages::COMPUTE, - ty: BindingType::Buffer { - ty: BufferBindingType::Storage { read_only: false }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], - }); - let pipeline_descriptor = ComputePipelineDescriptor { - label: Some("meshing_pipeline".into()), - layout: vec![layout.clone()], - shader, - shader_defs: vec![], - entry_point: "main".into(), - }; - let render_queue = world.resource::(); - let pipeline_cache = world.resource::(); - let pipeline = pipeline_cache.queue_compute_pipeline(render_queue, &pipeline_descriptor); - Self { pipeline, layout } +impl ComputeWorker for GpuMeshingWorker { + fn build(world: &mut World) -> AppComputeWorker { + AppComputeWorkerBuilder::new(world) + .add_storage::("voxels", &[0u32; 1]) + .add_uniform("params", &Params::default()) + .add_storage::("vertices", &[VertexGpu::default(); 1]) + .add_storage::("indices", &[0u32; 1]) + .add_storage::("counts", &[0u32; 2]) + .add_pass::( + [1, 1, 1], + &["voxels", "params", "vertices", "indices", "counts"], + ) + .one_shot() + .build() } } -/// System that dispatches the compute shader for dirty chunks. -fn queue_gpu_meshing( +/// Placeholder system that will dispatch the compute worker for dirty chunks. +pub fn queue_gpu_meshing( + mut worker: ResMut>, _octrees: Query<&SparseVoxelOctree>, _pool: ResMut, - _pipeline: Res, ) { - // TODO: upload voxel buffers and dispatch compute passes per chunk. + if !worker.ready() { + return; + } + // TODO: populate the worker buffers with chunk data before dispatching. + worker.execute(); } diff --git a/client/src/plugins/environment/systems/voxels/mod.rs b/client/src/plugins/environment/systems/voxels/mod.rs index 0b05e7c..b71c048 100644 --- a/client/src/plugins/environment/systems/voxels/mod.rs +++ b/client/src/plugins/environment/systems/voxels/mod.rs @@ -7,6 +7,6 @@ mod chunk; pub mod culling; pub mod lod; mod meshing; -mod meshing_gpu; +pub mod meshing_gpu; pub mod queue_systems; pub mod render_chunks;