wde_terrain/render/
extractor.rs1use bevy::prelude::*;
2use wde_renderer::{prelude::*, wgpu_utils::AsyncReadback};
3
4use crate::{manager::ChunkPos, prelude::TerrainRendererGPU};
5
6#[derive(Message)]
8pub struct ExtractedTileMessage {
9 pub pos: ChunkPos,
10 pub map_type: u32, pub splat_map_index: u32, pub data: Vec<u8>
13}
14
15#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
21pub enum TerrainRenderSets {
22 TextureWrite
24}
25
26pub struct TerrainExtractorPlugin;
27impl Plugin for TerrainExtractorPlugin {
28 fn build(&self, app: &mut App) {
29 app.init_resource::<TerrainExtractor>();
30
31 let render_app = app.get_sub_app_mut(RenderApp).unwrap();
32 render_app
33 .init_resource::<TerrainExtractor>()
34 .init_resource::<PendingReadbacks>()
35 .configure_sets(
37 Render,
38 TerrainRenderSets::TextureWrite.before(initiate_tile_readbacks)
39 )
40 .add_systems(Extract, flush_completed_readbacks)
42 .add_systems(Render, initiate_tile_readbacks.in_set(RenderSet::Render));
44 }
45}
46
47#[derive(Resource, Default)]
51pub struct TerrainExtractor {
52 pub tiles_to_extract: Vec<(ChunkPos, u32, u32)>
53}
54
55impl TerrainExtractor {
56 pub fn queue_tile_extraction(&mut self, pos: ChunkPos, map_type: u32, splat_index: u32) {
57 self.tiles_to_extract.push((pos, map_type, splat_index));
58 }
59}
60
61struct PendingReadback {
63 pos: ChunkPos,
64 map_type: u32,
65 splat_index: u32,
66 readback: AsyncReadback
67}
68
69#[derive(Resource, Default)]
71pub struct PendingReadbacks {
72 pending: Vec<PendingReadback>
73}
74
75pub fn extract_dirty(main: &mut TerrainExtractor, render: &mut TerrainExtractor) {
78 render.tiles_to_extract = std::mem::take(&mut main.tiles_to_extract);
79}
80
81pub fn initiate_tile_readbacks(
85 mut extractor: ResMut<TerrainExtractor>,
86 mut pending: ResMut<PendingReadbacks>,
87 gpu: Res<TerrainRendererGPU>,
88 textures: Res<RenderAssets<GpuTexture>>,
89 render_instance: Res<RenderInstance>
90) {
91 if extractor.tiles_to_extract.is_empty() {
92 return;
93 }
94
95 let ri = render_instance.0.read().unwrap();
96 let tiles = std::mem::take(&mut extractor.tiles_to_extract);
97
98 for (pos, map_type, splat_index) in tiles {
99 let Some(&layer) = gpu.pos_to_layer.get(&pos) else {
100 continue;
101 };
102 let texture_handle = match map_type {
103 0 => gpu.heightmap_array.as_ref(),
104 1 => gpu.splatmap_array.as_ref(),
105 _ => continue
106 };
107 let Some(tex_h) = texture_handle else {
108 continue;
109 };
110 let Some(tex) = textures.get(tex_h) else {
111 continue;
112 };
113
114 pending.pending.push(PendingReadback {
115 pos,
116 map_type,
117 splat_index,
118 readback: AsyncReadback::from_texture_layer(&ri, &tex.texture.texture, layer)
119 });
120 }
121}
122
123fn flush_completed_readbacks(
126 mut pending: ResMut<PendingReadbacks>,
127 render_instance: Res<RenderInstance>,
128 mut main_world: ResMut<MainWorld>
129) {
130 if pending.pending.is_empty() {
131 return;
132 }
133
134 render_instance.0.read().unwrap().poll_non_blocking();
136
137 let drained = std::mem::take(&mut pending.pending);
138 for PendingReadback {
139 pos,
140 map_type,
141 splat_index,
142 readback
143 } in drained
144 {
145 match readback.try_collect() {
146 Ok(data) if !data.is_empty() => {
147 main_world.write_message(ExtractedTileMessage {
148 pos,
149 map_type,
150 splat_map_index: splat_index,
151 data
152 });
153 }
154 Ok(_) => {} Err(readback) => {
156 pending.pending.push(PendingReadback {
158 pos,
159 map_type,
160 splat_index,
161 readback
162 });
163 }
164 }
165 }
166}