1#![allow(clippy::too_many_lines, clippy::missing_panics_doc)]
11
12use std::num::NonZeroU64;
13
14use bytemuck::{Pod, Zeroable};
15use wgpu::util::DeviceExt;
16
17use crate::decompress::{ChunkUpload, CHUNK_Z};
18
19#[repr(C)]
22#[derive(Clone, Copy, Pod, Zeroable)]
23struct ProbeUniform {
24 coord: [u32; 3],
25 vsid: u32,
26 chunk_z: u32,
27 _pad: [u32; 3],
28}
29
30pub struct GpuChunkResident {
33 pub vsid: u32,
36 pub occupancy: wgpu::Buffer,
40 pub color_offsets: wgpu::Buffer,
44 pub colors: wgpu::Buffer,
49 pub occupancy_bytes: u64,
52 pub color_offsets_bytes: u64,
54 pub colors_bytes: u64,
56
57 probe_uniform: wgpu::Buffer,
61 probe_output: wgpu::Buffer,
62 probe_readback: wgpu::Buffer,
63 probe_bg: wgpu::BindGroup,
64 probe_pipeline: wgpu::ComputePipeline,
65}
66
67impl GpuChunkResident {
68 pub fn upload(device: &wgpu::Device, chunk: &ChunkUpload) -> Self {
71 let occupancy = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
72 label: Some("roxlap-gpu chunk.occupancy"),
73 contents: bytemuck::cast_slice(&chunk.occupancy),
74 usage: wgpu::BufferUsages::STORAGE,
75 });
76 let color_offsets = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
77 label: Some("roxlap-gpu chunk.color_offsets"),
78 contents: bytemuck::cast_slice(&chunk.color_offsets),
79 usage: wgpu::BufferUsages::STORAGE,
80 });
81 let colors = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
82 label: Some("roxlap-gpu chunk.colors"),
83 contents: bytemuck::cast_slice(&chunk.colors),
84 usage: wgpu::BufferUsages::STORAGE,
85 });
86
87 let occupancy_bytes = (chunk.occupancy.len() * 4) as u64;
88 let color_offsets_bytes = (chunk.color_offsets.len() * 4) as u64;
89 let colors_bytes = (chunk.colors.len() * 4) as u64;
90
91 let probe_uniform = device.create_buffer(&wgpu::BufferDescriptor {
93 label: Some("roxlap-gpu chunk.probe_uniform"),
94 size: std::mem::size_of::<ProbeUniform>() as u64,
95 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
96 mapped_at_creation: false,
97 });
98 let probe_output = device.create_buffer(&wgpu::BufferDescriptor {
99 label: Some("roxlap-gpu chunk.probe_output"),
100 size: 4,
101 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
102 mapped_at_creation: false,
103 });
104 let probe_readback = device.create_buffer(&wgpu::BufferDescriptor {
105 label: Some("roxlap-gpu chunk.probe_readback"),
106 size: 4,
107 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
108 mapped_at_creation: false,
109 });
110
111 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
112 label: Some("debug_read.wgsl"),
113 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/debug_read.wgsl").into()),
114 });
115
116 let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
117 label: Some("roxlap-gpu chunk.probe_bgl"),
118 entries: &[
119 bgl_uniform_entry(0),
120 bgl_storage_entry(1, true),
121 bgl_storage_entry(2, true),
122 bgl_storage_entry(3, true),
123 bgl_storage_entry(4, false),
124 ],
125 });
126 let pl = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
127 label: Some("roxlap-gpu chunk.probe_layout"),
128 bind_group_layouts: &[Some(&bgl)],
129 immediate_size: 0,
130 });
131 let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
132 label: Some("roxlap-gpu chunk.probe_pipeline"),
133 layout: Some(&pl),
134 module: &shader,
135 entry_point: Some("debug_read"),
136 compilation_options: wgpu::PipelineCompilationOptions::default(),
137 cache: None,
138 });
139 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
140 label: Some("roxlap-gpu chunk.probe_bg"),
141 layout: &bgl,
142 entries: &[
143 wgpu::BindGroupEntry {
144 binding: 0,
145 resource: probe_uniform.as_entire_binding(),
146 },
147 wgpu::BindGroupEntry {
148 binding: 1,
149 resource: occupancy.as_entire_binding(),
150 },
151 wgpu::BindGroupEntry {
152 binding: 2,
153 resource: color_offsets.as_entire_binding(),
154 },
155 wgpu::BindGroupEntry {
156 binding: 3,
157 resource: colors.as_entire_binding(),
158 },
159 wgpu::BindGroupEntry {
160 binding: 4,
161 resource: probe_output.as_entire_binding(),
162 },
163 ],
164 });
165
166 Self {
167 vsid: chunk.vsid,
168 occupancy,
169 color_offsets,
170 colors,
171 occupancy_bytes,
172 color_offsets_bytes,
173 colors_bytes,
174 probe_uniform,
175 probe_output,
176 probe_readback,
177 probe_bg: bg,
178 probe_pipeline: pipeline,
179 }
180 }
181
182 pub fn resident_bytes(&self) -> u64 {
185 self.occupancy_bytes + self.color_offsets_bytes + self.colors_bytes
186 }
187
188 pub fn read_voxel_blocking(
195 &self,
196 device: &wgpu::Device,
197 queue: &wgpu::Queue,
198 x: u32,
199 y: u32,
200 z: u32,
201 ) -> Option<u32> {
202 let uniform = ProbeUniform {
203 coord: [x, y, z],
204 vsid: self.vsid,
205 chunk_z: CHUNK_Z,
206 _pad: [0; 3],
207 };
208 queue.write_buffer(&self.probe_uniform, 0, bytemuck::bytes_of(&uniform));
209
210 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
211 label: Some("roxlap-gpu chunk.read_voxel"),
212 });
213 {
214 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
215 label: Some("roxlap-gpu chunk.debug_read"),
216 timestamp_writes: None,
217 });
218 cpass.set_pipeline(&self.probe_pipeline);
219 cpass.set_bind_group(0, &self.probe_bg, &[]);
220 cpass.dispatch_workgroups(1, 1, 1);
221 }
222 encoder.copy_buffer_to_buffer(&self.probe_output, 0, &self.probe_readback, 0, 4);
223 queue.submit(std::iter::once(encoder.finish()));
224
225 let slice = self.probe_readback.slice(..);
229 let (tx, rx) = std::sync::mpsc::channel();
230 slice.map_async(wgpu::MapMode::Read, move |res| {
231 tx.send(res).expect("send map result");
232 });
233 device.poll(wgpu::PollType::wait_indefinitely()).ok();
234 rx.recv()
235 .expect("recv map result")
236 .expect("map_async returned an error");
237
238 let bytes = slice.get_mapped_range();
239 let value = u32::from_le_bytes(bytes[..4].try_into().expect("4 bytes"));
240 drop(bytes);
241 self.probe_readback.unmap();
242
243 if value == 0 {
244 None
245 } else {
246 Some(value)
247 }
248 }
249}
250
251fn bgl_uniform_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
252 wgpu::BindGroupLayoutEntry {
253 binding,
254 visibility: wgpu::ShaderStages::COMPUTE,
255 ty: wgpu::BindingType::Buffer {
256 ty: wgpu::BufferBindingType::Uniform,
257 has_dynamic_offset: false,
258 min_binding_size: NonZeroU64::new(std::mem::size_of::<ProbeUniform>() as u64),
259 },
260 count: None,
261 }
262}
263
264fn bgl_storage_entry(binding: u32, read_only: bool) -> wgpu::BindGroupLayoutEntry {
265 wgpu::BindGroupLayoutEntry {
266 binding,
267 visibility: wgpu::ShaderStages::COMPUTE,
268 ty: wgpu::BindingType::Buffer {
269 ty: wgpu::BufferBindingType::Storage { read_only },
270 has_dynamic_offset: false,
271 min_binding_size: None,
272 },
273 count: None,
274 }
275}