Integrate bevy_app_compute for GPU meshing

This commit is contained in:
Elias Stepanik 2025-06-13 03:19:44 +02:00
parent 1802595f7e
commit 5a7269a446
4 changed files with 60 additions and 93 deletions

View File

@ -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"] }

View File

@ -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::<GpuMeshingWorker>::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)

View File

@ -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::<GpuMeshingPipeline>()
.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::<AssetServer>();
let shader: Handle<Shader> = asset_server.load("shaders/greedy_meshing.wgsl");
let render_device = world.resource::<RenderDevice>();
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::<RenderDevice>();
let pipeline_cache = world.resource::<PipelineCache>();
let pipeline = pipeline_cache.queue_compute_pipeline(render_queue, &pipeline_descriptor);
Self { pipeline, layout }
impl ComputeWorker for GpuMeshingWorker {
fn build(world: &mut World) -> AppComputeWorker<Self> {
AppComputeWorkerBuilder::new(world)
.add_storage::<u32>("voxels", &[0u32; 1])
.add_uniform("params", &Params::default())
.add_storage::<VertexGpu>("vertices", &[VertexGpu::default(); 1])
.add_storage::<u32>("indices", &[0u32; 1])
.add_storage::<u32>("counts", &[0u32; 2])
.add_pass::<GreedyMeshingShader>(
[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<AppComputeWorker<GpuMeshingWorker>>,
_octrees: Query<&SparseVoxelOctree>,
_pool: ResMut<MeshBufferPool>,
_pipeline: Res<GpuMeshingPipeline>,
) {
// 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();
}

View File

@ -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;