Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/euca-game/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ categories = ["game-development"]
[features]
default = []
metal-native = ["euca-render/metal-native"]
gpu-terrain = ["euca-terrain/gpu-terrain"]

[dependencies]
euca-ecs = { path = "../euca-ecs" }
Expand Down Expand Up @@ -65,6 +66,11 @@ path = "../../examples/tiled_level.rs"
name = "gpu_benchmark"
path = "../../examples/gpu_benchmark.rs"

[[example]]
name = "gpu_terrain_demo"
path = "../../examples/gpu_terrain_demo.rs"
required-features = ["gpu-terrain"]

[[example]]
name = "particle_demo"
path = "../../examples/particle_demo.rs"
Expand Down
43 changes: 43 additions & 0 deletions crates/euca-render/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,49 @@ impl<D: RenderDevice> Renderer<D> {
});
handle
}

/// Register pre-built GPU buffers as a renderable mesh.
///
/// Unlike [`upload_mesh`](Self::upload_mesh), which copies CPU-side vertex
/// and index data to the GPU, this method adopts buffers that already exist
/// on the GPU — for example, output buffers from a compute shader such as
/// the GPU terrain generator.
///
/// The caller is responsible for ensuring the vertex buffer contains data
/// in the engine's standard [`Vertex`] layout (44 bytes: pos.xyz +
/// normal.xyz + tangent.xyz + uv.xy) and that the index buffer contains
/// `u32` indices.
///
/// # Arguments
///
/// * `vertex_buffer` — GPU buffer containing interleaved vertex data.
/// * `vertex_buffer_size` — Size of the vertex buffer in bytes.
/// * `index_buffer` — GPU buffer containing `u32` triangle indices.
/// * `index_buffer_size` — Size of the index buffer in bytes.
/// * `index_count` — Number of indices (not bytes) in the index buffer.
pub fn register_gpu_mesh(
&mut self,
vertex_buffer: D::Buffer,
vertex_buffer_size: u64,
index_buffer: D::Buffer,
index_buffer_size: u64,
index_count: u32,
) -> MeshHandle {
let handle = MeshHandle(self.meshes.len() as u32);
self.meshes.push(GpuMesh {
vertex_buffer,
vertex_buffer_size,
index_buffer,
index_buffer_size,
index_count,
// GPU-generated meshes bypass the geometry pool and meshlet paths;
// they are rendered via standard indexed draw calls.
pool_alloc: None,
meshlet_data: None,
});
handle
}

/// Upload raw RGBA8 pixel data as a GPU texture with auto-generated mipmaps.
pub fn upload_texture(
&mut self,
Expand Down
64 changes: 64 additions & 0 deletions crates/euca-terrain/src/gpu_terrain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,70 @@ impl<D: RenderDevice> GpuTerrainGenerator<D> {
let quads = (grid_cols.saturating_sub(1) as u64) * (grid_rows.saturating_sub(1) as u64);
quads * (INDICES_PER_QUAD as u64) * std::mem::size_of::<u32>() as u64
}

/// Generate terrain mesh for a chunk, returning GPU buffers ready for rendering.
///
/// This is a higher-level wrapper around [`generate`](Self::generate) that
/// allocates output buffers with the correct usage flags
/// (`STORAGE | VERTEX` / `STORAGE | INDEX`) so they can serve as both
/// compute shader output and render pipeline input, then dispatches the
/// compute kernels.
///
/// The returned [`GpuTerrainChunkOutput`] contains the buffer handles and
/// index count needed to register the mesh with the renderer via
/// `Renderer::register_gpu_mesh`.
pub fn generate_chunk(
&self,
device: &D,
encoder: &mut D::CommandEncoder,
params: &TerrainGenParams,
) -> GpuTerrainChunkOutput<D> {
let vb_size = Self::vertex_buffer_size(params.grid_cols, params.grid_rows);
let ib_size = Self::index_buffer_size(params.grid_cols, params.grid_rows);

let vertex_buffer = device.create_buffer(&BufferDesc {
label: Some("Terrain Chunk Vertices"),
size: vb_size,
usage: BufferUsages::STORAGE | BufferUsages::VERTEX,
mapped_at_creation: false,
});

let index_buffer = device.create_buffer(&BufferDesc {
label: Some("Terrain Chunk Indices"),
size: ib_size,
usage: BufferUsages::STORAGE | BufferUsages::INDEX,
mapped_at_creation: false,
});

self.generate(device, encoder, params, &vertex_buffer, &index_buffer);

let index_count = (params.grid_cols.saturating_sub(1))
* (params.grid_rows.saturating_sub(1))
* INDICES_PER_QUAD;

GpuTerrainChunkOutput {
vertex_buffer,
vertex_buffer_size: vb_size,
index_buffer,
index_buffer_size: ib_size,
index_count,
}
}
}

/// Output from [`GpuTerrainGenerator::generate_chunk`]: GPU buffers containing
/// the generated terrain mesh, ready to be registered with the renderer.
pub struct GpuTerrainChunkOutput<D: RenderDevice> {
/// Vertex buffer (layout matches `euca_render::Vertex`: 44 bytes per vertex).
pub vertex_buffer: D::Buffer,
/// Size of the vertex buffer in bytes.
pub vertex_buffer_size: u64,
/// Index buffer (`u32` triangle indices).
pub index_buffer: D::Buffer,
/// Size of the index buffer in bytes.
pub index_buffer_size: u64,
/// Number of indices in the index buffer.
pub index_count: u32,
}

#[cfg(test)]
Expand Down
4 changes: 4 additions & 0 deletions crates/euca-terrain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ pub use lod::{ChunkLod, LodConfig, select_all_lods, select_chunk_lod};
pub use mesh::{TerrainMesh, TerrainVertex, generate_terrain_mesh};
pub use physics::{HeightfieldTile, generate_heightfield_colliders, height_at};
pub use splat::SplatMap;

// GPU terrain generation (requires the `gpu-terrain` feature).
#[cfg(feature = "gpu-terrain")]
pub use gpu_terrain::{GpuTerrainChunkOutput, GpuTerrainGenerator, TerrainGenParams};
Loading
Loading