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::{CHUNK_HEIGHT, CHUNK_RENDER_SUBDIVISIONS, CHUNK_SIZE},
6    render::renderer_gpu::TerrainRendererGPU
7};
8
9const MAX_TERRAIN_TILES: usize = 1000;
10
11/// Runtime-editable terrain rendering parameters synced to the render world each frame.
12#[derive(Resource, ExtractResource, Clone, Debug)]
13pub struct TerrainRenderSettings {
14    pub displacement_scales: [f32; 4],
15    pub tiling_scales: [f32; 4]
16}
17impl Default for TerrainRenderSettings {
18    fn default() -> Self {
19        Self {
20            displacement_scales: [0.0, 0.16, 0.07, 0.0],
21            tiling_scales: [1.0, 9.5, 8.5, 1.0]
22        }
23    }
24}
25
26#[repr(C)]
27#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
28pub struct TerrainDescription {
29    pub tile_size: [f32; 3],
30    pub tile_subdivisions: f32,
31    pub displacement_scales: [f32; 4],
32    pub tiling_scales: [f32; 4]
33}
34impl Default for TerrainDescription {
35    fn default() -> Self {
36        Self {
37            tile_size: [CHUNK_SIZE, CHUNK_HEIGHT, CHUNK_SIZE],
38            tile_subdivisions: CHUNK_RENDER_SUBDIVISIONS as f32,
39            displacement_scales: TerrainRenderSettings::default().displacement_scales,
40            tiling_scales: TerrainRenderSettings::default().tiling_scales
41        }
42    }
43}
44
45/// Per-tile GPU descriptor.
46/// `tile_layer` maps this draw-call instance to its layer in the texture arrays.
47#[repr(C)]
48#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
49pub struct TerrainTileDescription {
50    pub pos: [f32; 2],
51    pub lod: f32,
52    pub tile_layer: u32
53}
54
55#[derive(Asset, Clone, Debug, Default, TypePath)]
56pub struct TerrainBuffer;
57impl TerrainBuffer {
58    pub const DESC_BIND: u32 = 0;
59    pub const TILES_BIND: u32 = 1;
60}
61impl RenderData for TerrainBuffer {
62    type Params = ();
63
64    fn describe(_params: &mut SystemParamItem<Self::Params>, builder: &mut RenderDataBuilder) {
65        builder
66            .add_buffer(
67                Self::DESC_BIND,
68                Buffer {
69                    label: "terrain-description-buffer".to_string(),
70                    size: std::mem::size_of::<TerrainDescription>(),
71                    usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST,
72                    content: Some(bytemuck::cast_slice(&[TerrainDescription::default()]).into())
73                }
74            )
75            .add_buffer(
76                Self::TILES_BIND,
77                Buffer {
78                    label: "terrain-tiles-buffer".to_string(),
79                    size: std::mem::size_of::<TerrainTileDescription>() * MAX_TERRAIN_TILES,
80                    usage: BufferUsage::STORAGE | BufferUsage::COPY_DST,
81                    content: None
82                }
83            );
84    }
85}
86
87#[derive(Asset, Clone, TypePath, Default)]
88pub struct TerrainBufferBinding;
89impl RenderBinding for TerrainBufferBinding {
90    type Params = SRenderData<TerrainBuffer>;
91
92    fn describe(
93        &mut self,
94        buffer: &SystemParamItem<Self::Params>,
95        builder: &mut RenderBindingBuilder
96    ) {
97        builder
98            .add_buffer(buffer, TerrainBuffer::DESC_BIND)
99            .add_buffer(buffer, TerrainBuffer::TILES_BIND);
100    }
101
102    fn label(&self) -> &str {
103        "terrain_buffer_binding"
104    }
105}
106
107pub(crate) fn update_terrain_description_buffer(
108    render_instance: Res<RenderInstance>,
109    terrain_buffer: ResRenderData<TerrainBuffer>,
110    buffers: Res<RenderAssets<GpuBuffer>>,
111    settings: Res<TerrainRenderSettings>
112) {
113    if !settings.is_changed() {
114        return;
115    }
116    let terrain_buffer = match terrain_buffer.iter().next() {
117        Some((_, b)) => b,
118        None => return
119    };
120    let buf = match buffers.get(&terrain_buffer.get_buffer(TerrainBuffer::DESC_BIND).unwrap()) {
121        Some(b) => b,
122        None => return
123    };
124    let render_instance = render_instance.0.read().unwrap();
125    buf.buffer.write(
126        &render_instance,
127        bytemuck::cast_slice(&[TerrainDescription {
128            tile_size: [CHUNK_SIZE, CHUNK_HEIGHT, CHUNK_SIZE],
129            tile_subdivisions: CHUNK_RENDER_SUBDIVISIONS as f32,
130            displacement_scales: settings.displacement_scales,
131            tiling_scales: settings.tiling_scales
132        }]),
133        0
134    );
135}
136
137pub(crate) fn update_terrain_tiles_buffer(
138    render_instance: Res<RenderInstance>,
139    terrain_buffer: ResRenderData<TerrainBuffer>,
140    buffers: Res<RenderAssets<GpuBuffer>>,
141    terrain: Res<TerrainRendererGPU>,
142    mut is_set: Local<bool>
143) {
144    if *is_set || !terrain.ready {
145        return;
146    }
147
148    let terrain_buffer = match terrain_buffer.iter().next() {
149        Some((_, b)) => b,
150        None => return
151    };
152    let tile_buffer = match buffers.get(
153        &terrain_buffer
154            .get_buffer(TerrainBuffer::TILES_BIND)
155            .unwrap()
156    ) {
157        Some(b) => b,
158        None => return
159    };
160
161    // Build sorted slice: layer index is the draw instance index.
162    let mut tiles: Vec<(&crate::manager::ChunkPos, &u32)> = terrain.pos_to_layer.iter().collect();
163    tiles.sort_by_key(|&(_, layer)| *layer);
164
165    let data: Vec<TerrainTileDescription> = tiles
166        .iter()
167        .map(|&(pos, layer)| TerrainTileDescription {
168            pos: [pos.x as f32, pos.y as f32],
169            lod: 1.0,
170            tile_layer: *layer
171        })
172        .collect();
173
174    let render_instance = render_instance.0.read().unwrap();
175    tile_buffer
176        .buffer
177        .write(&render_instance, bytemuck::cast_slice(&data), 0);
178
179    *is_set = true;
180}