Skip to main content

wde_terrain/render/dependencies/
terrain_buffer.rs

1use bevy::{ecs::system::SystemParamItem, prelude::*};
2use wde_renderer::prelude::*;
3
4use crate::{
5    manager::{
6        CHUNK_HEIGHT, CHUNK_MAP_SIZE, CHUNK_RENDER_SUBDIVISIONS, CHUNK_SIZE, SPLAT_MAP_COUNT,
7        SPLAT_MAPS, TERRAIN_MIN_HEIGHT
8    },
9    render::renderer_gpu::TerrainRendererGPU
10};
11
12const MAX_TERRAIN_TILES: usize = 1024;
13/// Number of terrain materials.
14pub const TERRAIN_MATERIAL_COUNT: usize = (SPLAT_MAPS * SPLAT_MAP_COUNT) as usize;
15
16/// Runtime-editable terrain rendering parameters synced to the render world each frame.
17#[derive(Resource, ExtractResource, Clone, Debug)]
18pub struct TerrainRenderSettings {
19    /// World size covered by one repeat of each material's textures.
20    pub tiling: [f32; TERRAIN_MATERIAL_COUNT],
21    /// How much each material's relief (displacement map) pushes it over the others where they
22    /// blend: 0 gives smooth fades, higher values crisp, natural transitions.
23    pub height_blend: [f32; TERRAIN_MATERIAL_COUNT]
24}
25impl Default for TerrainRenderSettings {
26    fn default() -> Self {
27        Self {
28            //      dust regolith pebbles gravel sediment cliff basalt compacted
29            tiling: [10.0, 12.0, 5.0, 5.0, 12.0, 14.0, 12.0, 6.0],
30            height_blend: [0.6, 0.5, 0.8, 0.9, 0.7, 1.0, 1.0, 0.4]
31        }
32    }
33}
34
35#[repr(C)]
36#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
37pub struct TerrainDescription {
38    pub tile_size: [f32; 3],
39    pub tile_subdivisions: f32,
40    pub min_height: f32,
41    pub map_size: f32,
42    pub _padding: [f32; 2],
43    pub tiling: [[f32; 4]; SPLAT_MAPS as usize],
44    pub height_blend: [[f32; 4]; SPLAT_MAPS as usize]
45}
46impl TerrainDescription {
47    fn new(settings: &TerrainRenderSettings) -> Self {
48        let pack = |values: &[f32; TERRAIN_MATERIAL_COUNT]| {
49            std::array::from_fn(|k| std::array::from_fn(|c| values[k * 4 + c]))
50        };
51        Self {
52            tile_size: [CHUNK_SIZE, CHUNK_HEIGHT, CHUNK_SIZE],
53            tile_subdivisions: CHUNK_RENDER_SUBDIVISIONS as f32,
54            min_height: TERRAIN_MIN_HEIGHT,
55            map_size: CHUNK_MAP_SIZE as f32,
56            _padding: [0.0; 2],
57            tiling: pack(&settings.tiling),
58            height_blend: pack(&settings.height_blend)
59        }
60    }
61}
62
63/// Per-tile GPU descriptor.
64/// `tile_layer` maps this draw-call instance to its layer in the texture arrays.
65#[repr(C)]
66#[derive(Clone, Copy, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)]
67pub struct TerrainTileDescription {
68    pub pos: [f32; 2],
69    /// Level of detail of the mesh drawing this tile.
70    pub lod: f32,
71    pub tile_layer: u32,
72    /// Level of detail of the -X, +X, -Z and +Z neighbors: edges facing a coarser neighbor
73    /// follow its edge, so that no crack opens between them.
74    pub neighbor_lods: [f32; 4]
75}
76
77#[derive(Asset, Clone, Debug, Default, TypePath)]
78pub struct TerrainBuffer;
79impl TerrainBuffer {
80    pub const DESC_BIND: u32 = 0;
81    pub const TILES_BIND: u32 = 1;
82}
83impl RenderData for TerrainBuffer {
84    type Params = ();
85
86    fn describe(_params: &mut SystemParamItem<Self::Params>, builder: &mut RenderDataBuilder) {
87        builder
88            .add_buffer(
89                Self::DESC_BIND,
90                Buffer {
91                    label: "terrain-description-buffer".to_string(),
92                    size: std::mem::size_of::<TerrainDescription>(),
93                    usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST,
94                    content: Some(
95                        bytemuck::cast_slice(&[TerrainDescription::new(
96                            &TerrainRenderSettings::default()
97                        )])
98                        .into()
99                    )
100                }
101            )
102            .add_buffer(
103                Self::TILES_BIND,
104                Buffer {
105                    label: "terrain-tiles-buffer".to_string(),
106                    size: std::mem::size_of::<TerrainTileDescription>() * MAX_TERRAIN_TILES,
107                    usage: BufferUsage::STORAGE | BufferUsage::COPY_DST,
108                    content: None
109                }
110            );
111    }
112}
113
114#[derive(Asset, Clone, TypePath, Default)]
115pub struct TerrainBufferBinding;
116impl RenderBinding for TerrainBufferBinding {
117    type Params = SRenderData<TerrainBuffer>;
118
119    fn describe(
120        &mut self,
121        buffer: &SystemParamItem<Self::Params>,
122        builder: &mut RenderBindingBuilder
123    ) {
124        builder
125            .add_buffer(buffer, TerrainBuffer::DESC_BIND)
126            .add_buffer(buffer, TerrainBuffer::TILES_BIND);
127    }
128
129    fn label(&self) -> &str {
130        "terrain_buffer_binding"
131    }
132}
133
134pub(crate) fn update_terrain_description_buffer(
135    render_instance: Res<RenderInstance>,
136    terrain_buffer: ResRenderData<TerrainBuffer>,
137    buffers: Res<RenderAssets<GpuBuffer>>,
138    settings: Res<TerrainRenderSettings>
139) {
140    if !settings.is_changed() {
141        return;
142    }
143    let terrain_buffer = match terrain_buffer.iter().next() {
144        Some((_, b)) => b,
145        None => return
146    };
147    let buf = match buffers.get(&terrain_buffer.get_buffer(TerrainBuffer::DESC_BIND).unwrap()) {
148        Some(b) => b,
149        None => return
150    };
151    let render_instance = render_instance.0.read().unwrap();
152    buf.buffer.write(
153        &render_instance,
154        bytemuck::cast_slice(&[TerrainDescription::new(&settings)]),
155        0
156    );
157}
158
159/// Uploads the chunks drawn this frame (see [`crate::render::renderer::TerrainDrawList`]).
160pub(crate) fn update_terrain_tiles_buffer(
161    render_instance: Res<RenderInstance>,
162    terrain_buffer: ResRenderData<TerrainBuffer>,
163    buffers: Res<RenderAssets<GpuBuffer>>,
164    terrain: Res<TerrainRendererGPU>
165) {
166    if !terrain.ready || terrain.draw_list.tiles.is_empty() {
167        return;
168    }
169    let terrain_buffer = match terrain_buffer.iter().next() {
170        Some((_, b)) => b,
171        None => return
172    };
173    let tile_buffer = match buffers.get(
174        &terrain_buffer
175            .get_buffer(TerrainBuffer::TILES_BIND)
176            .unwrap()
177    ) {
178        Some(b) => b,
179        None => return
180    };
181    let tiles = &terrain.draw_list.tiles[..terrain.draw_list.tiles.len().min(MAX_TERRAIN_TILES)];
182    let render_instance = render_instance.0.read().unwrap();
183    tile_buffer
184        .buffer
185        .write(&render_instance, bytemuck::cast_slice(tiles), 0);
186}