1#![allow(clippy::must_use_candidate, clippy::too_many_lines)]
34
35pub mod camera;
36pub mod decompress;
37pub mod grid;
38#[cfg(not(target_arch = "wasm32"))]
41pub mod headless;
42pub mod resident;
43pub mod scene;
44pub mod sprite_model;
45
46pub use camera::Camera;
47pub use decompress::{decompress_chunk, ChunkUpload, BEDROCK_RGB, CHUNK_Z};
48pub use grid::{bounding_box_of, GpuGridResident, GridUpload};
49#[cfg(not(target_arch = "wasm32"))]
50pub use headless::HeadlessGpu;
51pub use resident::GpuChunkResident;
52pub use scene::{
53 GpuSceneResident, GridRuntimeTransform, GridStaticMeta, RefreshOutcome, SceneUpload,
54};
55pub use sprite_model::{
56 build_sprite_model, build_sprite_model_with_materials, sprite_model_from_clip_frame,
57 sprite_model_from_clip_frame_with_materials, sprite_model_from_voxel_frame,
58 sprite_model_from_voxel_frame_with_materials, SpriteInstance, SpriteInstanceTransform,
59 SpriteModel, SpriteModelRegistry, SpriteRegistryResident,
60};
61
62use std::sync::Arc;
63
64use bytemuck::{Pod, Zeroable};
65use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
66
67#[derive(Debug, Clone, Copy)]
72pub struct GpuRendererSettings {
73 pub power_preference: PowerPreference,
74 pub clear_colour: [f64; 3],
77 pub uncapped_present: bool,
80}
81
82#[derive(Debug, Clone, Copy)]
83pub enum PowerPreference {
84 Low,
85 High,
86}
87
88impl Default for GpuRendererSettings {
89 fn default() -> Self {
90 Self {
91 power_preference: PowerPreference::High,
92 clear_colour: [0.06, 0.08, 0.12],
93 uncapped_present: true,
94 }
95 }
96}
97
98#[derive(Debug)]
101pub enum GpuInitError {
102 CreateSurface(wgpu::CreateSurfaceError),
103 NoAdapter,
104 RequestDevice(wgpu::RequestDeviceError),
105}
106
107impl std::fmt::Display for GpuInitError {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 match self {
110 Self::CreateSurface(e) => write!(f, "create_surface failed: {e}"),
111 Self::NoAdapter => write!(
112 f,
113 "no compatible adapter — does this system have a Vulkan/Metal/DX12 driver?"
114 ),
115 Self::RequestDevice(e) => write!(f, "request_device failed: {e}"),
116 }
117 }
118}
119
120impl std::error::Error for GpuInitError {
121 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
122 match self {
123 Self::CreateSurface(e) => Some(e),
124 Self::RequestDevice(e) => Some(e),
125 Self::NoAdapter => None,
126 }
127 }
128}
129
130impl From<wgpu::CreateSurfaceError> for GpuInitError {
131 fn from(value: wgpu::CreateSurfaceError) -> Self {
132 Self::CreateSurface(value)
133 }
134}
135
136impl From<wgpu::RequestDeviceError> for GpuInitError {
137 fn from(value: wgpu::RequestDeviceError) -> Self {
138 Self::RequestDevice(value)
139 }
140}
141
142#[derive(Clone, Copy, Debug)]
155pub struct GpuLine {
156 pub a: [f32; 3],
157 pub b: [f32; 3],
158 pub color: [f32; 4],
159 pub width_px: f32,
160 pub depth_test: bool,
161}
162
163#[derive(Clone, Copy, Debug)]
167pub struct GpuLineCamera {
168 pub pos: [f32; 3],
169 pub right: [f32; 3],
170 pub down: [f32; 3],
171 pub forward: [f32; 3],
172}
173
174const LINE_NEAR_Z: f32 = 0.0625;
177const LINE_DEPTH_BIAS: f32 = 0.5;
180
181#[repr(C)]
185#[derive(Clone, Copy, Pod, Zeroable)]
186struct LineVertex {
187 pos: [f32; 2],
188 depth: f32,
189 depth_test: f32,
190 color: [f32; 4],
191}
192
193#[repr(C)]
196#[derive(Clone, Copy, Pod, Zeroable)]
197struct LineParams {
198 screen_w: u32,
200 screen_h: u32,
201 depth_bias: f32,
202 no_depth: u32,
203 flip_x: u32,
207 depth_w: u32,
211 depth_h: u32,
212 _pad: u32,
213}
214
215struct LineResources {
219 pipeline: wgpu::RenderPipeline,
220 bgl: wgpu::BindGroupLayout,
221 uniform_buf: wgpu::Buffer,
222 dummy_depth: wgpu::Buffer,
225}
226
227fn build_line_vertices(
235 cam: &GpuLineCamera,
236 lines: &[GpuLine],
237 w: u32,
238 h: u32,
239 fov_y: f32,
240 flip_x: bool,
241) -> Vec<LineVertex> {
242 let aspect = w as f32 / h as f32;
243 let half_h = (fov_y * 0.5).tan();
244 let half_w = half_h * aspect;
245 let (wf, hf) = (w as f32, h as f32);
246
247 let cam_coords = |p: [f32; 3]| -> [f32; 3] {
248 let d = [p[0] - cam.pos[0], p[1] - cam.pos[1], p[2] - cam.pos[2]];
249 [
250 cam.right[0] * d[0] + cam.right[1] * d[1] + cam.right[2] * d[2],
251 cam.down[0] * d[0] + cam.down[1] * d[1] + cam.down[2] * d[2],
252 cam.forward[0] * d[0] + cam.forward[1] * d[1] + cam.forward[2] * d[2],
253 ]
254 };
255 let project = |q: [f32; 3]| -> ([f32; 2], f32) {
258 let inv = 1.0 / q[2];
259 let nx = q[0] * inv / half_w;
260 let ny = -q[1] * inv / half_h;
261 let depth = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2]).sqrt();
262 ([nx, ny], depth)
263 };
264
265 let mut out = Vec::with_capacity(lines.len() * 6);
266 for line in lines {
267 let ca = cam_coords(line.a);
268 let cb = cam_coords(line.b);
269 let (cfa, cfb) = (ca[2], cb[2]);
270 if cfa < LINE_NEAR_Z && cfb < LINE_NEAR_Z {
271 continue;
272 }
273 let (mut t0, mut t1) = (0.0f32, 1.0f32);
275 let dz = cfb - cfa;
276 if dz.abs() > f32::EPSILON {
277 let tn = (LINE_NEAR_Z - cfa) / dz;
278 if dz > 0.0 {
279 t0 = t0.max(tn);
280 } else {
281 t1 = t1.min(tn);
282 }
283 }
284 if t0 > t1 {
285 continue;
286 }
287 let lerp3 = |t: f32| {
288 [
289 ca[0] + (cb[0] - ca[0]) * t,
290 ca[1] + (cb[1] - ca[1]) * t,
291 ca[2] + (cb[2] - ca[2]) * t,
292 ]
293 };
294 let (n0, d0) = project(lerp3(t0));
295 let (n1, d1) = project(lerp3(t1));
296
297 let to_px = |n: [f32; 2]| [(n[0] * 0.5 + 0.5) * wf, (0.5 - n[1] * 0.5) * hf];
299 let to_ndc = |p: [f32; 2]| [p[0] / wf * 2.0 - 1.0, 1.0 - p[1] / hf * 2.0];
300 let p0 = to_px(n0);
301 let p1 = to_px(n1);
302 let (dx, dy) = (p1[0] - p0[0], p1[1] - p0[1]);
303 let len = (dx * dx + dy * dy).sqrt().max(1e-6);
304 let half = line.width_px.max(1.0) * 0.5;
305 let (ex, ey) = (-dy / len * half, dx / len * half);
306
307 let c0a = to_ndc([p0[0] + ex, p0[1] + ey]);
308 let c0b = to_ndc([p0[0] - ex, p0[1] - ey]);
309 let c1a = to_ndc([p1[0] + ex, p1[1] + ey]);
310 let c1b = to_ndc([p1[0] - ex, p1[1] - ey]);
311 let dt = if line.depth_test { 1.0 } else { 0.0 };
312 let vert = |pos: [f32; 2], depth: f32| LineVertex {
314 pos: [if flip_x { -pos[0] } else { pos[0] }, pos[1]],
315 depth,
316 depth_test: dt,
317 color: line.color,
318 };
319 out.push(vert(c0a, d0));
321 out.push(vert(c0b, d0));
322 out.push(vert(c1a, d1));
323 out.push(vert(c1a, d1));
324 out.push(vert(c0b, d0));
325 out.push(vert(c1b, d1));
326 }
327 out
328}
329
330#[derive(Clone, Copy, Debug)]
338pub struct GpuImageQuad {
339 pub corners: [[f32; 3]; 4],
340 pub image: usize,
341 pub tint: [f32; 4],
342 pub depth_test: bool,
343 pub alpha_cutoff: f32,
346}
347
348#[repr(C)]
354#[derive(Clone, Copy, Pod, Zeroable)]
355struct ImageVertex {
356 ndc: [f32; 2],
357 w: f32,
358 depth: f32,
359 depth_test: f32,
360 cutoff: f32,
361 uv: [f32; 2],
362 tint: [f32; 4],
363}
364
365struct ImageResources {
369 pipeline: wgpu::RenderPipeline,
370 bgl: wgpu::BindGroupLayout,
371 uniform_buf: wgpu::Buffer,
372 dummy_depth: wgpu::Buffer,
373 sampler: wgpu::Sampler,
374}
375
376struct ImageResident {
379 view: wgpu::TextureView,
380 _texture: wgpu::Texture,
382}
383
384#[derive(Clone, Copy)]
387struct ImgClipV {
388 cam: [f32; 3],
389 uv: [f32; 2],
390}
391
392fn clip_near_image(poly: &[ImgClipV]) -> Vec<ImgClipV> {
395 let n = poly.len();
396 let mut out: Vec<ImgClipV> = Vec::with_capacity(n + 1);
397 for i in 0..n {
398 let cur = poly[i];
399 let prev = poly[(i + n - 1) % n];
400 let cur_in = cur.cam[2] >= LINE_NEAR_Z;
401 let prev_in = prev.cam[2] >= LINE_NEAR_Z;
402 if cur_in != prev_in {
403 let t = (LINE_NEAR_Z - prev.cam[2]) / (cur.cam[2] - prev.cam[2]);
404 out.push(ImgClipV {
405 cam: [
406 prev.cam[0] + (cur.cam[0] - prev.cam[0]) * t,
407 prev.cam[1] + (cur.cam[1] - prev.cam[1]) * t,
408 LINE_NEAR_Z,
409 ],
410 uv: [
411 prev.uv[0] + (cur.uv[0] - prev.uv[0]) * t,
412 prev.uv[1] + (cur.uv[1] - prev.uv[1]) * t,
413 ],
414 });
415 }
416 if cur_in {
417 out.push(cur);
418 }
419 }
420 out
421}
422
423fn build_image_vertices(
429 cam: &GpuLineCamera,
430 quad: &GpuImageQuad,
431 w: u32,
432 h: u32,
433 fov_y: f32,
434 flip_x: bool,
435) -> Vec<ImageVertex> {
436 let aspect = w as f32 / h as f32;
437 let half_h = (fov_y * 0.5).tan();
438 let half_w = half_h * aspect;
439 let dt = if quad.depth_test { 1.0 } else { 0.0 };
440
441 let cam_coords = |p: [f32; 3]| -> [f32; 3] {
442 let d = [p[0] - cam.pos[0], p[1] - cam.pos[1], p[2] - cam.pos[2]];
443 [
444 cam.right[0] * d[0] + cam.right[1] * d[1] + cam.right[2] * d[2],
445 cam.down[0] * d[0] + cam.down[1] * d[1] + cam.down[2] * d[2],
446 cam.forward[0] * d[0] + cam.forward[1] * d[1] + cam.forward[2] * d[2],
447 ]
448 };
449 let project = |v: ImgClipV| -> ImageVertex {
450 let (cx, cy, cz) = (v.cam[0], v.cam[1], v.cam[2]);
451 let nx = cx / (cz * half_w);
452 ImageVertex {
453 ndc: [if flip_x { -nx } else { nx }, -cy / (cz * half_h)],
455 w: cz,
456 depth: (cx * cx + cy * cy + cz * cz).sqrt(),
457 depth_test: dt,
458 cutoff: quad.alpha_cutoff,
459 uv: v.uv,
460 tint: quad.tint,
461 }
462 };
463
464 let uvs = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
466 let verts: Vec<ImgClipV> = quad
467 .corners
468 .iter()
469 .zip(uvs)
470 .map(|(c, uv)| ImgClipV {
471 cam: cam_coords(*c),
472 uv,
473 })
474 .collect();
475
476 let mut out = Vec::with_capacity(12);
477 for tri in [[0usize, 1, 2], [1, 3, 2]] {
478 let poly = [verts[tri[0]], verts[tri[1]], verts[tri[2]]];
479 let clipped = clip_near_image(&poly);
480 if clipped.len() < 3 {
481 continue;
482 }
483 for i in 1..clipped.len() - 1 {
484 out.push(project(clipped[0]));
485 out.push(project(clipped[i]));
486 out.push(project(clipped[i + 1]));
487 }
488 }
489 out
490}
491
492#[derive(Clone, Copy, Debug)]
496pub struct PosterizeGpu {
497 pub levels: [u32; 3],
498 pub dither: u32,
499}
500
501#[derive(Clone, Copy, Debug, PartialEq, Default)]
505pub enum RenderResolution {
506 #[default]
508 Native,
509 Fixed { w: u32, h: u32 },
511 Scale(f32),
513}
514
515impl RenderResolution {
516 #[must_use]
518 fn logical_for(self, native: (u32, u32)) -> (u32, u32) {
519 let (nw, nh) = (native.0.max(1), native.1.max(1));
520 match self {
521 Self::Native => (nw, nh),
522 Self::Fixed { w, h } => (w.max(1), h.max(1)),
523 Self::Scale(f) => {
524 let s = f.max(1e-3);
525 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
526 let lw = ((nw as f32) * s).round() as u32;
527 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
528 let lh = ((nh as f32) * s).round() as u32;
529 (lw.max(1), lh.max(1))
530 }
531 }
532 }
533}
534
535#[allow(clippy::struct_excessive_bools)] pub struct GpuRenderer {
537 surface: wgpu::Surface<'static>,
538 surface_config: wgpu::SurfaceConfiguration,
539 device: wgpu::Device,
540 queue: wgpu::Queue,
541 adapter_info: String,
542 clear_colour: [f64; 3],
543 frame_count: u32,
544 flip_x: bool,
548 render_res: RenderResolution,
554 ssaa: u32,
558 posterize: Option<PosterizeGpu>,
561 chunk_dda: Option<ChunkDdaResources>,
564 grid_dda: Option<GridDdaResources>,
568 scene_dda: Option<SceneDdaResources>,
571 scene_materials: Box<[MaterialGpu; 256]>,
574 scene_terrain_map: Vec<[u32; 2]>,
577 scene_terrain_translucent: bool,
578 scene_depth_valid: bool,
585 sky_texture: wgpu::Texture,
590 sky_view: wgpu::TextureView,
591 sky_sampler: wgpu::Sampler,
592 fog_color: [f32; 3],
598 fog_near: f32,
599 fog_far: f32,
600 sprite_registry: Option<sprite_model::SpriteRegistryResident>,
605 sprite_model_dda: Option<SpriteModelDdaResources>,
607 sprite_materials: Box<[MaterialGpu; 256]>,
611 sprite_has_translucent: bool,
612 sprite_shadows_capable: bool,
619 sprite_lod_px: f32,
624 scene_mip_scan_dist: f32,
631 scene_side_shades: [[i32; 4]; 2],
636 scene_lights: SceneLights,
641 last_fov_y_rad: f32,
645 pending_frame: Option<(wgpu::SurfaceTexture, wgpu::TextureView)>,
652 line_resources: Option<LineResources>,
655 line_vbuf: Option<wgpu::Buffer>,
660 line_vbuf_cap: u64,
661 image_resources: Option<ImageResources>,
664 image_vbuf: Option<wgpu::Buffer>,
667 image_vbuf_cap: u64,
668 images: Vec<Option<ImageResident>>,
672 #[cfg(feature = "hud")]
675 egui_renderer: Option<egui_wgpu::Renderer>,
676}
677
678struct ChunkDdaResources {
682 storage_size: (u32, u32),
683 storage_view: wgpu::TextureView,
684 uniform_buf: wgpu::Buffer,
685 bgl_dda: wgpu::BindGroupLayout,
686 pipeline_dda: wgpu::ComputePipeline,
687 blit_bg: wgpu::BindGroup,
688 pipeline_blit: wgpu::RenderPipeline,
689 _sampler: wgpu::Sampler,
692}
693
694struct GridDdaResources {
695 storage_size: (u32, u32),
696 storage_view: wgpu::TextureView,
697 uniform_buf: wgpu::Buffer,
698 bgl_dda: wgpu::BindGroupLayout,
699 pipeline_dda: wgpu::ComputePipeline,
700 blit_bg: wgpu::BindGroup,
701 pipeline_blit: wgpu::RenderPipeline,
702 _sampler: wgpu::Sampler,
703}
704
705struct SceneDdaResources {
706 storage_size: (u32, u32),
709 logical_size: (u32, u32),
711 framebuffer: wgpu::Buffer,
717 uniform_buf: wgpu::Buffer,
718 bgl_dda: wgpu::BindGroupLayout,
719 pipeline_dda: wgpu::ComputePipeline,
720 pipeline_resolve: wgpu::ComputePipeline,
724 resolve_bg: wgpu::BindGroup,
725 resolve_dims: wgpu::Buffer,
728 blit_bg: wgpu::BindGroup,
730 pipeline_blit: wgpu::RenderPipeline,
731 blit_dims: wgpu::Buffer,
734 depth_buffer: wgpu::Buffer,
739 depth_readback: wgpu::Buffer,
743 materials_pal_buf: wgpu::Buffer,
746 terrain_map_buf: wgpu::Buffer,
749 sprite_cast_dummy: Option<wgpu::Buffer>,
754}
755
756struct SpriteModelDdaResources {
760 bgl: wgpu::BindGroupLayout,
761 pipeline: wgpu::ComputePipeline,
762 uniform_buf: wgpu::Buffer,
763 materials_buf: wgpu::Buffer,
767}
768
769#[repr(C)]
774#[derive(Clone, Copy, Pod, Zeroable)]
775struct SpriteModelUniform {
776 cam_pos: [f32; 3],
777 _p0: f32,
778 cam_right: [f32; 3],
779 _p1: f32,
780 cam_down: [f32; 3],
781 _p2: f32,
782 cam_forward: [f32; 3],
783 _p3: f32,
784 fog_color: [f32; 4],
785 screen_size: [u32; 2],
786 instance_count: u32,
787 fog_far: f32,
788 fov_y_rad: f32,
789 tiles_x: u32,
790 tile_size: u32,
791 has_translucent: u32,
794 sun_dir: [f32; 4],
798 sun_color: [f32; 4],
800 ambient_color: [f32; 4],
802 sun_flags: u32,
804 point_light_count: u32,
805 _pad_dl: [u32; 2],
806 shadow_tint: [f32; 4],
809 style_bands: u32,
811 occ_num_pages: u32,
816 occ_page_words: u32,
817 grid_count: u32,
818 max_outer_steps: u32,
819 shadow_max_steps: u32,
820 shadow_bias: f32,
821 shadow_max_dist: f32,
822 shadow_strength: f32,
824 _pad_xs: [u32; 3],
825}
826
827const SPRITE_TILE_SIZE: u32 = 16;
829
830#[repr(C)]
833#[derive(Clone, Copy, Pod, Zeroable)]
834struct MaterialGpu {
835 alpha: f32,
837 mode: u32,
839}
840
841fn material_palette(
845 table: &roxlap_formats::material::MaterialTable,
846) -> (Box<[MaterialGpu; 256]>, bool) {
847 let mut out = Box::new(
848 [MaterialGpu {
849 alpha: 1.0,
850 mode: 0,
851 }; 256],
852 );
853 let mut any_translucent = false;
854 for (id, slot) in out.iter_mut().enumerate() {
855 let m = table.get(id as u8);
856 slot.alpha = f32::from(m.alpha) / 255.0;
857 slot.mode = u32::from(m.mode.as_u8());
858 if !m.is_opaque() {
859 any_translucent = true;
860 }
861 }
862 (out, any_translucent)
863}
864
865pub const MAX_POINT_LIGHTS: usize = 32;
877pub const MAX_SHADOW_CASTERS: usize = 4;
881
882#[derive(Clone, Copy, Debug)]
886pub struct GpuLight {
887 pub position: [f32; 3],
889 pub radius: f32,
891 pub color: [f32; 3],
893 pub intensity: f32,
894 pub casts_shadow: bool,
895 pub spot_dir: [f32; 3],
900 pub cos_inner: f32,
902 pub cos_outer: f32,
905}
906
907#[derive(Clone, Default)]
913pub struct SceneLights {
914 pub enabled: bool,
920 pub grid_sun_dirs: Vec<[f32; 3]>,
922 pub sun_color: [f32; 3],
923 pub sun_intensity: f32,
924 pub sun_casts_shadow: bool,
925 pub grid_point_lights: Vec<Vec<GpuLight>>,
928 pub ambient: [f32; 3],
930 pub shadow_strength: f32,
931 pub shadow_bias: f32,
932 pub shadow_max_dist: f32,
933 pub shadow_max_steps: u32,
934 pub world_sun_dir: [f32; 3],
939 pub world_points: Vec<GpuLight>,
942 pub style_bands: u32,
945 pub shadow_tint: [f32; 3],
947}
948
949#[repr(C)]
954#[derive(Clone, Copy, Pod, Zeroable)]
955struct GpuPointLight {
956 pos: [f32; 3],
957 radius: f32,
958 color: [f32; 3],
959 intensity: f32,
960 spot_dir: [f32; 3],
962 cos_outer: f32,
963 cos_inner: f32,
964 casts_shadow: u32,
965 _pad: [u32; 2],
966}
967
968const _: () = assert!(core::mem::size_of::<GpuPointLight>() == 64);
971
972fn upload_grid_point_lights(device: &wgpu::Device, lights: &[GpuPointLight]) -> wgpu::Buffer {
976 use wgpu::util::DeviceExt;
977 let one = [GpuPointLight::zeroed()];
978 let src: &[GpuPointLight] = if lights.is_empty() { &one } else { lights };
979 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
980 label: Some("roxlap-gpu scene_dda.grid_point_lights"),
981 contents: bytemuck::cast_slice(src),
982 usage: wgpu::BufferUsages::STORAGE,
983 })
984}
985
986fn pack_scene_lights(
994 device: &wgpu::Device,
995 lights: &SceneLights,
996 grid_count: usize,
997 cam_vec: &mut [SceneDdaPerGridCamera],
998) -> (wgpu::Buffer, u32, u32) {
999 let sun_enabled = !lights.grid_sun_dirs.is_empty();
1000 if sun_enabled {
1001 for (g, cam) in cam_vec.iter_mut().enumerate() {
1002 let d = lights.grid_sun_dirs.get(g).copied().unwrap_or([0.0; 3]);
1003 cam.sun_dir = [d[0], d[1], d[2], 0.0];
1004 }
1005 }
1006 let mut point_count = lights
1008 .grid_point_lights
1009 .first()
1010 .map_or(0, std::vec::Vec::len);
1011 if point_count > MAX_POINT_LIGHTS {
1012 eprintln!(
1013 "roxlap-gpu: {point_count} point lights > MAX_POINT_LIGHTS ({MAX_POINT_LIGHTS}); dropping the excess"
1014 );
1015 point_count = MAX_POINT_LIGHTS;
1016 }
1017 let mut budget = MAX_SHADOW_CASTERS;
1023 if sun_enabled && lights.sun_casts_shadow {
1024 budget = budget.saturating_sub(1);
1025 }
1026 let mut allow_shadow = vec![false; point_count];
1027 let mut demoted = 0usize;
1028 if let Some(rep) = lights.grid_point_lights.first() {
1029 for (i, slot) in allow_shadow.iter_mut().enumerate() {
1030 if rep.get(i).is_some_and(|l| l.casts_shadow) {
1031 if budget > 0 {
1032 *slot = true;
1033 budget -= 1;
1034 } else {
1035 demoted += 1;
1036 }
1037 }
1038 }
1039 }
1040 if demoted > 0 {
1041 eprintln!(
1042 "roxlap-gpu: {demoted} shadow-casting point lights > MAX_SHADOW_CASTERS ({MAX_SHADOW_CASTERS}); demoting the excess to shadowless"
1043 );
1044 }
1045 let mut packed: Vec<GpuPointLight> = Vec::with_capacity(grid_count * point_count);
1047 for g in 0..grid_count {
1048 let row = lights.grid_point_lights.get(g);
1049 for (i, &allow) in allow_shadow.iter().enumerate() {
1050 let p = row.and_then(|r| r.get(i));
1051 packed.push(p.map_or(GpuPointLight::zeroed(), |l| GpuPointLight {
1052 pos: l.position,
1053 radius: l.radius,
1054 color: l.color,
1055 intensity: l.intensity,
1056 spot_dir: l.spot_dir,
1057 cos_outer: l.cos_outer,
1058 cos_inner: l.cos_inner,
1059 casts_shadow: u32::from(l.casts_shadow && allow),
1060 _pad: [0; 2],
1061 }));
1062 }
1063 }
1064 let buf = upload_grid_point_lights(device, &packed);
1065 let sun_flags = u32::from(sun_enabled)
1066 | (u32::from(sun_enabled && lights.sun_casts_shadow) << 1)
1067 | (u32::from(lights.enabled) << 2);
1068 (buf, sun_flags, point_count as u32)
1069}
1070
1071fn upload_grid_cameras(device: &wgpu::Device, cams: &[SceneDdaPerGridCamera]) -> wgpu::Buffer {
1078 use wgpu::util::DeviceExt;
1079 let one = [SceneDdaPerGridCamera::zeroed()];
1080 let src: &[SceneDdaPerGridCamera] = if cams.is_empty() { &one } else { cams };
1081 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1082 label: Some("roxlap-gpu scene_dda.grid_cameras"),
1083 contents: bytemuck::cast_slice(src),
1084 usage: wgpu::BufferUsages::STORAGE,
1085 })
1086}
1087
1088const _: () = assert!(scene::MAX_OCC_PAGES == 4);
1093
1094#[repr(C)]
1095#[derive(Clone, Copy, Pod, Zeroable)]
1096struct SceneDdaPerGridCamera {
1097 pos: [f32; 3],
1098 _pad0: f32,
1099 right: [f32; 3],
1100 _pad1: f32,
1101 down: [f32; 3],
1102 _pad2: f32,
1103 forward: [f32; 3],
1104 _pad3: f32,
1105 sun_dir: [f32; 4],
1110 world_origin: [f32; 4],
1116 rot0: [f32; 4],
1117 rot1: [f32; 4],
1118 rot2: [f32; 4],
1119}
1120
1121impl SceneDdaPerGridCamera {
1122 fn from_camera(c: &Camera) -> Self {
1123 Self {
1124 pos: c.position,
1125 _pad0: 0.0,
1126 right: c.right,
1127 _pad1: 0.0,
1128 down: c.down,
1129 _pad2: 0.0,
1130 forward: c.forward,
1131 _pad3: 0.0,
1132 sun_dir: [0.0; 4],
1133 world_origin: [0.0; 4],
1136 rot0: [1.0, 0.0, 0.0, 0.0],
1137 rot1: [0.0, 1.0, 0.0, 0.0],
1138 rot2: [0.0, 0.0, 1.0, 0.0],
1139 }
1140 }
1141
1142 fn set_world_transform(&mut self, t: &GridWorldTransform) {
1146 self.world_origin = [t.origin[0], t.origin[1], t.origin[2], 0.0];
1147 self.rot0 = [t.rot_cols[0][0], t.rot_cols[0][1], t.rot_cols[0][2], 0.0];
1148 self.rot1 = [t.rot_cols[1][0], t.rot_cols[1][1], t.rot_cols[1][2], 0.0];
1149 self.rot2 = [t.rot_cols[2][0], t.rot_cols[2][1], t.rot_cols[2][2], 0.0];
1150 }
1151}
1152
1153#[derive(Clone, Copy)]
1158pub struct GridWorldTransform {
1159 pub origin: [f32; 3],
1160 pub rot_cols: [[f32; 3]; 3],
1161}
1162
1163impl Default for GridWorldTransform {
1164 fn default() -> Self {
1165 Self {
1166 origin: [0.0; 3],
1167 rot_cols: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
1168 }
1169 }
1170}
1171
1172#[repr(C)]
1173#[derive(Clone, Copy, Pod, Zeroable)]
1174struct SceneDdaUniform {
1175 fov_y_rad: f32,
1176 grid_count: u32,
1177 max_outer_steps: u32,
1178 _pad0: u32,
1179 screen_size: [u32; 2],
1180 _pad1: [u32; 2],
1181 fog_color: [f32; 4],
1185 fog_far: f32,
1186 write_depth: u32,
1189 occ_page_words: u32,
1193 occ_num_pages: u32,
1196 mip_scan_dist: f32,
1201 terrain_has_translucent: u32,
1204 terrain_map_count: u32,
1206 _pad4: u32,
1207 sky_cam: SceneDdaPerGridCamera,
1212 side_shades0: [i32; 4],
1217 side_shades1: [i32; 4],
1218 sun_color: [f32; 4],
1221 ambient_color: [f32; 4],
1223 sun_flags: u32,
1225 point_light_count: u32,
1227 shadow_max_steps: u32,
1229 _pad5: u32,
1230 shadow_bias: f32,
1232 shadow_max_dist: f32,
1234 _pad6: [f32; 2],
1235 shadow_tint: [f32; 4],
1237 style_bands: u32,
1239 sprite_cast_count: u32,
1243 _pad7: [u32; 2],
1244}
1245
1246#[repr(C)]
1247#[derive(Clone, Copy, Pod, Zeroable)]
1248struct GridDdaUniform {
1249 camera_pos: [f32; 3],
1250 _pad0: f32,
1251 camera_right: [f32; 3],
1252 _pad1: f32,
1253 camera_down: [f32; 3],
1254 _pad2: f32,
1255 camera_forward: [f32; 3],
1256 fov_y_rad: f32,
1257 screen_size: [u32; 2],
1258 vsid: u32,
1259 max_outer_steps: u32,
1260 chunks_dims: [u32; 3],
1261 _pad3: u32,
1262 origin_chunk: [i32; 3],
1263 _pad4: u32,
1264}
1265
1266#[repr(C)]
1267#[derive(Clone, Copy, Pod, Zeroable)]
1268struct ChunkDdaUniform {
1269 camera_pos: [f32; 3],
1270 _pad0: f32,
1271 camera_right: [f32; 3],
1272 _pad1: f32,
1273 camera_down: [f32; 3],
1274 _pad2: f32,
1275 camera_forward: [f32; 3],
1276 fov_y_rad: f32,
1277 screen_size: [u32; 2],
1278 vsid: u32,
1279 max_scan_dist: u32,
1280}
1281
1282impl GpuRenderer {
1283 pub async fn new<W>(
1298 window: Arc<W>,
1299 size: (u32, u32),
1300 settings: GpuRendererSettings,
1301 ) -> Result<Self, GpuInitError>
1302 where
1303 W: HasWindowHandle + HasDisplayHandle + Send + Sync + 'static,
1304 {
1305 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
1306 let surface = instance.create_surface(window.clone())?;
1307 let adapter = Self::request_adapter(&instance, Some(&surface), settings).await?;
1308 let (device, queue) = Self::request_device(&adapter).await?;
1309 Ok(Self::finish_init(
1310 &adapter, device, queue, surface, size, settings,
1311 ))
1312 }
1313
1314 #[cfg(target_arch = "wasm32")]
1331 pub async fn new_from_canvas(
1332 canvas: web_sys::HtmlCanvasElement,
1333 size: (u32, u32),
1334 settings: GpuRendererSettings,
1335 ) -> Result<Self, GpuInitError> {
1336 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
1337 let adapter = Self::request_adapter(&instance, None, settings).await?;
1343 let (device, queue) = Self::request_device(&adapter).await?;
1344 let surface = instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas))?;
1345 Ok(Self::finish_init(
1346 &adapter, device, queue, surface, size, settings,
1347 ))
1348 }
1349
1350 async fn request_adapter(
1356 instance: &wgpu::Instance,
1357 compatible_surface: Option<&wgpu::Surface<'static>>,
1358 settings: GpuRendererSettings,
1359 ) -> Result<wgpu::Adapter, GpuInitError> {
1360 let power_preference = match settings.power_preference {
1361 PowerPreference::Low => wgpu::PowerPreference::LowPower,
1362 PowerPreference::High => wgpu::PowerPreference::HighPerformance,
1363 };
1364 instance
1365 .request_adapter(&wgpu::RequestAdapterOptions {
1366 power_preference,
1367 compatible_surface,
1368 force_fallback_adapter: false,
1369 })
1370 .await
1371 .map_err(|_| GpuInitError::NoAdapter)
1372 }
1373
1374 async fn request_device(
1381 adapter: &wgpu::Adapter,
1382 ) -> Result<(wgpu::Device, wgpu::Queue), GpuInitError> {
1383 Ok(adapter
1384 .request_device(&wgpu::DeviceDescriptor {
1385 label: Some("roxlap-gpu device"),
1386 required_features: wgpu::Features::empty(),
1387 required_limits: pick_required_limits(&adapter.limits()),
1388 experimental_features: wgpu::ExperimentalFeatures::disabled(),
1389 memory_hints: wgpu::MemoryHints::default(),
1390 trace: wgpu::Trace::Off,
1391 })
1392 .await?)
1393 }
1394
1395 fn finish_init(
1400 adapter: &wgpu::Adapter,
1401 device: wgpu::Device,
1402 queue: wgpu::Queue,
1403 surface: wgpu::Surface<'static>,
1404 size: (u32, u32),
1405 settings: GpuRendererSettings,
1406 ) -> Self {
1407 let info = adapter.get_info();
1408 let adapter_info = format!(
1409 "{name} ({backend:?}, {device_type:?})",
1410 name = info.name,
1411 backend = info.backend,
1412 device_type = info.device_type,
1413 );
1414
1415 let caps = surface.get_capabilities(adapter);
1416 let surface_format = caps
1428 .formats
1429 .iter()
1430 .copied()
1431 .find(|f| {
1432 matches!(
1433 f,
1434 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Rgba8Unorm
1435 )
1436 })
1437 .or_else(|| caps.formats.iter().copied().find(|f| !f.is_srgb()))
1438 .unwrap_or(caps.formats[0]);
1439 let present_mode = if settings.uncapped_present {
1440 pick_present_mode(&caps.present_modes)
1441 } else {
1442 wgpu::PresentMode::Fifo
1443 };
1444 eprintln!(
1449 "roxlap-gpu: present mode = {present_mode:?} (available: {:?})",
1450 caps.present_modes,
1451 );
1452 let (init_w, init_h) = size;
1453 let surface_config = wgpu::SurfaceConfiguration {
1454 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1455 format: surface_format,
1456 width: init_w.max(1),
1457 height: init_h.max(1),
1458 present_mode,
1459 alpha_mode: caps.alpha_modes[0],
1460 view_formats: vec![],
1461 desired_maximum_frame_latency: 2,
1462 };
1463 surface.configure(&device, &surface_config);
1464
1465 let default_sky_pixel = [0x80u8, 0x80, 0x80, 0xff];
1470 let (sky_texture, sky_view) = create_sky_texture(&device, 1, 1, &default_sky_pixel);
1471 queue.write_texture(
1472 wgpu::TexelCopyTextureInfo {
1473 texture: &sky_texture,
1474 mip_level: 0,
1475 origin: wgpu::Origin3d::ZERO,
1476 aspect: wgpu::TextureAspect::All,
1477 },
1478 &default_sky_pixel,
1479 wgpu::TexelCopyBufferLayout {
1480 offset: 0,
1481 bytes_per_row: Some(4),
1482 rows_per_image: Some(1),
1483 },
1484 wgpu::Extent3d {
1485 width: 1,
1486 height: 1,
1487 depth_or_array_layers: 1,
1488 },
1489 );
1490 let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1491 label: Some("roxlap-gpu sky_sampler"),
1492 address_mode_u: wgpu::AddressMode::Repeat,
1496 address_mode_v: wgpu::AddressMode::Repeat,
1497 address_mode_w: wgpu::AddressMode::ClampToEdge,
1498 mag_filter: wgpu::FilterMode::Linear,
1499 min_filter: wgpu::FilterMode::Linear,
1500 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
1501 ..Default::default()
1502 });
1503
1504 let sprite_shadows_capable = device.limits().max_storage_buffers_per_shader_stage
1508 >= SPRITE_SHADOW_MIN_STORAGE_BUFFERS;
1509
1510 Self {
1511 surface,
1512 surface_config,
1513 device,
1514 queue,
1515 adapter_info,
1516 clear_colour: settings.clear_colour,
1517 frame_count: 0,
1518 flip_x: false,
1519 render_res: RenderResolution::Native,
1520 ssaa: 1,
1521 posterize: None,
1522 chunk_dda: None,
1523 grid_dda: None,
1524 scene_dda: None,
1525 scene_materials: Box::new(
1526 [MaterialGpu {
1527 alpha: 1.0,
1528 mode: 0,
1529 }; 256],
1530 ),
1531 scene_terrain_map: Vec::new(),
1532 scene_terrain_translucent: false,
1533 scene_depth_valid: false,
1534 sky_texture,
1535 sky_view,
1536 sky_sampler,
1537 fog_color: [0.66, 0.74, 0.88],
1542 fog_near: 0.0,
1543 fog_far: 1.0e30,
1544 sprite_registry: None,
1545 sprite_model_dda: None,
1546 sprite_shadows_capable,
1547 sprite_materials: Box::new(
1548 [MaterialGpu {
1549 alpha: 1.0,
1550 mode: 0,
1551 }; 256],
1552 ),
1553 sprite_has_translucent: false,
1554 sprite_lod_px: 4.0,
1558 scene_mip_scan_dist: 64.0,
1560 scene_side_shades: [[0; 4]; 2],
1561 scene_lights: SceneLights::default(),
1562 last_fov_y_rad: 0.0,
1563 pending_frame: None,
1564 line_resources: None,
1565 line_vbuf: None,
1566 line_vbuf_cap: 0,
1567 image_resources: None,
1568 image_vbuf: None,
1569 image_vbuf_cap: 0,
1570 images: Vec::new(),
1571 #[cfg(feature = "hud")]
1572 egui_renderer: None,
1573 }
1574 }
1575
1576 #[cfg(not(target_arch = "wasm32"))]
1582 pub fn new_blocking<W>(
1583 window: Arc<W>,
1584 size: (u32, u32),
1585 settings: GpuRendererSettings,
1586 ) -> Result<Self, GpuInitError>
1587 where
1588 W: HasWindowHandle + HasDisplayHandle + Send + Sync + 'static,
1589 {
1590 pollster::block_on(Self::new(window, size, settings))
1591 }
1592
1593 pub fn adapter_info(&self) -> &str {
1596 &self.adapter_info
1597 }
1598
1599 pub fn device(&self) -> &wgpu::Device {
1602 &self.device
1603 }
1604
1605 #[must_use]
1610 pub fn sprite_shadows_capable(&self) -> bool {
1611 self.sprite_shadows_capable
1612 }
1613
1614 pub fn queue(&self) -> &wgpu::Queue {
1617 &self.queue
1618 }
1619
1620 pub fn set_flip_x(&mut self, flip: bool) {
1630 self.flip_x = flip;
1631 }
1632
1633 pub fn set_sky_panorama(&mut self, rgba: &[u8], width: u32, height: u32) {
1637 assert_eq!(
1638 rgba.len(),
1639 (width as usize) * (height as usize) * 4,
1640 "set_sky_panorama: expected w*h*4 bytes, got {}",
1641 rgba.len(),
1642 );
1643 let (tex, view) = create_sky_texture(&self.device, width, height, rgba);
1644 self.queue.write_texture(
1647 wgpu::TexelCopyTextureInfo {
1648 texture: &tex,
1649 mip_level: 0,
1650 origin: wgpu::Origin3d::ZERO,
1651 aspect: wgpu::TextureAspect::All,
1652 },
1653 rgba,
1654 wgpu::TexelCopyBufferLayout {
1655 offset: 0,
1656 bytes_per_row: Some(width * 4),
1657 rows_per_image: Some(height),
1658 },
1659 wgpu::Extent3d {
1660 width,
1661 height,
1662 depth_or_array_layers: 1,
1663 },
1664 );
1665 self.sky_texture = tex;
1666 self.sky_view = view;
1667 }
1668
1669 pub fn set_fog(&mut self, color: [f32; 3], near: f32, far: f32) {
1675 self.fog_color = color;
1676 self.fog_near = near;
1677 self.fog_far = far.max(near + 1.0);
1678 }
1679
1680 pub fn resize(&mut self, width: u32, height: u32) {
1684 if width == 0 || height == 0 {
1685 return;
1686 }
1687 self.surface_config.width = width;
1688 self.surface_config.height = height;
1689 self.surface.configure(&self.device, &self.surface_config);
1690 self.chunk_dda = None;
1691 self.grid_dda = None;
1692 self.scene_dda = None;
1693 }
1694
1695 pub fn set_render_resolution(&mut self, res: RenderResolution) {
1699 self.render_res = res;
1700 }
1701
1702 pub fn set_ssaa(&mut self, factor: u8) {
1704 self.ssaa = u32::from(factor).clamp(1, 4);
1705 }
1706
1707 pub fn set_posterize(&mut self, cfg: Option<PosterizeGpu>) {
1710 self.posterize = cfg;
1711 }
1712
1713 #[must_use]
1717 pub fn logical_dims(&self) -> (u32, u32) {
1718 self.render_res.logical_for(self.surface_dims())
1719 }
1720
1721 #[must_use]
1724 pub fn render_dims(&self) -> (u32, u32) {
1725 let (lw, lh) = self.logical_dims();
1726 (lw * self.ssaa, lh * self.ssaa)
1727 }
1728
1729 #[must_use]
1731 pub fn surface_dims(&self) -> (u32, u32) {
1732 (self.surface_config.width, self.surface_config.height)
1733 }
1734
1735 fn acquire_frame(&self) -> Option<wgpu::SurfaceTexture> {
1741 use wgpu::CurrentSurfaceTexture as C;
1742 match self.surface.get_current_texture() {
1743 C::Success(t) | C::Suboptimal(t) => Some(t),
1744 C::Outdated | C::Lost => {
1745 self.surface.configure(&self.device, &self.surface_config);
1746 None
1747 }
1748 C::Timeout | C::Occluded | C::Validation => None,
1749 }
1750 }
1751
1752 pub fn render(&mut self) {
1756 let Some(surf_tex) = self.acquire_frame() else {
1757 return;
1758 };
1759 let view = surf_tex
1760 .texture
1761 .create_view(&wgpu::TextureViewDescriptor::default());
1762
1763 let phase = f64::from(self.frame_count % 1257) * 0.005;
1767 let [r, g, b] = self.clear_colour;
1768 let drift = (phase.sin() * 0.04 + 0.04).clamp(0.0, 0.1);
1769 let clear = wgpu::Color {
1770 r: (r + drift).clamp(0.0, 1.0),
1771 g: (g + drift * 0.5).clamp(0.0, 1.0),
1772 b: (b + drift * 0.25).clamp(0.0, 1.0),
1773 a: 1.0,
1774 };
1775
1776 let mut encoder = self
1777 .device
1778 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1779 label: Some("roxlap-gpu encoder"),
1780 });
1781 {
1782 let _rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1783 label: Some("roxlap-gpu clear"),
1784 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1785 view: &view,
1786 depth_slice: None,
1787 resolve_target: None,
1788 ops: wgpu::Operations {
1789 load: wgpu::LoadOp::Clear(clear),
1790 store: wgpu::StoreOp::Store,
1791 },
1792 })],
1793 depth_stencil_attachment: None,
1794 timestamp_writes: None,
1795 occlusion_query_set: None,
1796 multiview_mask: None,
1797 });
1798 }
1799 self.queue.submit(std::iter::once(encoder.finish()));
1800 surf_tex.present();
1801 self.frame_count = self.frame_count.wrapping_add(1);
1802 }
1803
1804 pub fn render_chunk(
1816 &mut self,
1817 resident: &GpuChunkResident,
1818 camera: &Camera,
1819 max_scan_dist: u32,
1820 ) {
1821 let Some(surf_tex) = self.acquire_frame() else {
1822 return;
1823 };
1824 let surf_view = surf_tex
1825 .texture
1826 .create_view(&wgpu::TextureViewDescriptor::default());
1827
1828 let surface_w = self.surface_config.width;
1829 let surface_h = self.surface_config.height;
1830 let surface_format = self.surface_config.format;
1831
1832 let needs_build = match &self.chunk_dda {
1835 Some(r) => r.storage_size != (surface_w, surface_h),
1836 None => true,
1837 };
1838 if needs_build {
1839 self.chunk_dda = Some(self.build_chunk_dda(surface_w, surface_h, surface_format));
1840 }
1841 let dda = self.chunk_dda.as_ref().expect("just built");
1842
1843 let uniform = ChunkDdaUniform {
1845 camera_pos: camera.position,
1846 _pad0: 0.0,
1847 camera_right: camera.right,
1848 _pad1: 0.0,
1849 camera_down: camera.down,
1850 _pad2: 0.0,
1851 camera_forward: camera.forward,
1852 fov_y_rad: camera.fov_y_rad,
1853 screen_size: [surface_w, surface_h],
1854 vsid: resident.vsid,
1855 max_scan_dist,
1856 };
1857 self.queue
1858 .write_buffer(&dda.uniform_buf, 0, bytemuck::bytes_of(&uniform));
1859
1860 let dda_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1864 label: Some("roxlap-gpu chunk_dda.bg"),
1865 layout: &dda.bgl_dda,
1866 entries: &[
1867 wgpu::BindGroupEntry {
1868 binding: 0,
1869 resource: dda.uniform_buf.as_entire_binding(),
1870 },
1871 wgpu::BindGroupEntry {
1872 binding: 1,
1873 resource: resident.occupancy.as_entire_binding(),
1874 },
1875 wgpu::BindGroupEntry {
1876 binding: 2,
1877 resource: resident.color_offsets.as_entire_binding(),
1878 },
1879 wgpu::BindGroupEntry {
1880 binding: 3,
1881 resource: resident.colors.as_entire_binding(),
1882 },
1883 wgpu::BindGroupEntry {
1884 binding: 4,
1885 resource: wgpu::BindingResource::TextureView(&dda.storage_view),
1886 },
1887 ],
1888 });
1889
1890 let mut encoder = self
1891 .device
1892 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1893 label: Some("roxlap-gpu chunk encoder"),
1894 });
1895 {
1896 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1897 label: Some("roxlap-gpu chunk_dda compute"),
1898 timestamp_writes: None,
1899 });
1900 cpass.set_pipeline(&dda.pipeline_dda);
1901 cpass.set_bind_group(0, &dda_bg, &[]);
1902 cpass.dispatch_workgroups(surface_w.div_ceil(8), surface_h.div_ceil(8), 1);
1903 }
1904 {
1905 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1906 label: Some("roxlap-gpu chunk_dda blit"),
1907 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1908 view: &surf_view,
1909 depth_slice: None,
1910 resolve_target: None,
1911 ops: wgpu::Operations {
1912 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1913 store: wgpu::StoreOp::Store,
1914 },
1915 })],
1916 depth_stencil_attachment: None,
1917 timestamp_writes: None,
1918 occlusion_query_set: None,
1919 multiview_mask: None,
1920 });
1921 rpass.set_pipeline(&dda.pipeline_blit);
1922 rpass.set_bind_group(0, &dda.blit_bg, &[]);
1923 rpass.draw(0..3, 0..1);
1924 }
1925 self.queue.submit(std::iter::once(encoder.finish()));
1926 surf_tex.present();
1927 self.frame_count = self.frame_count.wrapping_add(1);
1928 }
1929
1930 fn build_chunk_dda(
1931 &self,
1932 width: u32,
1933 height: u32,
1934 surface_format: wgpu::TextureFormat,
1935 ) -> ChunkDdaResources {
1936 let storage_tex = self.device.create_texture(&wgpu::TextureDescriptor {
1937 label: Some("roxlap-gpu chunk_dda.storage"),
1938 size: wgpu::Extent3d {
1939 width,
1940 height,
1941 depth_or_array_layers: 1,
1942 },
1943 mip_level_count: 1,
1944 sample_count: 1,
1945 dimension: wgpu::TextureDimension::D2,
1946 format: wgpu::TextureFormat::Rgba8Unorm,
1947 usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
1948 view_formats: &[],
1949 });
1950 let storage_view = storage_tex.create_view(&wgpu::TextureViewDescriptor::default());
1951
1952 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
1953 label: Some("roxlap-gpu chunk_dda.uniform"),
1954 size: std::mem::size_of::<ChunkDdaUniform>() as u64,
1955 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1956 mapped_at_creation: false,
1957 });
1958
1959 let dda_shader = self
1960 .device
1961 .create_shader_module(wgpu::ShaderModuleDescriptor {
1962 label: Some("chunk_dda.wgsl"),
1963 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/chunk_dda.wgsl").into()),
1964 });
1965 let bgl_dda = self
1966 .device
1967 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1968 label: Some("roxlap-gpu chunk_dda.bgl"),
1969 entries: &[
1970 bgl_uniform_entry(0),
1971 bgl_storage_entry(1, true),
1972 bgl_storage_entry(2, true),
1973 bgl_storage_entry(3, true),
1974 wgpu::BindGroupLayoutEntry {
1975 binding: 4,
1976 visibility: wgpu::ShaderStages::COMPUTE,
1977 ty: wgpu::BindingType::StorageTexture {
1978 access: wgpu::StorageTextureAccess::WriteOnly,
1979 format: wgpu::TextureFormat::Rgba8Unorm,
1980 view_dimension: wgpu::TextureViewDimension::D2,
1981 },
1982 count: None,
1983 },
1984 ],
1985 });
1986 let dda_pl = self
1987 .device
1988 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1989 label: Some("roxlap-gpu chunk_dda.layout"),
1990 bind_group_layouts: &[Some(&bgl_dda)],
1991 immediate_size: 0,
1992 });
1993 let pipeline_dda = self
1994 .device
1995 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1996 label: Some("roxlap-gpu chunk_dda.pipeline"),
1997 layout: Some(&dda_pl),
1998 module: &dda_shader,
1999 entry_point: Some("render_chunk"),
2000 compilation_options: wgpu::PipelineCompilationOptions::default(),
2001 cache: None,
2002 });
2003
2004 let blit_shader = self
2007 .device
2008 .create_shader_module(wgpu::ShaderModuleDescriptor {
2009 label: Some("blit.wgsl"),
2010 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/blit.wgsl").into()),
2011 });
2012 let bgl_blit = self
2013 .device
2014 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2015 label: Some("roxlap-gpu chunk_dda.blit_bgl"),
2016 entries: &[
2017 wgpu::BindGroupLayoutEntry {
2018 binding: 0,
2019 visibility: wgpu::ShaderStages::FRAGMENT,
2020 ty: wgpu::BindingType::Texture {
2021 sample_type: wgpu::TextureSampleType::Float { filterable: false },
2022 view_dimension: wgpu::TextureViewDimension::D2,
2023 multisampled: false,
2024 },
2025 count: None,
2026 },
2027 wgpu::BindGroupLayoutEntry {
2028 binding: 1,
2029 visibility: wgpu::ShaderStages::FRAGMENT,
2030 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
2031 count: None,
2032 },
2033 ],
2034 });
2035 let blit_pl = self
2036 .device
2037 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2038 label: Some("roxlap-gpu chunk_dda.blit_layout"),
2039 bind_group_layouts: &[Some(&bgl_blit)],
2040 immediate_size: 0,
2041 });
2042 let pipeline_blit = self
2043 .device
2044 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
2045 label: Some("roxlap-gpu chunk_dda.blit_pipeline"),
2046 layout: Some(&blit_pl),
2047 vertex: wgpu::VertexState {
2048 module: &blit_shader,
2049 entry_point: Some("vs_main"),
2050 compilation_options: wgpu::PipelineCompilationOptions::default(),
2051 buffers: &[],
2052 },
2053 fragment: Some(wgpu::FragmentState {
2054 module: &blit_shader,
2055 entry_point: Some("fs_main"),
2056 compilation_options: wgpu::PipelineCompilationOptions::default(),
2057 targets: &[Some(wgpu::ColorTargetState {
2058 format: surface_format,
2059 blend: None,
2060 write_mask: wgpu::ColorWrites::ALL,
2061 })],
2062 }),
2063 primitive: wgpu::PrimitiveState::default(),
2064 depth_stencil: None,
2065 multisample: wgpu::MultisampleState::default(),
2066 multiview_mask: None,
2067 cache: None,
2068 });
2069 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
2070 label: Some("roxlap-gpu chunk_dda.blit_sampler"),
2071 address_mode_u: wgpu::AddressMode::ClampToEdge,
2072 address_mode_v: wgpu::AddressMode::ClampToEdge,
2073 address_mode_w: wgpu::AddressMode::ClampToEdge,
2074 mag_filter: wgpu::FilterMode::Nearest,
2075 min_filter: wgpu::FilterMode::Nearest,
2076 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
2077 ..Default::default()
2078 });
2079 let blit_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2080 label: Some("roxlap-gpu chunk_dda.blit_bg"),
2081 layout: &bgl_blit,
2082 entries: &[
2083 wgpu::BindGroupEntry {
2084 binding: 0,
2085 resource: wgpu::BindingResource::TextureView(&storage_view),
2086 },
2087 wgpu::BindGroupEntry {
2088 binding: 1,
2089 resource: wgpu::BindingResource::Sampler(&sampler),
2090 },
2091 ],
2092 });
2093
2094 ChunkDdaResources {
2095 storage_size: (width, height),
2096 storage_view,
2097 uniform_buf,
2098 bgl_dda,
2099 pipeline_dda,
2100 blit_bg,
2101 pipeline_blit,
2102 _sampler: sampler,
2103 }
2104 }
2105
2106 pub fn render_grid(&mut self, grid: &GpuGridResident, camera: &Camera, max_outer_steps: u32) {
2116 let Some(surf_tex) = self.acquire_frame() else {
2117 return;
2118 };
2119 let surf_view = surf_tex
2120 .texture
2121 .create_view(&wgpu::TextureViewDescriptor::default());
2122
2123 let surface_w = self.surface_config.width;
2124 let surface_h = self.surface_config.height;
2125 let surface_format = self.surface_config.format;
2126
2127 let needs_build = match &self.grid_dda {
2128 Some(r) => r.storage_size != (surface_w, surface_h),
2129 None => true,
2130 };
2131 if needs_build {
2132 self.grid_dda = Some(self.build_grid_dda(surface_w, surface_h, surface_format));
2133 }
2134 let dda = self.grid_dda.as_ref().expect("just built");
2135
2136 let uniform = GridDdaUniform {
2137 camera_pos: camera.position,
2138 _pad0: 0.0,
2139 camera_right: camera.right,
2140 _pad1: 0.0,
2141 camera_down: camera.down,
2142 _pad2: 0.0,
2143 camera_forward: camera.forward,
2144 fov_y_rad: camera.fov_y_rad,
2145 screen_size: [surface_w, surface_h],
2146 vsid: grid.vsid,
2147 max_outer_steps,
2148 chunks_dims: grid.chunks_dims,
2149 _pad3: 0,
2150 origin_chunk: grid.origin_chunk,
2151 _pad4: 0,
2152 };
2153 self.queue
2154 .write_buffer(&dda.uniform_buf, 0, bytemuck::bytes_of(&uniform));
2155
2156 let dda_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2157 label: Some("roxlap-gpu grid_dda.bg"),
2158 layout: &dda.bgl_dda,
2159 entries: &[
2160 wgpu::BindGroupEntry {
2161 binding: 0,
2162 resource: dda.uniform_buf.as_entire_binding(),
2163 },
2164 wgpu::BindGroupEntry {
2165 binding: 1,
2166 resource: grid.occupancy.as_entire_binding(),
2167 },
2168 wgpu::BindGroupEntry {
2169 binding: 2,
2170 resource: grid.color_offsets.as_entire_binding(),
2171 },
2172 wgpu::BindGroupEntry {
2173 binding: 3,
2174 resource: grid.colors.as_entire_binding(),
2175 },
2176 wgpu::BindGroupEntry {
2177 binding: 4,
2178 resource: grid.chunk_colors_base.as_entire_binding(),
2179 },
2180 wgpu::BindGroupEntry {
2181 binding: 5,
2182 resource: grid.chunk_occupancy.as_entire_binding(),
2183 },
2184 wgpu::BindGroupEntry {
2185 binding: 6,
2186 resource: wgpu::BindingResource::TextureView(&dda.storage_view),
2187 },
2188 ],
2189 });
2190
2191 let mut encoder = self
2192 .device
2193 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2194 label: Some("roxlap-gpu grid encoder"),
2195 });
2196 {
2197 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
2198 label: Some("roxlap-gpu grid_dda compute"),
2199 timestamp_writes: None,
2200 });
2201 cpass.set_pipeline(&dda.pipeline_dda);
2202 cpass.set_bind_group(0, &dda_bg, &[]);
2203 cpass.dispatch_workgroups(surface_w.div_ceil(8), surface_h.div_ceil(8), 1);
2204 }
2205 {
2206 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2207 label: Some("roxlap-gpu grid_dda blit"),
2208 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2209 view: &surf_view,
2210 depth_slice: None,
2211 resolve_target: None,
2212 ops: wgpu::Operations {
2213 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
2214 store: wgpu::StoreOp::Store,
2215 },
2216 })],
2217 depth_stencil_attachment: None,
2218 timestamp_writes: None,
2219 occlusion_query_set: None,
2220 multiview_mask: None,
2221 });
2222 rpass.set_pipeline(&dda.pipeline_blit);
2223 rpass.set_bind_group(0, &dda.blit_bg, &[]);
2224 rpass.draw(0..3, 0..1);
2225 }
2226 self.queue.submit(std::iter::once(encoder.finish()));
2227 surf_tex.present();
2228 self.frame_count = self.frame_count.wrapping_add(1);
2229 }
2230
2231 fn build_grid_dda(
2232 &self,
2233 width: u32,
2234 height: u32,
2235 surface_format: wgpu::TextureFormat,
2236 ) -> GridDdaResources {
2237 let storage_tex = self.device.create_texture(&wgpu::TextureDescriptor {
2238 label: Some("roxlap-gpu grid_dda.storage"),
2239 size: wgpu::Extent3d {
2240 width,
2241 height,
2242 depth_or_array_layers: 1,
2243 },
2244 mip_level_count: 1,
2245 sample_count: 1,
2246 dimension: wgpu::TextureDimension::D2,
2247 format: wgpu::TextureFormat::Rgba8Unorm,
2248 usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
2249 view_formats: &[],
2250 });
2251 let storage_view = storage_tex.create_view(&wgpu::TextureViewDescriptor::default());
2252
2253 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
2254 label: Some("roxlap-gpu grid_dda.uniform"),
2255 size: std::mem::size_of::<GridDdaUniform>() as u64,
2256 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2257 mapped_at_creation: false,
2258 });
2259
2260 let dda_shader = self
2261 .device
2262 .create_shader_module(wgpu::ShaderModuleDescriptor {
2263 label: Some("grid_dda.wgsl"),
2264 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/grid_dda.wgsl").into()),
2265 });
2266 let bgl_dda = self
2267 .device
2268 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2269 label: Some("roxlap-gpu grid_dda.bgl"),
2270 entries: &[
2271 bgl_uniform_entry(0),
2272 bgl_storage_entry(1, true),
2273 bgl_storage_entry(2, true),
2274 bgl_storage_entry(3, true),
2275 bgl_storage_entry(4, true),
2276 bgl_storage_entry(5, true),
2277 wgpu::BindGroupLayoutEntry {
2278 binding: 6,
2279 visibility: wgpu::ShaderStages::COMPUTE,
2280 ty: wgpu::BindingType::StorageTexture {
2281 access: wgpu::StorageTextureAccess::WriteOnly,
2282 format: wgpu::TextureFormat::Rgba8Unorm,
2283 view_dimension: wgpu::TextureViewDimension::D2,
2284 },
2285 count: None,
2286 },
2287 ],
2288 });
2289 let dda_pl = self
2290 .device
2291 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2292 label: Some("roxlap-gpu grid_dda.layout"),
2293 bind_group_layouts: &[Some(&bgl_dda)],
2294 immediate_size: 0,
2295 });
2296 let pipeline_dda = self
2297 .device
2298 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
2299 label: Some("roxlap-gpu grid_dda.pipeline"),
2300 layout: Some(&dda_pl),
2301 module: &dda_shader,
2302 entry_point: Some("render_grid"),
2303 compilation_options: wgpu::PipelineCompilationOptions::default(),
2304 cache: None,
2305 });
2306
2307 let blit_shader = self
2308 .device
2309 .create_shader_module(wgpu::ShaderModuleDescriptor {
2310 label: Some("blit.wgsl"),
2311 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/blit.wgsl").into()),
2312 });
2313 let bgl_blit = self
2314 .device
2315 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2316 label: Some("roxlap-gpu grid_dda.blit_bgl"),
2317 entries: &[
2318 wgpu::BindGroupLayoutEntry {
2319 binding: 0,
2320 visibility: wgpu::ShaderStages::FRAGMENT,
2321 ty: wgpu::BindingType::Texture {
2322 sample_type: wgpu::TextureSampleType::Float { filterable: false },
2323 view_dimension: wgpu::TextureViewDimension::D2,
2324 multisampled: false,
2325 },
2326 count: None,
2327 },
2328 wgpu::BindGroupLayoutEntry {
2329 binding: 1,
2330 visibility: wgpu::ShaderStages::FRAGMENT,
2331 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
2332 count: None,
2333 },
2334 ],
2335 });
2336 let blit_pl = self
2337 .device
2338 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2339 label: Some("roxlap-gpu grid_dda.blit_layout"),
2340 bind_group_layouts: &[Some(&bgl_blit)],
2341 immediate_size: 0,
2342 });
2343 let pipeline_blit = self
2344 .device
2345 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
2346 label: Some("roxlap-gpu grid_dda.blit_pipeline"),
2347 layout: Some(&blit_pl),
2348 vertex: wgpu::VertexState {
2349 module: &blit_shader,
2350 entry_point: Some("vs_main"),
2351 compilation_options: wgpu::PipelineCompilationOptions::default(),
2352 buffers: &[],
2353 },
2354 fragment: Some(wgpu::FragmentState {
2355 module: &blit_shader,
2356 entry_point: Some("fs_main"),
2357 compilation_options: wgpu::PipelineCompilationOptions::default(),
2358 targets: &[Some(wgpu::ColorTargetState {
2359 format: surface_format,
2360 blend: None,
2361 write_mask: wgpu::ColorWrites::ALL,
2362 })],
2363 }),
2364 primitive: wgpu::PrimitiveState::default(),
2365 depth_stencil: None,
2366 multisample: wgpu::MultisampleState::default(),
2367 multiview_mask: None,
2368 cache: None,
2369 });
2370 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
2371 label: Some("roxlap-gpu grid_dda.blit_sampler"),
2372 address_mode_u: wgpu::AddressMode::ClampToEdge,
2373 address_mode_v: wgpu::AddressMode::ClampToEdge,
2374 address_mode_w: wgpu::AddressMode::ClampToEdge,
2375 mag_filter: wgpu::FilterMode::Nearest,
2376 min_filter: wgpu::FilterMode::Nearest,
2377 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
2378 ..Default::default()
2379 });
2380 let blit_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2381 label: Some("roxlap-gpu grid_dda.blit_bg"),
2382 layout: &bgl_blit,
2383 entries: &[
2384 wgpu::BindGroupEntry {
2385 binding: 0,
2386 resource: wgpu::BindingResource::TextureView(&storage_view),
2387 },
2388 wgpu::BindGroupEntry {
2389 binding: 1,
2390 resource: wgpu::BindingResource::Sampler(&sampler),
2391 },
2392 ],
2393 });
2394
2395 GridDdaResources {
2396 storage_size: (width, height),
2397 storage_view,
2398 uniform_buf,
2399 bgl_dda,
2400 pipeline_dda,
2401 blit_bg,
2402 pipeline_blit,
2403 _sampler: sampler,
2404 }
2405 }
2406
2407 pub fn render_scene(
2423 &mut self,
2424 scene: &GpuSceneResident,
2425 cameras: &[Camera],
2426 grid_world: &[GridWorldTransform],
2429 sprite_camera: &Camera,
2430 fov_y_rad: f32,
2431 max_outer_steps: u32,
2432 ) {
2433 assert_eq!(
2434 cameras.len(),
2435 scene.grid_count as usize,
2436 "render_scene: {} cameras supplied, scene has {} grids",
2437 cameras.len(),
2438 scene.grid_count,
2439 );
2440 self.last_fov_y_rad = fov_y_rad; self.pending_frame = None;
2446 let Some(surf_tex) = self.acquire_frame() else {
2447 return;
2448 };
2449 let surf_view = surf_tex
2450 .texture
2451 .create_view(&wgpu::TextureViewDescriptor::default());
2452
2453 let surface_w = self.surface_config.width;
2454 let surface_h = self.surface_config.height;
2455 let surface_format = self.surface_config.format;
2456 let (logical_w, logical_h) = self.logical_dims();
2462 let (render_w, render_h) = self.render_dims();
2463
2464 let needs_build = match &self.scene_dda {
2465 Some(r) => {
2466 r.storage_size != (render_w, render_h) || r.logical_size != (logical_w, logical_h)
2467 }
2468 None => true,
2469 };
2470 if needs_build {
2471 self.scene_dda = Some(self.build_scene_dda(
2472 render_w,
2473 render_h,
2474 logical_w,
2475 logical_h,
2476 surface_w,
2477 surface_h,
2478 surface_format,
2479 ));
2480 }
2481 if self.sprite_registry.is_some() && self.sprite_model_dda.is_none() {
2486 self.sprite_model_dda = Some(self.build_sprite_model_dda());
2487 }
2488 let sprite_pass: Option<(u32, u32)> = if let Some(reg) = self.sprite_registry.as_mut() {
2493 if reg.instance_capacity > 0 {
2494 let cam = sprite_camera;
2497 #[allow(clippy::cast_precision_loss)]
2500 let aspect = render_w as f32 / render_h as f32;
2501 let half_h = (fov_y_rad * 0.5).tan();
2502 let frustum = sprite_model::ViewFrustum {
2503 pos: cam.position,
2504 right: cam.right,
2505 down: cam.down,
2506 forward: cam.forward,
2507 half_w: half_h * aspect,
2508 half_h,
2509 far: 1.0e9,
2510 };
2511 let (visible, tiles_x, _tiles_y) = reg.cull_bin_upload(
2512 &self.device,
2513 &self.queue,
2514 &frustum,
2515 render_w,
2516 render_h,
2517 SPRITE_TILE_SIZE,
2518 self.sprite_lod_px,
2519 );
2520 (visible > 0).then_some((visible, tiles_x))
2521 } else {
2522 None
2523 }
2524 } else {
2525 None
2526 };
2527 let dda = self.scene_dda.as_ref().expect("just built");
2528
2529 self.queue.write_buffer(
2534 &dda.blit_dims,
2535 16,
2536 bytemuck::bytes_of(&[u32::from(self.flip_x), 0u32]),
2537 );
2538 let (plevels, pdither) = match self.posterize {
2542 Some(p) => (p.levels, p.dither),
2543 None => ([1u32; 3], 0u32),
2544 };
2545 self.queue.write_buffer(
2546 &dda.resolve_dims,
2547 20,
2548 bytemuck::bytes_of(&[plevels[0], plevels[1], plevels[2], pdither]),
2549 );
2550
2551 let mut cam_vec: Vec<SceneDdaPerGridCamera> = cameras
2554 .iter()
2555 .map(SceneDdaPerGridCamera::from_camera)
2556 .collect();
2557 for (c, t) in cam_vec.iter_mut().zip(grid_world.iter()) {
2559 c.set_world_transform(t);
2560 }
2561
2562 let lights = self.scene_lights.clone();
2567 let (grid_point_lights, sun_flags, point_count) = pack_scene_lights(
2568 &self.device,
2569 &lights,
2570 scene.grid_count as usize,
2571 &mut cam_vec,
2572 );
2573 let grid_cameras = upload_grid_cameras(&self.device, &cam_vec);
2574
2575 let uniform = SceneDdaUniform {
2576 fov_y_rad,
2577 grid_count: scene.grid_count,
2578 max_outer_steps,
2579 _pad0: 0,
2580 screen_size: [render_w, render_h],
2581 _pad1: [0; 2],
2582 fog_color: [
2583 self.fog_color[0],
2584 self.fog_color[1],
2585 self.fog_color[2],
2586 self.fog_near,
2587 ],
2588 fog_far: self.fog_far,
2589 write_depth: 1,
2593 occ_page_words: scene.occupancy_page_words,
2594 occ_num_pages: scene.occupancy_num_pages,
2595 mip_scan_dist: self.scene_mip_scan_dist,
2596 terrain_has_translucent: u32::from(self.scene_terrain_translucent),
2597 terrain_map_count: self.scene_terrain_map.len() as u32,
2598 _pad4: 0,
2599 sky_cam: SceneDdaPerGridCamera::from_camera(sprite_camera),
2602 side_shades0: self.scene_side_shades[0],
2603 side_shades1: self.scene_side_shades[1],
2604 sun_color: [
2605 lights.sun_color[0],
2606 lights.sun_color[1],
2607 lights.sun_color[2],
2608 lights.sun_intensity,
2609 ],
2610 ambient_color: [
2611 lights.ambient[0],
2612 lights.ambient[1],
2613 lights.ambient[2],
2614 lights.shadow_strength,
2615 ],
2616 sun_flags,
2617 point_light_count: point_count,
2618 shadow_max_steps: lights.shadow_max_steps,
2619 _pad5: 0,
2620 shadow_bias: lights.shadow_bias,
2621 shadow_max_dist: lights.shadow_max_dist,
2622 _pad6: [0.0; 2],
2623 shadow_tint: [
2624 lights.shadow_tint[0],
2625 lights.shadow_tint[1],
2626 lights.shadow_tint[2],
2627 0.0,
2628 ],
2629 style_bands: lights.style_bands,
2630 sprite_cast_count: if self.sprite_shadows_capable {
2634 sprite_pass.map_or(0, |(visible, _)| visible)
2635 } else {
2636 0
2637 },
2638 _pad7: [0; 2],
2639 };
2640 self.queue
2641 .write_buffer(&dda.uniform_buf, 0, bytemuck::bytes_of(&uniform));
2642
2643 let mut dda_entries = vec![
2644 wgpu::BindGroupEntry {
2645 binding: 0,
2646 resource: dda.uniform_buf.as_entire_binding(),
2647 },
2648 wgpu::BindGroupEntry {
2651 binding: 1,
2652 resource: scene.occupancy_pages[0].as_entire_binding(),
2653 },
2654 wgpu::BindGroupEntry {
2655 binding: 2,
2656 resource: scene.all_color_offsets.as_entire_binding(),
2657 },
2658 wgpu::BindGroupEntry {
2659 binding: 3,
2660 resource: scene.all_colors.as_entire_binding(),
2661 },
2662 wgpu::BindGroupEntry {
2663 binding: 4,
2664 resource: scene.all_chunk_colors_base.as_entire_binding(),
2665 },
2666 wgpu::BindGroupEntry {
2667 binding: 5,
2668 resource: scene.all_chunk_occupancy.as_entire_binding(),
2669 },
2670 wgpu::BindGroupEntry {
2671 binding: 6,
2672 resource: scene.grid_static_meta.as_entire_binding(),
2673 },
2674 wgpu::BindGroupEntry {
2675 binding: 7,
2676 resource: scene.all_slot_chunk_idx.as_entire_binding(),
2677 },
2678 wgpu::BindGroupEntry {
2679 binding: 8,
2680 resource: dda.framebuffer.as_entire_binding(),
2681 },
2682 wgpu::BindGroupEntry {
2683 binding: 9,
2684 resource: wgpu::BindingResource::TextureView(&self.sky_view),
2685 },
2686 wgpu::BindGroupEntry {
2687 binding: 10,
2688 resource: wgpu::BindingResource::Sampler(&self.sky_sampler),
2689 },
2690 wgpu::BindGroupEntry {
2691 binding: 11,
2692 resource: dda.depth_buffer.as_entire_binding(),
2693 },
2694 wgpu::BindGroupEntry {
2695 binding: 12,
2696 resource: scene.occupancy_pages[1].as_entire_binding(),
2697 },
2698 wgpu::BindGroupEntry {
2699 binding: 13,
2700 resource: scene.occupancy_pages[2].as_entire_binding(),
2701 },
2702 wgpu::BindGroupEntry {
2703 binding: 14,
2704 resource: scene.occupancy_pages[3].as_entire_binding(),
2705 },
2706 wgpu::BindGroupEntry {
2707 binding: 15,
2708 resource: grid_cameras.as_entire_binding(),
2709 },
2710 wgpu::BindGroupEntry {
2711 binding: 16,
2712 resource: dda.materials_pal_buf.as_entire_binding(),
2713 },
2714 wgpu::BindGroupEntry {
2715 binding: 17,
2716 resource: dda.terrain_map_buf.as_entire_binding(),
2717 },
2718 wgpu::BindGroupEntry {
2721 binding: 18,
2722 resource: grid_point_lights.as_entire_binding(),
2723 },
2724 ];
2725 if self.sprite_shadows_capable {
2729 let dummy = dda
2730 .sprite_cast_dummy
2731 .as_ref()
2732 .expect("capable scene_dda has a sprite-cast dummy");
2733 let (insts, models, occ) = match &self.sprite_registry {
2734 Some(reg) => (®.instances, ®.model_meta, ®.occupancy),
2735 None => (dummy, dummy, dummy),
2736 };
2737 dda_entries.push(wgpu::BindGroupEntry {
2738 binding: 19,
2739 resource: insts.as_entire_binding(),
2740 });
2741 dda_entries.push(wgpu::BindGroupEntry {
2742 binding: 20,
2743 resource: models.as_entire_binding(),
2744 });
2745 dda_entries.push(wgpu::BindGroupEntry {
2746 binding: 21,
2747 resource: occ.as_entire_binding(),
2748 });
2749 }
2750 let dda_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2751 label: Some("roxlap-gpu scene_dda.bg"),
2752 layout: &dda.bgl_dda,
2753 entries: &dda_entries,
2754 });
2755
2756 let sprite_model_bg = match (&self.sprite_model_dda, &self.sprite_registry, sprite_pass) {
2763 (Some(smd), Some(reg), Some((visible, tiles_x))) => {
2764 let cam = sprite_camera;
2767 let dl = &self.scene_lights;
2770 let sprite_sun_enabled = dl.world_sun_dir != [0.0; 3];
2771 let sprite_pts: Vec<GpuPointLight> = dl
2772 .world_points
2773 .iter()
2774 .take(MAX_POINT_LIGHTS)
2775 .map(|l| GpuPointLight {
2776 pos: l.position,
2777 radius: l.radius,
2778 color: l.color,
2779 intensity: l.intensity,
2780 spot_dir: l.spot_dir,
2781 cos_outer: l.cos_outer,
2782 cos_inner: l.cos_inner,
2783 casts_shadow: u32::from(l.casts_shadow),
2786 _pad: [0; 2],
2787 })
2788 .collect();
2789 let sprite_point_count = sprite_pts.len() as u32;
2790 let sprite_point_buf = upload_grid_point_lights(&self.device, &sprite_pts);
2791 let sprite_sun_flags = u32::from(sprite_sun_enabled)
2794 | (u32::from(dl.sun_casts_shadow) << 1)
2795 | (u32::from(dl.enabled) << 2);
2796 let uni = SpriteModelUniform {
2797 cam_pos: cam.position,
2798 _p0: 0.0,
2799 cam_right: cam.right,
2800 _p1: 0.0,
2801 cam_down: cam.down,
2802 _p2: 0.0,
2803 cam_forward: cam.forward,
2804 _p3: 0.0,
2805 fog_color: [
2806 self.fog_color[0],
2807 self.fog_color[1],
2808 self.fog_color[2],
2809 self.fog_near,
2810 ],
2811 screen_size: [render_w, render_h],
2812 instance_count: visible,
2813 fog_far: self.fog_far,
2814 fov_y_rad,
2815 tiles_x,
2816 tile_size: SPRITE_TILE_SIZE,
2817 has_translucent: u32::from(self.sprite_has_translucent),
2818 sun_dir: [
2819 dl.world_sun_dir[0],
2820 dl.world_sun_dir[1],
2821 dl.world_sun_dir[2],
2822 0.0,
2823 ],
2824 sun_color: [
2825 dl.sun_color[0],
2826 dl.sun_color[1],
2827 dl.sun_color[2],
2828 dl.sun_intensity,
2829 ],
2830 ambient_color: [dl.ambient[0], dl.ambient[1], dl.ambient[2], 0.0],
2831 sun_flags: sprite_sun_flags,
2832 point_light_count: sprite_point_count,
2833 _pad_dl: [0; 2],
2834 shadow_tint: [dl.shadow_tint[0], dl.shadow_tint[1], dl.shadow_tint[2], 0.0],
2835 style_bands: dl.style_bands,
2836 occ_num_pages: scene.occupancy_num_pages,
2841 occ_page_words: scene.occupancy_page_words,
2842 grid_count: scene.grid_count,
2843 max_outer_steps,
2844 shadow_max_steps: dl.shadow_max_steps,
2845 shadow_bias: dl.shadow_bias,
2846 shadow_max_dist: dl.shadow_max_dist,
2847 shadow_strength: dl.shadow_strength,
2848 _pad_xs: [0; 3],
2849 };
2850 self.queue
2851 .write_buffer(&smd.uniform_buf, 0, bytemuck::bytes_of(&uni));
2852 let mut sprite_entries = vec![
2853 wgpu::BindGroupEntry {
2854 binding: 0,
2855 resource: smd.uniform_buf.as_entire_binding(),
2856 },
2857 wgpu::BindGroupEntry {
2858 binding: 1,
2859 resource: reg.occupancy.as_entire_binding(),
2860 },
2861 wgpu::BindGroupEntry {
2862 binding: 2,
2863 resource: reg.colors.as_entire_binding(),
2864 },
2865 wgpu::BindGroupEntry {
2866 binding: 3,
2867 resource: reg.color_offsets.as_entire_binding(),
2868 },
2869 wgpu::BindGroupEntry {
2870 binding: 4,
2871 resource: reg.model_meta.as_entire_binding(),
2872 },
2873 wgpu::BindGroupEntry {
2874 binding: 5,
2875 resource: reg.instances.as_entire_binding(),
2876 },
2877 wgpu::BindGroupEntry {
2878 binding: 6,
2879 resource: dda.depth_buffer.as_entire_binding(),
2880 },
2881 wgpu::BindGroupEntry {
2882 binding: 7,
2883 resource: dda.framebuffer.as_entire_binding(),
2884 },
2885 wgpu::BindGroupEntry {
2886 binding: 8,
2887 resource: reg.tile_ranges.as_entire_binding(),
2888 },
2889 wgpu::BindGroupEntry {
2890 binding: 9,
2891 resource: reg.tile_instances.as_entire_binding(),
2892 },
2893 wgpu::BindGroupEntry {
2894 binding: 10,
2895 resource: reg.dirs.as_entire_binding(),
2896 },
2897 wgpu::BindGroupEntry {
2898 binding: 11,
2899 resource: reg.colmul.as_entire_binding(),
2900 },
2901 wgpu::BindGroupEntry {
2902 binding: 12,
2903 resource: smd.materials_buf.as_entire_binding(),
2904 },
2905 wgpu::BindGroupEntry {
2906 binding: 13,
2907 resource: reg.materials_vox.as_entire_binding(),
2908 },
2909 wgpu::BindGroupEntry {
2912 binding: 15,
2913 resource: sprite_point_buf.as_entire_binding(),
2914 },
2915 ];
2916 if self.sprite_shadows_capable {
2921 let terrain: [(u32, &wgpu::Buffer); 8] = [
2922 (16, &scene.occupancy_pages[0]),
2923 (17, &scene.occupancy_pages[1]),
2924 (18, &scene.occupancy_pages[2]),
2925 (19, &scene.occupancy_pages[3]),
2926 (20, &scene.all_chunk_occupancy),
2927 (21, &scene.all_slot_chunk_idx),
2928 (22, &scene.grid_static_meta),
2929 (23, &grid_cameras),
2930 ];
2931 for (binding, buf) in terrain {
2932 sprite_entries.push(wgpu::BindGroupEntry {
2933 binding,
2934 resource: buf.as_entire_binding(),
2935 });
2936 }
2937 }
2938 Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2939 label: Some("roxlap-gpu sprite_model_dda.bg"),
2940 layout: &smd.bgl,
2941 entries: &sprite_entries,
2942 }))
2943 }
2944 _ => None,
2945 };
2946
2947 let mut encoder = self
2948 .device
2949 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2950 label: Some("roxlap-gpu scene encoder"),
2951 });
2952 {
2953 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
2954 label: Some("roxlap-gpu scene_dda compute"),
2955 timestamp_writes: None,
2956 });
2957 cpass.set_pipeline(&dda.pipeline_dda);
2958 cpass.set_bind_group(0, &dda_bg, &[]);
2959 cpass.dispatch_workgroups(render_w.div_ceil(8), render_h.div_ceil(8), 1);
2960 }
2961 if let (Some(smd), Some(bg)) = (&self.sprite_model_dda, &sprite_model_bg) {
2965 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
2966 label: Some("roxlap-gpu sprite_model_dda"),
2967 timestamp_writes: None,
2968 });
2969 cpass.set_pipeline(&smd.pipeline);
2970 cpass.set_bind_group(0, bg, &[]);
2971 cpass.dispatch_workgroups(render_w.div_ceil(8), render_h.div_ceil(8), 1);
2972 }
2973 {
2977 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
2978 label: Some("roxlap-gpu scene_dda resolve"),
2979 timestamp_writes: None,
2980 });
2981 cpass.set_pipeline(&dda.pipeline_resolve);
2982 cpass.set_bind_group(0, &dda.resolve_bg, &[]);
2983 cpass.dispatch_workgroups(logical_w.div_ceil(8), logical_h.div_ceil(8), 1);
2984 }
2985 {
2986 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2987 label: Some("roxlap-gpu scene_dda blit"),
2988 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2989 view: &surf_view,
2990 depth_slice: None,
2991 resolve_target: None,
2992 ops: wgpu::Operations {
2993 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
2994 store: wgpu::StoreOp::Store,
2995 },
2996 })],
2997 depth_stencil_attachment: None,
2998 timestamp_writes: None,
2999 occlusion_query_set: None,
3000 multiview_mask: None,
3001 });
3002 rpass.set_pipeline(&dda.pipeline_blit);
3003 rpass.set_bind_group(0, &dda.blit_bg, &[]);
3004 rpass.draw(0..3, 0..1);
3005 }
3006 self.queue.submit(std::iter::once(encoder.finish()));
3007 self.scene_depth_valid = true;
3010 self.pending_frame = Some((surf_tex, surf_view));
3012 self.frame_count = self.frame_count.wrapping_add(1);
3013 }
3014
3015 pub fn render_clear_deferred(&mut self) {
3020 self.scene_depth_valid = false;
3024 self.pending_frame = None;
3025 let Some(surf_tex) = self.acquire_frame() else {
3026 return;
3027 };
3028 let view = surf_tex
3029 .texture
3030 .create_view(&wgpu::TextureViewDescriptor::default());
3031 let [r, g, b] = self.clear_colour;
3032 let mut encoder = self
3033 .device
3034 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3035 label: Some("roxlap-gpu clear (deferred)"),
3036 });
3037 {
3038 let _rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3039 label: Some("roxlap-gpu clear (deferred)"),
3040 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3041 view: &view,
3042 depth_slice: None,
3043 resolve_target: None,
3044 ops: wgpu::Operations {
3045 load: wgpu::LoadOp::Clear(wgpu::Color { r, g, b, a: 1.0 }),
3046 store: wgpu::StoreOp::Store,
3047 },
3048 })],
3049 depth_stencil_attachment: None,
3050 timestamp_writes: None,
3051 occlusion_query_set: None,
3052 multiview_mask: None,
3053 });
3054 }
3055 self.queue.submit(std::iter::once(encoder.finish()));
3056 self.pending_frame = Some((surf_tex, view));
3057 }
3058
3059 pub fn present(&mut self) {
3063 if let Some((surf_tex, _view)) = self.pending_frame.take() {
3064 surf_tex.present();
3065 }
3066 }
3067
3068 pub fn wait_idle(&mut self) {
3077 self.pending_frame = None;
3080 #[cfg(not(target_arch = "wasm32"))]
3081 {
3082 self.device.poll(wgpu::PollType::wait_indefinitely()).ok();
3083 }
3084 }
3085
3086 pub fn draw_lines_deferred(&mut self, cam: &GpuLineCamera, lines: &[GpuLine]) {
3096 if self.pending_frame.is_none() || lines.is_empty() {
3097 return;
3098 }
3099 let (w, h) = (self.surface_config.width, self.surface_config.height);
3100 let (rw, rh) = self.render_dims();
3103 let fov = self.last_fov_y_rad;
3104 if w == 0 || h == 0 || fov <= 0.0 {
3105 return; }
3107 let verts = build_line_vertices(cam, lines, rw, rh, fov, self.flip_x);
3108 if verts.is_empty() {
3109 return;
3110 }
3111 self.ensure_line_resources();
3112 let res = self.line_resources.as_ref().expect("just built");
3113
3114 let no_depth = u32::from(self.scene_dda.is_none() || !self.scene_depth_valid);
3121 let params = LineParams {
3122 screen_w: w,
3123 screen_h: h,
3124 depth_bias: LINE_DEPTH_BIAS,
3125 no_depth,
3126 flip_x: u32::from(self.flip_x),
3127 depth_w: rw,
3128 depth_h: rh,
3129 _pad: 0,
3130 };
3131 self.queue
3132 .write_buffer(&res.uniform_buf, 0, bytemuck::bytes_of(¶ms));
3133
3134 let depth_resource = match &self.scene_dda {
3135 Some(dda) => dda.depth_buffer.as_entire_binding(),
3136 None => res.dummy_depth.as_entire_binding(),
3137 };
3138 let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3139 label: Some("roxlap-gpu line.bg"),
3140 layout: &res.bgl,
3141 entries: &[
3142 wgpu::BindGroupEntry {
3143 binding: 0,
3144 resource: res.uniform_buf.as_entire_binding(),
3145 },
3146 wgpu::BindGroupEntry {
3147 binding: 1,
3148 resource: depth_resource,
3149 },
3150 ],
3151 });
3152
3153 let needed = std::mem::size_of_val(verts.as_slice()) as u64;
3157 if self.line_vbuf_cap < needed {
3158 let cap = needed.next_power_of_two().max(4096);
3159 self.line_vbuf = Some(self.device.create_buffer(&wgpu::BufferDescriptor {
3160 label: Some("roxlap-gpu line.vbuf"),
3161 size: cap,
3162 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
3163 mapped_at_creation: false,
3164 }));
3165 self.line_vbuf_cap = cap;
3166 }
3167 let vbuf = self.line_vbuf.as_ref().expect("ensured above");
3168 self.queue
3169 .write_buffer(vbuf, 0, bytemuck::cast_slice(&verts));
3170
3171 let view = &self.pending_frame.as_ref().expect("checked above").1;
3172 let mut encoder = self
3173 .device
3174 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3175 label: Some("roxlap-gpu lines"),
3176 });
3177 {
3178 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3181 label: Some("roxlap-gpu line paint"),
3182 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3183 view,
3184 depth_slice: None,
3185 resolve_target: None,
3186 ops: wgpu::Operations {
3187 load: wgpu::LoadOp::Load,
3188 store: wgpu::StoreOp::Store,
3189 },
3190 })],
3191 depth_stencil_attachment: None,
3192 timestamp_writes: None,
3193 occlusion_query_set: None,
3194 multiview_mask: None,
3195 });
3196 pass.set_pipeline(&res.pipeline);
3197 pass.set_bind_group(0, &bg, &[]);
3198 pass.set_vertex_buffer(0, vbuf.slice(..));
3199 pass.draw(0..verts.len() as u32, 0..1);
3200 }
3201 self.queue.submit(std::iter::once(encoder.finish()));
3202 }
3204
3205 fn ensure_line_resources(&mut self) {
3210 if self.line_resources.is_some() {
3211 return;
3212 }
3213 let shader = self
3214 .device
3215 .create_shader_module(wgpu::ShaderModuleDescriptor {
3216 label: Some("line.wgsl"),
3217 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/line.wgsl").into()),
3218 });
3219 let bgl = self
3220 .device
3221 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3222 label: Some("roxlap-gpu line.bgl"),
3223 entries: &[
3224 wgpu::BindGroupLayoutEntry {
3225 binding: 0,
3226 visibility: wgpu::ShaderStages::FRAGMENT,
3227 ty: wgpu::BindingType::Buffer {
3228 ty: wgpu::BufferBindingType::Uniform,
3229 has_dynamic_offset: false,
3230 min_binding_size: None,
3231 },
3232 count: None,
3233 },
3234 wgpu::BindGroupLayoutEntry {
3235 binding: 1,
3236 visibility: wgpu::ShaderStages::FRAGMENT,
3237 ty: wgpu::BindingType::Buffer {
3238 ty: wgpu::BufferBindingType::Storage { read_only: true },
3239 has_dynamic_offset: false,
3240 min_binding_size: None,
3241 },
3242 count: None,
3243 },
3244 ],
3245 });
3246 let layout = self
3247 .device
3248 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
3249 label: Some("roxlap-gpu line.layout"),
3250 bind_group_layouts: &[Some(&bgl)],
3251 immediate_size: 0,
3252 });
3253 let pipeline = self
3254 .device
3255 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
3256 label: Some("roxlap-gpu line.pipeline"),
3257 layout: Some(&layout),
3258 vertex: wgpu::VertexState {
3259 module: &shader,
3260 entry_point: Some("vs_main"),
3261 compilation_options: wgpu::PipelineCompilationOptions::default(),
3262 buffers: &[wgpu::VertexBufferLayout {
3263 array_stride: std::mem::size_of::<LineVertex>() as u64,
3264 step_mode: wgpu::VertexStepMode::Vertex,
3265 attributes: &wgpu::vertex_attr_array![
3266 0 => Float32x2, 1 => Float32, 2 => Float32, 3 => Float32x4, ],
3271 }],
3272 },
3273 fragment: Some(wgpu::FragmentState {
3274 module: &shader,
3275 entry_point: Some("fs_main"),
3276 compilation_options: wgpu::PipelineCompilationOptions::default(),
3277 targets: &[Some(wgpu::ColorTargetState {
3278 format: self.surface_config.format,
3279 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
3280 write_mask: wgpu::ColorWrites::ALL,
3281 })],
3282 }),
3283 primitive: wgpu::PrimitiveState {
3284 cull_mode: None,
3285 ..Default::default()
3286 },
3287 depth_stencil: None,
3288 multisample: wgpu::MultisampleState::default(),
3289 multiview_mask: None,
3290 cache: None,
3291 });
3292 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3293 label: Some("roxlap-gpu line.uniform"),
3294 size: std::mem::size_of::<LineParams>() as u64,
3295 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3296 mapped_at_creation: false,
3297 });
3298 let dummy_depth = self.device.create_buffer(&wgpu::BufferDescriptor {
3299 label: Some("roxlap-gpu line.dummy_depth"),
3300 size: 4,
3301 usage: wgpu::BufferUsages::STORAGE,
3302 mapped_at_creation: false,
3303 });
3304 self.line_resources = Some(LineResources {
3305 pipeline,
3306 bgl,
3307 uniform_buf,
3308 dummy_depth,
3309 });
3310 }
3311
3312 pub fn upload_image(&mut self, rgba: &[u8], width: u32, height: u32) -> usize {
3318 if width == 0 || height == 0 || rgba.len() != (width as usize) * (height as usize) * 4 {
3319 return 0;
3320 }
3321 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
3322 label: Some("roxlap-gpu image_sprite"),
3323 size: wgpu::Extent3d {
3324 width,
3325 height,
3326 depth_or_array_layers: 1,
3327 },
3328 mip_level_count: 1,
3329 sample_count: 1,
3330 dimension: wgpu::TextureDimension::D2,
3331 format: wgpu::TextureFormat::Rgba8Unorm,
3332 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3333 view_formats: &[],
3334 });
3335 self.queue.write_texture(
3336 wgpu::TexelCopyTextureInfo {
3337 texture: &texture,
3338 mip_level: 0,
3339 origin: wgpu::Origin3d::ZERO,
3340 aspect: wgpu::TextureAspect::All,
3341 },
3342 rgba,
3343 wgpu::TexelCopyBufferLayout {
3344 offset: 0,
3345 bytes_per_row: Some(width * 4),
3346 rows_per_image: Some(height),
3347 },
3348 wgpu::Extent3d {
3349 width,
3350 height,
3351 depth_or_array_layers: 1,
3352 },
3353 );
3354 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
3355 let resident = ImageResident {
3356 view,
3357 _texture: texture,
3358 };
3359 if let Some(slot) = self.images.iter().position(Option::is_none) {
3360 self.images[slot] = Some(resident);
3361 slot
3362 } else {
3363 self.images.push(Some(resident));
3364 self.images.len() - 1
3365 }
3366 }
3367
3368 pub fn drop_image(&mut self, id: usize) {
3371 if let Some(slot) = self.images.get_mut(id) {
3372 *slot = None;
3373 }
3374 }
3375
3376 pub fn draw_images_deferred(&mut self, cam: &GpuLineCamera, quads: &[GpuImageQuad]) {
3386 if self.pending_frame.is_none() || quads.is_empty() {
3387 return;
3388 }
3389 let (w, h) = (self.surface_config.width, self.surface_config.height);
3390 let (rw, rh) = self.render_dims();
3393 let fov = self.last_fov_y_rad;
3394 if w == 0 || h == 0 || fov <= 0.0 {
3395 return;
3396 }
3397
3398 let mut verts: Vec<ImageVertex> = Vec::new();
3401 let mut draws: Vec<(u32, u32, usize)> = Vec::new();
3402 for quad in quads {
3403 if !matches!(self.images.get(quad.image), Some(Some(_))) {
3404 continue; }
3406 let v = build_image_vertices(cam, quad, rw, rh, fov, self.flip_x);
3407 if v.is_empty() {
3408 continue;
3409 }
3410 let start = verts.len() as u32;
3411 verts.extend_from_slice(&v);
3412 draws.push((start, verts.len() as u32, quad.image));
3413 }
3414 if draws.is_empty() {
3415 return;
3416 }
3417
3418 self.ensure_image_resources();
3419 let no_depth = u32::from(self.scene_dda.is_none() || !self.scene_depth_valid);
3422 let params = LineParams {
3423 screen_w: w,
3424 screen_h: h,
3425 depth_bias: LINE_DEPTH_BIAS,
3426 no_depth,
3427 flip_x: u32::from(self.flip_x),
3428 depth_w: rw,
3429 depth_h: rh,
3430 _pad: 0,
3431 };
3432 {
3433 let res = self.image_resources.as_ref().expect("just built");
3434 self.queue
3435 .write_buffer(&res.uniform_buf, 0, bytemuck::bytes_of(¶ms));
3436 }
3437
3438 let needed = std::mem::size_of_val(verts.as_slice()) as u64;
3440 if self.image_vbuf_cap < needed {
3441 let cap = needed.next_power_of_two().max(4096);
3442 self.image_vbuf = Some(self.device.create_buffer(&wgpu::BufferDescriptor {
3443 label: Some("roxlap-gpu image.vbuf"),
3444 size: cap,
3445 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
3446 mapped_at_creation: false,
3447 }));
3448 self.image_vbuf_cap = cap;
3449 }
3450 let vbuf = self.image_vbuf.as_ref().expect("ensured above");
3451 self.queue
3452 .write_buffer(vbuf, 0, bytemuck::cast_slice(&verts));
3453
3454 let res = self.image_resources.as_ref().expect("just built");
3456 let depth_resource = match &self.scene_dda {
3457 Some(dda) => dda.depth_buffer.as_entire_binding(),
3458 None => res.dummy_depth.as_entire_binding(),
3459 };
3460 let bind_groups: Vec<wgpu::BindGroup> = draws
3461 .iter()
3462 .map(|&(_, _, image_id)| {
3463 let resident = self.images[image_id].as_ref().expect("checked present");
3464 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3465 label: Some("roxlap-gpu image.bg"),
3466 layout: &res.bgl,
3467 entries: &[
3468 wgpu::BindGroupEntry {
3469 binding: 0,
3470 resource: res.uniform_buf.as_entire_binding(),
3471 },
3472 wgpu::BindGroupEntry {
3473 binding: 1,
3474 resource: depth_resource.clone(),
3475 },
3476 wgpu::BindGroupEntry {
3477 binding: 2,
3478 resource: wgpu::BindingResource::TextureView(&resident.view),
3479 },
3480 wgpu::BindGroupEntry {
3481 binding: 3,
3482 resource: wgpu::BindingResource::Sampler(&res.sampler),
3483 },
3484 ],
3485 })
3486 })
3487 .collect();
3488
3489 let view = &self.pending_frame.as_ref().expect("checked above").1;
3490 let mut encoder = self
3491 .device
3492 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3493 label: Some("roxlap-gpu images"),
3494 });
3495 {
3496 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3497 label: Some("roxlap-gpu image paint"),
3498 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3499 view,
3500 depth_slice: None,
3501 resolve_target: None,
3502 ops: wgpu::Operations {
3503 load: wgpu::LoadOp::Load,
3504 store: wgpu::StoreOp::Store,
3505 },
3506 })],
3507 depth_stencil_attachment: None,
3508 timestamp_writes: None,
3509 occlusion_query_set: None,
3510 multiview_mask: None,
3511 });
3512 pass.set_pipeline(&res.pipeline);
3513 pass.set_vertex_buffer(0, vbuf.slice(..));
3514 for (&(start, end, _), bg) in draws.iter().zip(&bind_groups) {
3515 pass.set_bind_group(0, bg, &[]);
3516 pass.draw(start..end, 0..1);
3517 }
3518 }
3519 self.queue.submit(std::iter::once(encoder.finish()));
3520 }
3522
3523 fn ensure_image_resources(&mut self) {
3527 if self.image_resources.is_some() {
3528 return;
3529 }
3530 let shader = self
3531 .device
3532 .create_shader_module(wgpu::ShaderModuleDescriptor {
3533 label: Some("image.wgsl"),
3534 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/image.wgsl").into()),
3535 });
3536 let bgl = self
3537 .device
3538 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3539 label: Some("roxlap-gpu image.bgl"),
3540 entries: &[
3541 wgpu::BindGroupLayoutEntry {
3542 binding: 0,
3543 visibility: wgpu::ShaderStages::FRAGMENT,
3544 ty: wgpu::BindingType::Buffer {
3545 ty: wgpu::BufferBindingType::Uniform,
3546 has_dynamic_offset: false,
3547 min_binding_size: None,
3548 },
3549 count: None,
3550 },
3551 wgpu::BindGroupLayoutEntry {
3552 binding: 1,
3553 visibility: wgpu::ShaderStages::FRAGMENT,
3554 ty: wgpu::BindingType::Buffer {
3555 ty: wgpu::BufferBindingType::Storage { read_only: true },
3556 has_dynamic_offset: false,
3557 min_binding_size: None,
3558 },
3559 count: None,
3560 },
3561 wgpu::BindGroupLayoutEntry {
3562 binding: 2,
3563 visibility: wgpu::ShaderStages::FRAGMENT,
3564 ty: wgpu::BindingType::Texture {
3565 sample_type: wgpu::TextureSampleType::Float { filterable: true },
3566 view_dimension: wgpu::TextureViewDimension::D2,
3567 multisampled: false,
3568 },
3569 count: None,
3570 },
3571 wgpu::BindGroupLayoutEntry {
3572 binding: 3,
3573 visibility: wgpu::ShaderStages::FRAGMENT,
3574 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
3575 count: None,
3576 },
3577 ],
3578 });
3579 let layout = self
3580 .device
3581 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
3582 label: Some("roxlap-gpu image.layout"),
3583 bind_group_layouts: &[Some(&bgl)],
3584 immediate_size: 0,
3585 });
3586 let pipeline = self
3587 .device
3588 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
3589 label: Some("roxlap-gpu image.pipeline"),
3590 layout: Some(&layout),
3591 vertex: wgpu::VertexState {
3592 module: &shader,
3593 entry_point: Some("vs_main"),
3594 compilation_options: wgpu::PipelineCompilationOptions::default(),
3595 buffers: &[wgpu::VertexBufferLayout {
3596 array_stride: std::mem::size_of::<ImageVertex>() as u64,
3597 step_mode: wgpu::VertexStepMode::Vertex,
3598 attributes: &wgpu::vertex_attr_array![
3599 0 => Float32x2, 1 => Float32, 2 => Float32, 3 => Float32, 4 => Float32, 5 => Float32x2, 6 => Float32x4, ],
3607 }],
3608 },
3609 fragment: Some(wgpu::FragmentState {
3610 module: &shader,
3611 entry_point: Some("fs_main"),
3612 compilation_options: wgpu::PipelineCompilationOptions::default(),
3613 targets: &[Some(wgpu::ColorTargetState {
3614 format: self.surface_config.format,
3615 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
3616 write_mask: wgpu::ColorWrites::ALL,
3617 })],
3618 }),
3619 primitive: wgpu::PrimitiveState {
3620 cull_mode: None,
3621 ..Default::default()
3622 },
3623 depth_stencil: None,
3624 multisample: wgpu::MultisampleState::default(),
3625 multiview_mask: None,
3626 cache: None,
3627 });
3628 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3629 label: Some("roxlap-gpu image.uniform"),
3630 size: std::mem::size_of::<LineParams>() as u64,
3631 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3632 mapped_at_creation: false,
3633 });
3634 let dummy_depth = self.device.create_buffer(&wgpu::BufferDescriptor {
3635 label: Some("roxlap-gpu image.dummy_depth"),
3636 size: 4,
3637 usage: wgpu::BufferUsages::STORAGE,
3638 mapped_at_creation: false,
3639 });
3640 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
3641 label: Some("roxlap-gpu image.sampler"),
3642 address_mode_u: wgpu::AddressMode::ClampToEdge,
3645 address_mode_v: wgpu::AddressMode::ClampToEdge,
3646 address_mode_w: wgpu::AddressMode::ClampToEdge,
3647 mag_filter: wgpu::FilterMode::Nearest,
3648 min_filter: wgpu::FilterMode::Nearest,
3649 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
3650 ..Default::default()
3651 });
3652 self.image_resources = Some(ImageResources {
3653 pipeline,
3654 bgl,
3655 uniform_buf,
3656 dummy_depth,
3657 sampler,
3658 });
3659 }
3660
3661 #[must_use]
3666 pub fn project_point(
3667 &self,
3668 cam_pos: [f32; 3],
3669 right: [f32; 3],
3670 down: [f32; 3],
3671 forward: [f32; 3],
3672 world: [f32; 3],
3673 ) -> Option<(f32, f32)> {
3674 let dda = self.scene_dda.as_ref()?;
3675 let (w, h) = dda.storage_size;
3676 if w == 0 || h == 0 || self.last_fov_y_rad <= 0.0 {
3677 return None;
3678 }
3679 let d = [
3680 world[0] - cam_pos[0],
3681 world[1] - cam_pos[1],
3682 world[2] - cam_pos[2],
3683 ];
3684 let cz = forward[0] * d[0] + forward[1] * d[1] + forward[2] * d[2];
3685 if cz < LINE_NEAR_Z {
3686 return None;
3687 }
3688 let cx = right[0] * d[0] + right[1] * d[1] + right[2] * d[2];
3689 let cy = down[0] * d[0] + down[1] * d[1] + down[2] * d[2];
3690 let half_h = (self.last_fov_y_rad * 0.5).tan();
3691 let half_w = half_h * (w as f32 / h as f32);
3692 let ndc_x = (cx / cz) / half_w;
3693 let ndc_y = -(cy / cz) / half_h;
3694 let sx = (ndc_x * 0.5 + 0.5) * w as f32;
3695 let sy = (0.5 - ndc_y * 0.5) * h as f32;
3696 Some((sx, sy))
3697 }
3698
3699 #[cfg(feature = "hud")]
3708 pub fn paint_egui(
3709 &mut self,
3710 jobs: &[egui::ClippedPrimitive],
3711 textures: &egui::TexturesDelta,
3712 pixels_per_point: f32,
3713 ) {
3714 let Some((surf_tex, surf_view)) = self.pending_frame.take() else {
3715 return;
3716 };
3717 let format = self.surface_config.format;
3718 let egui_rend = self.egui_renderer.get_or_insert_with(|| {
3719 egui_wgpu::Renderer::new(
3720 &self.device,
3721 format,
3722 egui_wgpu::RendererOptions {
3723 msaa_samples: 1,
3724 depth_stencil_format: None,
3725 dithering: false,
3726 ..Default::default()
3727 },
3728 )
3729 });
3730
3731 let screen = egui_wgpu::ScreenDescriptor {
3732 size_in_pixels: [self.surface_config.width, self.surface_config.height],
3733 pixels_per_point,
3734 };
3735 for (id, delta) in &textures.set {
3736 egui_rend.update_texture(&self.device, &self.queue, *id, delta);
3737 }
3738 let mut encoder = self
3739 .device
3740 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3741 label: Some("roxlap-gpu egui"),
3742 });
3743 let user_bufs =
3744 egui_rend.update_buffers(&self.device, &self.queue, &mut encoder, jobs, &screen);
3745 {
3746 let mut pass = encoder
3748 .begin_render_pass(&wgpu::RenderPassDescriptor {
3749 label: Some("roxlap-gpu egui paint"),
3750 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3751 view: &surf_view,
3752 depth_slice: None,
3753 resolve_target: None,
3754 ops: wgpu::Operations {
3755 load: wgpu::LoadOp::Load,
3756 store: wgpu::StoreOp::Store,
3757 },
3758 })],
3759 depth_stencil_attachment: None,
3760 timestamp_writes: None,
3761 occlusion_query_set: None,
3762 multiview_mask: None,
3763 })
3764 .forget_lifetime();
3766 egui_rend.render(&mut pass, jobs, &screen);
3767 }
3768 for id in &textures.free {
3769 egui_rend.free_texture(id);
3770 }
3771 self.queue.submit(
3772 user_bufs
3773 .into_iter()
3774 .chain(std::iter::once(encoder.finish())),
3775 );
3776 surf_tex.present();
3777 }
3778
3779 fn build_scene_dda(
3780 &self,
3781 width: u32,
3782 height: u32,
3783 logical_w: u32,
3784 logical_h: u32,
3785 surface_w: u32,
3786 surface_h: u32,
3787 surface_format: wgpu::TextureFormat,
3788 ) -> SceneDdaResources {
3789 let framebuffer = self.device.create_buffer(&wgpu::BufferDescriptor {
3795 label: Some("roxlap-gpu scene_dda.framebuffer"),
3796 size: u64::from(width) * u64::from(height) * 4,
3797 usage: wgpu::BufferUsages::STORAGE,
3798 mapped_at_creation: false,
3799 });
3800 let resolve_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3803 label: Some("roxlap-gpu scene_dda.resolve_buf"),
3804 size: u64::from(logical_w) * u64::from(logical_h) * 4,
3805 usage: wgpu::BufferUsages::STORAGE,
3806 mapped_at_creation: false,
3807 });
3808 let resolve_dims = self.device.create_buffer(&wgpu::BufferDescriptor {
3812 label: Some("roxlap-gpu scene_dda.resolve_dims"),
3813 size: 48,
3814 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3815 mapped_at_creation: false,
3816 });
3817 self.queue.write_buffer(
3818 &resolve_dims,
3819 0,
3820 bytemuck::bytes_of(&[width, height, logical_w, logical_h, self.ssaa]),
3821 );
3822 let blit_dims = self.device.create_buffer(&wgpu::BufferDescriptor {
3827 label: Some("roxlap-gpu scene_dda.blit_dims"),
3828 size: 32,
3829 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3830 mapped_at_creation: false,
3831 });
3832 self.queue.write_buffer(
3833 &blit_dims,
3834 0,
3835 bytemuck::bytes_of(&[
3836 logical_w,
3837 logical_h,
3838 surface_w,
3839 surface_h,
3840 u32::from(self.flip_x),
3841 0u32,
3842 0u32,
3843 0u32,
3844 ]),
3845 );
3846
3847 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3848 label: Some("roxlap-gpu scene_dda.uniform"),
3849 size: std::mem::size_of::<SceneDdaUniform>() as u64,
3850 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3851 mapped_at_creation: false,
3852 });
3853
3854 let depth_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
3858 label: Some("roxlap-gpu scene_dda.depth"),
3859 size: u64::from(width) * u64::from(height) * 4,
3860 usage: wgpu::BufferUsages::STORAGE
3862 | wgpu::BufferUsages::COPY_DST
3863 | wgpu::BufferUsages::COPY_SRC,
3864 mapped_at_creation: false,
3865 });
3866 let depth_readback = self.device.create_buffer(&wgpu::BufferDescriptor {
3867 label: Some("roxlap-gpu scene_dda.depth_readback"),
3868 size: u64::from(width) * u64::from(height) * 4,
3869 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
3870 mapped_at_creation: false,
3871 });
3872 let capable = self.sprite_shadows_capable;
3876 let dda_shader = self
3877 .device
3878 .create_shader_module(wgpu::ShaderModuleDescriptor {
3879 label: Some("scene_dda.wgsl"),
3880 source: wgpu::ShaderSource::Wgsl(scene_shader_source(capable).into()),
3881 });
3882 let mut dda_entries = vec![
3883 bgl_uniform_entry(0),
3884 bgl_storage_entry(1, true),
3885 bgl_storage_entry(2, true),
3886 bgl_storage_entry(3, true),
3887 bgl_storage_entry(4, true),
3888 bgl_storage_entry(5, true),
3889 bgl_storage_entry(6, true),
3890 bgl_storage_entry(7, true),
3891 bgl_storage_entry(8, false),
3894 wgpu::BindGroupLayoutEntry {
3896 binding: 9,
3897 visibility: wgpu::ShaderStages::COMPUTE,
3898 ty: wgpu::BindingType::Texture {
3899 sample_type: wgpu::TextureSampleType::Float { filterable: true },
3900 view_dimension: wgpu::TextureViewDimension::D2,
3901 multisampled: false,
3902 },
3903 count: None,
3904 },
3905 wgpu::BindGroupLayoutEntry {
3906 binding: 10,
3907 visibility: wgpu::ShaderStages::COMPUTE,
3908 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
3909 count: None,
3910 },
3911 bgl_storage_entry(11, false),
3913 bgl_storage_entry(12, true),
3916 bgl_storage_entry(13, true),
3917 bgl_storage_entry(14, true),
3918 bgl_storage_entry(15, true),
3920 bgl_storage_entry(16, true),
3922 bgl_storage_entry(17, true),
3923 bgl_storage_entry(18, true),
3927 ];
3928 if capable {
3929 dda_entries.push(bgl_storage_entry(19, true)); dda_entries.push(bgl_storage_entry(20, true)); dda_entries.push(bgl_storage_entry(21, true)); }
3934 let bgl_dda = self
3935 .device
3936 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3937 label: Some("roxlap-gpu scene_dda.bgl"),
3938 entries: &dda_entries,
3939 });
3940 let dda_pl = self
3941 .device
3942 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
3943 label: Some("roxlap-gpu scene_dda.layout"),
3944 bind_group_layouts: &[Some(&bgl_dda)],
3945 immediate_size: 0,
3946 });
3947 let pipeline_dda = self
3948 .device
3949 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
3950 label: Some("roxlap-gpu scene_dda.pipeline"),
3951 layout: Some(&dda_pl),
3952 module: &dda_shader,
3953 entry_point: Some("render_scene"),
3954 compilation_options: wgpu::PipelineCompilationOptions::default(),
3955 cache: None,
3956 });
3957
3958 let resolve_shader = self
3961 .device
3962 .create_shader_module(wgpu::ShaderModuleDescriptor {
3963 label: Some("scene_resolve.wgsl"),
3964 source: wgpu::ShaderSource::Wgsl(
3965 include_str!("../shaders/scene_resolve.wgsl").into(),
3966 ),
3967 });
3968 let bgl_resolve = self
3969 .device
3970 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3971 label: Some("roxlap-gpu scene_dda.resolve_bgl"),
3972 entries: &[
3973 bgl_storage_entry(0, true), bgl_storage_entry(1, false), bgl_uniform_entry(2), ],
3977 });
3978 let resolve_pl = self
3979 .device
3980 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
3981 label: Some("roxlap-gpu scene_dda.resolve_layout"),
3982 bind_group_layouts: &[Some(&bgl_resolve)],
3983 immediate_size: 0,
3984 });
3985 let pipeline_resolve =
3986 self.device
3987 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
3988 label: Some("roxlap-gpu scene_dda.resolve_pipeline"),
3989 layout: Some(&resolve_pl),
3990 module: &resolve_shader,
3991 entry_point: Some("main"),
3992 compilation_options: wgpu::PipelineCompilationOptions::default(),
3993 cache: None,
3994 });
3995 let resolve_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3996 label: Some("roxlap-gpu scene_dda.resolve_bg"),
3997 layout: &bgl_resolve,
3998 entries: &[
3999 wgpu::BindGroupEntry {
4000 binding: 0,
4001 resource: framebuffer.as_entire_binding(),
4002 },
4003 wgpu::BindGroupEntry {
4004 binding: 1,
4005 resource: resolve_buf.as_entire_binding(),
4006 },
4007 wgpu::BindGroupEntry {
4008 binding: 2,
4009 resource: resolve_dims.as_entire_binding(),
4010 },
4011 ],
4012 });
4013
4014 let blit_shader = self
4015 .device
4016 .create_shader_module(wgpu::ShaderModuleDescriptor {
4017 label: Some("scene_blit.wgsl"),
4018 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/scene_blit.wgsl").into()),
4019 });
4020 let bgl_blit = self
4021 .device
4022 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
4023 label: Some("roxlap-gpu scene_dda.blit_bgl"),
4024 entries: &[
4025 wgpu::BindGroupLayoutEntry {
4027 binding: 0,
4028 visibility: wgpu::ShaderStages::FRAGMENT,
4029 ty: wgpu::BindingType::Buffer {
4030 ty: wgpu::BufferBindingType::Storage { read_only: true },
4031 has_dynamic_offset: false,
4032 min_binding_size: None,
4033 },
4034 count: None,
4035 },
4036 wgpu::BindGroupLayoutEntry {
4038 binding: 1,
4039 visibility: wgpu::ShaderStages::FRAGMENT,
4040 ty: wgpu::BindingType::Buffer {
4041 ty: wgpu::BufferBindingType::Uniform,
4042 has_dynamic_offset: false,
4043 min_binding_size: None,
4044 },
4045 count: None,
4046 },
4047 ],
4048 });
4049 let blit_pl = self
4050 .device
4051 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
4052 label: Some("roxlap-gpu scene_dda.blit_layout"),
4053 bind_group_layouts: &[Some(&bgl_blit)],
4054 immediate_size: 0,
4055 });
4056 let pipeline_blit = self
4057 .device
4058 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
4059 label: Some("roxlap-gpu scene_dda.blit_pipeline"),
4060 layout: Some(&blit_pl),
4061 vertex: wgpu::VertexState {
4062 module: &blit_shader,
4063 entry_point: Some("vs_main"),
4064 compilation_options: wgpu::PipelineCompilationOptions::default(),
4065 buffers: &[],
4066 },
4067 fragment: Some(wgpu::FragmentState {
4068 module: &blit_shader,
4069 entry_point: Some("fs_main"),
4070 compilation_options: wgpu::PipelineCompilationOptions::default(),
4071 targets: &[Some(wgpu::ColorTargetState {
4072 format: surface_format,
4073 blend: None,
4074 write_mask: wgpu::ColorWrites::ALL,
4075 })],
4076 }),
4077 primitive: wgpu::PrimitiveState::default(),
4078 depth_stencil: None,
4079 multisample: wgpu::MultisampleState::default(),
4080 multiview_mask: None,
4081 cache: None,
4082 });
4083 let blit_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4084 label: Some("roxlap-gpu scene_dda.blit_bg"),
4085 layout: &bgl_blit,
4086 entries: &[
4087 wgpu::BindGroupEntry {
4088 binding: 0,
4089 resource: resolve_buf.as_entire_binding(),
4091 },
4092 wgpu::BindGroupEntry {
4093 binding: 1,
4094 resource: blit_dims.as_entire_binding(),
4095 },
4096 ],
4097 });
4098
4099 let (materials_pal_buf, terrain_map_buf) = {
4103 use wgpu::util::DeviceExt;
4104 let pal = self
4105 .device
4106 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
4107 label: Some("roxlap-gpu scene_dda.materials_pal"),
4108 contents: bytemuck::cast_slice(self.scene_materials.as_slice()),
4109 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
4110 });
4111 let mut rows = [[0u32; 2]; 256];
4114 for (slot, &row) in rows.iter_mut().zip(self.scene_terrain_map.iter()) {
4115 *slot = row;
4116 }
4117 let map = self
4118 .device
4119 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
4120 label: Some("roxlap-gpu scene_dda.terrain_map"),
4121 contents: bytemuck::cast_slice(&rows),
4122 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
4123 });
4124 (pal, map)
4125 };
4126
4127 SceneDdaResources {
4128 storage_size: (width, height),
4129 logical_size: (logical_w, logical_h),
4130 framebuffer,
4131 uniform_buf,
4132 bgl_dda,
4133 pipeline_dda,
4134 pipeline_resolve,
4135 resolve_bg,
4136 resolve_dims,
4137 blit_bg,
4138 pipeline_blit,
4139 blit_dims,
4140 depth_buffer,
4141 depth_readback,
4142 materials_pal_buf,
4143 terrain_map_buf,
4144 sprite_cast_dummy: capable.then(|| {
4147 self.device.create_buffer(&wgpu::BufferDescriptor {
4148 label: Some("roxlap-gpu scene_dda.sprite_cast_dummy"),
4149 size: 80,
4150 usage: wgpu::BufferUsages::STORAGE,
4151 mapped_at_creation: false,
4152 })
4153 }),
4154 }
4155 }
4156
4157 #[must_use]
4182 pub fn read_depth_pixel(&self, x: u32, y: u32) -> Option<f32> {
4183 let dda = self.scene_dda.as_ref()?;
4184 let (w, h) = dda.storage_size;
4185 if x >= w || y >= h {
4186 return None;
4187 }
4188 let mut enc = self
4189 .device
4190 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
4191 label: Some("roxlap-gpu depth readback"),
4192 });
4193 let size = u64::from(w) * u64::from(h) * 4;
4194 enc.copy_buffer_to_buffer(&dda.depth_buffer, 0, &dda.depth_readback, 0, size);
4195 self.queue.submit(std::iter::once(enc.finish()));
4196
4197 let slice = dda.depth_readback.slice(..);
4198 let (tx, rx) = std::sync::mpsc::channel();
4199 slice.map_async(wgpu::MapMode::Read, move |r| {
4200 let _ = tx.send(r);
4201 });
4202 self.device.poll(wgpu::PollType::wait_indefinitely()).ok();
4203 rx.recv().ok()?.ok()?;
4204
4205 let t = {
4206 let data = slice.get_mapped_range();
4207 let idx = ((y * w + x) * 4) as usize;
4208 let bytes: [u8; 4] = data[idx..idx + 4].try_into().ok()?;
4209 f32::from_le_bytes(bytes)
4210 };
4211 dda.depth_readback.unmap();
4212
4213 if !t.is_finite() || t >= 1.0e29 {
4215 return None;
4216 }
4217 Some(t)
4218 }
4219
4220 #[must_use]
4227 pub fn pixel_ray(
4228 &self,
4229 right: [f64; 3],
4230 down: [f64; 3],
4231 forward: [f64; 3],
4232 x: f64,
4233 y: f64,
4234 ) -> Option<[f64; 3]> {
4235 let dda = self.scene_dda.as_ref()?;
4236 let (w, h) = dda.storage_size;
4237 if w == 0 || h == 0 || self.last_fov_y_rad <= 0.0 {
4238 return None;
4239 }
4240 Some(pinhole_pixel_ray(
4241 right,
4242 down,
4243 forward,
4244 x,
4245 y,
4246 f64::from(w),
4247 f64::from(h),
4248 f64::from(self.last_fov_y_rad),
4249 ))
4250 }
4251
4252 pub fn set_sprite_instances(
4255 &mut self,
4256 registry: &sprite_model::SpriteModelRegistry,
4257 instances: &[sprite_model::SpriteInstance],
4258 ) {
4259 if instances.is_empty() {
4260 self.sprite_registry = None;
4261 return;
4262 }
4263 self.sprite_registry = Some(sprite_model::SpriteRegistryResident::upload(
4264 &self.device,
4265 registry,
4266 instances,
4267 ));
4268 }
4269
4270 pub fn append_sprite_instances(
4285 &mut self,
4286 registry: &sprite_model::SpriteModelRegistry,
4287 instances: &[sprite_model::SpriteInstance],
4288 ) -> u32 {
4289 match self.sprite_registry.as_mut() {
4290 Some(reg) => reg.append_instances(&self.device, registry, instances),
4291 None => {
4292 self.set_sprite_instances(registry, instances);
4293 0
4294 }
4295 }
4296 }
4297
4298 pub fn remove_sprite_instance(&mut self, index: usize) -> Option<usize> {
4305 self.sprite_registry
4306 .as_mut()
4307 .and_then(|reg| reg.remove_instance(index))
4308 }
4309
4310 pub fn flush_writes(&self) {
4337 self.queue.submit(std::iter::empty::<wgpu::CommandBuffer>());
4338 }
4339
4340 pub fn add_sprite_model(
4341 &mut self,
4342 registry: &sprite_model::SpriteModelRegistry,
4343 chain_id: u32,
4344 ) {
4345 match self.sprite_registry.as_mut() {
4346 Some(reg) => reg.add_model(&self.device, &self.queue, registry, chain_id),
4347 None => {
4348 self.sprite_registry = Some(sprite_model::SpriteRegistryResident::upload(
4349 &self.device,
4350 registry,
4351 &[],
4352 ));
4353 }
4354 }
4355 }
4356
4357 pub fn remove_sprite_model(&mut self, chain_id: u32) {
4368 if let Some(reg) = self.sprite_registry.as_mut() {
4369 reg.remove_model(chain_id);
4370 }
4371 }
4372
4373 pub fn compact_sprite_models(&mut self, registry: &sprite_model::SpriteModelRegistry) {
4379 if let Some(reg) = self.sprite_registry.as_mut() {
4380 reg.compact(&self.device, &self.queue, registry);
4381 }
4382 }
4383
4384 #[must_use]
4386 pub fn sprite_model_count(&self) -> usize {
4387 self.sprite_registry
4388 .as_ref()
4389 .map_or(0, sprite_model::SpriteRegistryResident::live_model_count)
4390 }
4391
4392 #[must_use]
4396 pub fn dead_sprite_model_count(&self) -> usize {
4397 self.sprite_registry
4398 .as_ref()
4399 .map_or(0, sprite_model::SpriteRegistryResident::dead_model_count)
4400 }
4401
4402 #[must_use]
4404 pub fn sprite_instance_count(&self) -> usize {
4405 self.sprite_registry
4406 .as_ref()
4407 .map_or(0, sprite_model::SpriteRegistryResident::instance_count)
4408 }
4409
4410 pub fn update_sprite_instance_transforms(
4415 &mut self,
4416 instances: &[sprite_model::SpriteInstance],
4417 ) {
4418 if let Some(reg) = self.sprite_registry.as_mut() {
4419 reg.update_transforms(instances);
4420 }
4421 }
4422
4423 pub fn update_sprite_model(
4429 &mut self,
4430 registry: &sprite_model::SpriteModelRegistry,
4431 chain_id: u32,
4432 ) {
4433 if let Some(reg) = self.sprite_registry.as_mut() {
4434 reg.update_model(&self.device, &self.queue, registry, chain_id);
4435 }
4436 }
4437
4438 pub fn set_sprite_instance_model(
4445 &mut self,
4446 registry: &sprite_model::SpriteModelRegistry,
4447 index: usize,
4448 chain_id: u32,
4449 ) {
4450 if let Some(reg) = self.sprite_registry.as_mut() {
4451 reg.set_instance_model(registry, index, chain_id);
4452 }
4453 }
4454
4455 pub fn set_sprite_instance_colmul(&mut self, tables: &[[u64; 256]]) {
4462 if let Some(reg) = self.sprite_registry.as_mut() {
4463 reg.set_instance_colmul(tables);
4464 }
4465 }
4466
4467 pub fn set_sprite_lod_px(&mut self, px: f32) {
4473 self.sprite_lod_px = px.max(0.25);
4474 }
4475
4476 pub fn set_scene_mip_scan_dist(&mut self, dist: f32) {
4484 self.scene_mip_scan_dist = dist.max(0.0);
4485 }
4486
4487 pub fn set_scene_side_shades(&mut self, s: [i8; 6]) {
4495 let v = |i: usize| i32::from(s[i] as u8);
4499 self.scene_side_shades = [[v(0), v(1), v(2), v(3)], [v(4), v(5), 0, 0]];
4500 }
4501
4502 pub fn set_scene_lights(&mut self, lights: SceneLights) {
4508 self.scene_lights = lights;
4509 }
4510
4511 fn build_sprite_model_dda(&self) -> SpriteModelDdaResources {
4514 let capable = self.sprite_shadows_capable;
4519 let src = sprite_shader_source(capable);
4520 let shader = self
4521 .device
4522 .create_shader_module(wgpu::ShaderModuleDescriptor {
4523 label: Some("sprite_model_dda.wgsl"),
4524 source: wgpu::ShaderSource::Wgsl(src.into()),
4525 });
4526 let mut entries = vec![
4527 bgl_uniform_entry(0),
4528 bgl_storage_entry(1, true), bgl_storage_entry(2, true), bgl_storage_entry(3, true), bgl_storage_entry(4, true), bgl_storage_entry(5, true), bgl_storage_entry(6, true), bgl_storage_entry(7, false), bgl_storage_entry(8, true), bgl_storage_entry(9, true), bgl_storage_entry(10, true), bgl_storage_entry(11, true), bgl_storage_entry(12, true), bgl_storage_entry(13, true), bgl_storage_entry(15, true), ];
4543 if capable {
4544 entries.push(bgl_storage_entry(16, true)); entries.push(bgl_storage_entry(17, true)); entries.push(bgl_storage_entry(18, true)); entries.push(bgl_storage_entry(19, true)); entries.push(bgl_storage_entry(20, true)); entries.push(bgl_storage_entry(21, true)); entries.push(bgl_storage_entry(22, true)); entries.push(bgl_storage_entry(23, true)); }
4554 let bgl = self
4555 .device
4556 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
4557 label: Some("roxlap-gpu sprite_model_dda.bgl"),
4558 entries: &entries,
4559 });
4560 let pl = self
4561 .device
4562 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
4563 label: Some("roxlap-gpu sprite_model_dda.layout"),
4564 bind_group_layouts: &[Some(&bgl)],
4565 immediate_size: 0,
4566 });
4567 let pipeline = self
4568 .device
4569 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
4570 label: Some("roxlap-gpu sprite_model_dda.pipeline"),
4571 layout: Some(&pl),
4572 module: &shader,
4573 entry_point: Some("march"),
4574 compilation_options: wgpu::PipelineCompilationOptions::default(),
4575 cache: None,
4576 });
4577 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
4578 label: Some("roxlap-gpu sprite_model_dda.uniform"),
4579 size: std::mem::size_of::<SpriteModelUniform>() as u64,
4580 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
4581 mapped_at_creation: false,
4582 });
4583 let materials_buf = {
4586 use wgpu::util::DeviceExt;
4587 self.device
4588 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
4589 label: Some("roxlap-gpu sprite_model_dda.materials"),
4590 contents: bytemuck::cast_slice(self.sprite_materials.as_slice()),
4591 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
4592 })
4593 };
4594 SpriteModelDdaResources {
4595 bgl,
4596 pipeline,
4597 uniform_buf,
4598 materials_buf,
4599 }
4600 }
4601
4602 pub fn set_sprite_materials(&mut self, table: &roxlap_formats::material::MaterialTable) {
4609 let (palette, any_translucent) = material_palette(table);
4610 self.sprite_materials = palette;
4611 self.sprite_has_translucent = any_translucent;
4612 if let Some(smd) = &self.sprite_model_dda {
4613 self.queue.write_buffer(
4614 &smd.materials_buf,
4615 0,
4616 bytemuck::cast_slice(self.sprite_materials.as_slice()),
4617 );
4618 }
4619 }
4620
4621 pub fn set_scene_terrain_materials(
4626 &mut self,
4627 table: &roxlap_formats::material::MaterialTable,
4628 map: &[(u32, u8)],
4629 ) {
4630 let (palette, _) = material_palette(table);
4631 self.scene_materials = palette;
4632 self.scene_terrain_map = map
4633 .iter()
4634 .take(256)
4635 .map(|&(c, m)| [c & 0x00ff_ffff, u32::from(m)])
4636 .collect();
4637 self.scene_terrain_translucent = map.iter().any(|&(_, m)| !table.get(m).is_opaque());
4638 if let Some(dda) = &self.scene_dda {
4639 self.queue.write_buffer(
4640 &dda.materials_pal_buf,
4641 0,
4642 bytemuck::cast_slice(self.scene_materials.as_slice()),
4643 );
4644 if !self.scene_terrain_map.is_empty() {
4645 self.queue.write_buffer(
4646 &dda.terrain_map_buf,
4647 0,
4648 bytemuck::cast_slice(&self.scene_terrain_map),
4649 );
4650 }
4651 }
4652 }
4653}
4654
4655pub struct HeadlessSceneRenderer {
4662 width: u32,
4663 height: u32,
4664 framebuffer: wgpu::Buffer,
4667 depth_buffer: wgpu::Buffer,
4668 uniform_buf: wgpu::Buffer,
4669 _sky_texture: wgpu::Texture,
4670 sky_view: wgpu::TextureView,
4671 sky_sampler: wgpu::Sampler,
4672 bgl: wgpu::BindGroupLayout,
4673 pipeline: wgpu::ComputePipeline,
4674 readback: wgpu::Buffer,
4675 side_shades: [[i32; 4]; 2],
4679 lights: SceneLights,
4683}
4684
4685impl HeadlessSceneRenderer {
4686 #[must_use]
4691 pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, width: u32, height: u32) -> Self {
4692 let framebuffer = device.create_buffer(&wgpu::BufferDescriptor {
4693 label: Some("roxlap-gpu headless.framebuffer"),
4694 size: u64::from(width) * u64::from(height) * 4,
4695 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
4696 mapped_at_creation: false,
4697 });
4698
4699 let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
4700 label: Some("roxlap-gpu headless.uniform"),
4701 size: std::mem::size_of::<SceneDdaUniform>() as u64,
4702 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
4703 mapped_at_creation: false,
4704 });
4705 let depth_buffer = device.create_buffer(&wgpu::BufferDescriptor {
4706 label: Some("roxlap-gpu headless.depth"),
4707 size: u64::from(width) * u64::from(height) * 4,
4708 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
4709 mapped_at_creation: false,
4710 });
4711
4712 let default_sky_pixel = [120u8, 150, 220, 255];
4713 let (sky_texture, sky_view) = create_sky_texture(device, 1, 1, &default_sky_pixel);
4714 queue.write_texture(
4718 wgpu::TexelCopyTextureInfo {
4719 texture: &sky_texture,
4720 mip_level: 0,
4721 origin: wgpu::Origin3d::ZERO,
4722 aspect: wgpu::TextureAspect::All,
4723 },
4724 &default_sky_pixel,
4725 wgpu::TexelCopyBufferLayout {
4726 offset: 0,
4727 bytes_per_row: Some(4),
4728 rows_per_image: Some(1),
4729 },
4730 wgpu::Extent3d {
4731 width: 1,
4732 height: 1,
4733 depth_or_array_layers: 1,
4734 },
4735 );
4736 let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
4737 label: Some("roxlap-gpu headless.sky_sampler"),
4738 address_mode_u: wgpu::AddressMode::Repeat,
4739 address_mode_v: wgpu::AddressMode::Repeat,
4740 mag_filter: wgpu::FilterMode::Linear,
4741 min_filter: wgpu::FilterMode::Linear,
4742 ..Default::default()
4743 });
4744
4745 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
4746 label: Some("scene_dda.wgsl (headless)"),
4747 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/scene_dda.wgsl").into()),
4748 });
4749 let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
4750 label: Some("roxlap-gpu headless.bgl"),
4751 entries: &[
4752 bgl_uniform_entry(0),
4753 bgl_storage_entry(1, true),
4754 bgl_storage_entry(2, true),
4755 bgl_storage_entry(3, true),
4756 bgl_storage_entry(4, true),
4757 bgl_storage_entry(5, true),
4758 bgl_storage_entry(6, true),
4759 bgl_storage_entry(7, true),
4760 bgl_storage_entry(8, false),
4762 wgpu::BindGroupLayoutEntry {
4763 binding: 9,
4764 visibility: wgpu::ShaderStages::COMPUTE,
4765 ty: wgpu::BindingType::Texture {
4766 sample_type: wgpu::TextureSampleType::Float { filterable: true },
4767 view_dimension: wgpu::TextureViewDimension::D2,
4768 multisampled: false,
4769 },
4770 count: None,
4771 },
4772 wgpu::BindGroupLayoutEntry {
4773 binding: 10,
4774 visibility: wgpu::ShaderStages::COMPUTE,
4775 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
4776 count: None,
4777 },
4778 bgl_storage_entry(11, false),
4779 bgl_storage_entry(12, true),
4780 bgl_storage_entry(13, true),
4781 bgl_storage_entry(14, true),
4782 bgl_storage_entry(15, true),
4784 bgl_storage_entry(16, true),
4786 bgl_storage_entry(17, true),
4787 bgl_storage_entry(18, true),
4790 ],
4791 });
4792 let pl = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
4793 label: Some("roxlap-gpu headless.layout"),
4794 bind_group_layouts: &[Some(&bgl)],
4795 immediate_size: 0,
4796 });
4797 let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
4798 label: Some("roxlap-gpu headless.pipeline"),
4799 layout: Some(&pl),
4800 module: &shader,
4801 entry_point: Some("render_scene"),
4802 compilation_options: wgpu::PipelineCompilationOptions::default(),
4803 cache: None,
4804 });
4805
4806 let readback = device.create_buffer(&wgpu::BufferDescriptor {
4809 label: Some("roxlap-gpu headless.readback"),
4810 size: u64::from(width) * u64::from(height) * 4,
4811 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
4812 mapped_at_creation: false,
4813 });
4814
4815 Self {
4816 width,
4817 height,
4818 framebuffer,
4819 depth_buffer,
4820 uniform_buf,
4821 _sky_texture: sky_texture,
4822 sky_view,
4823 sky_sampler,
4824 bgl,
4825 pipeline,
4826 readback,
4827 side_shades: [[0; 4]; 2],
4828 lights: SceneLights::default(),
4829 }
4830 }
4831
4832 pub fn set_scene_lights(&mut self, lights: SceneLights) {
4836 self.lights = lights;
4837 }
4838
4839 pub fn set_side_shades(&mut self, s: [i8; 6]) {
4844 let v = |i: usize| i32::from(s[i] as u8);
4845 self.side_shades = [[v(0), v(1), v(2), v(3)], [v(4), v(5), 0, 0]];
4846 }
4847
4848 #[must_use]
4860 #[allow(clippy::too_many_arguments)]
4861 pub fn render(
4862 &self,
4863 device: &wgpu::Device,
4864 queue: &wgpu::Queue,
4865 scene: &GpuSceneResident,
4866 cameras: &[Camera],
4867 fov_y_rad: f32,
4868 max_outer_steps: u32,
4869 mip_scan_dist: f32,
4870 ) -> Vec<u32> {
4871 self.render_with_transforms(
4872 device,
4873 queue,
4874 scene,
4875 cameras,
4876 &[],
4877 fov_y_rad,
4878 max_outer_steps,
4879 mip_scan_dist,
4880 )
4881 }
4882
4883 #[must_use]
4887 #[allow(clippy::too_many_arguments)]
4888 pub fn render_with_transforms(
4889 &self,
4890 device: &wgpu::Device,
4891 queue: &wgpu::Queue,
4892 scene: &GpuSceneResident,
4893 cameras: &[Camera],
4894 grid_world: &[GridWorldTransform],
4895 fov_y_rad: f32,
4896 max_outer_steps: u32,
4897 mip_scan_dist: f32,
4898 ) -> Vec<u32> {
4899 assert_eq!(
4900 cameras.len(),
4901 scene.grid_count as usize,
4902 "headless render: {} cameras for {} grids",
4903 cameras.len(),
4904 scene.grid_count,
4905 );
4906
4907 let mut cam_vec: Vec<SceneDdaPerGridCamera> = cameras
4908 .iter()
4909 .map(SceneDdaPerGridCamera::from_camera)
4910 .collect();
4911 for (c, t) in cam_vec.iter_mut().zip(grid_world.iter()) {
4913 c.set_world_transform(t);
4914 }
4915 let (dummy_pal, dummy_map) = {
4918 use wgpu::util::DeviceExt;
4919 let pal: Vec<MaterialGpu> = vec![
4920 MaterialGpu {
4921 alpha: 1.0,
4922 mode: 0
4923 };
4924 256
4925 ];
4926 let p = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
4927 label: Some("roxlap-gpu headless.materials_pal"),
4928 contents: bytemuck::cast_slice(&pal),
4929 usage: wgpu::BufferUsages::STORAGE,
4930 });
4931 let m = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
4932 label: Some("roxlap-gpu headless.terrain_map"),
4933 contents: bytemuck::cast_slice(&[[0u32; 2]]),
4934 usage: wgpu::BufferUsages::STORAGE,
4935 });
4936 (p, m)
4937 };
4938 let dl = self.lights.clone();
4943 let (dummy_point_lights, sun_flags, point_count) =
4944 pack_scene_lights(device, &dl, scene.grid_count as usize, &mut cam_vec);
4945 let grid_cameras = upload_grid_cameras(device, &cam_vec);
4946 let uniform = SceneDdaUniform {
4947 fov_y_rad,
4948 grid_count: scene.grid_count,
4949 max_outer_steps,
4950 _pad0: 0,
4951 screen_size: [self.width, self.height],
4952 _pad1: [0; 2],
4953 fog_color: [0.0, 0.0, 0.0, 1.0e29],
4955 fog_far: 1.0e30,
4956 write_depth: 0,
4957 occ_page_words: scene.occupancy_page_words,
4958 occ_num_pages: scene.occupancy_num_pages,
4959 mip_scan_dist,
4960 terrain_has_translucent: 0, terrain_map_count: 0,
4962 _pad4: 0,
4963 sky_cam: SceneDdaPerGridCamera::from_camera(&cameras.first().copied().unwrap_or(
4967 Camera {
4968 position: [0.0; 3],
4969 right: [1.0, 0.0, 0.0],
4970 down: [0.0, 0.0, 1.0],
4971 forward: [0.0, 1.0, 0.0],
4972 fov_y_rad,
4973 },
4974 )),
4975 side_shades0: self.side_shades[0],
4976 side_shades1: self.side_shades[1],
4977 sun_color: [
4979 dl.sun_color[0],
4980 dl.sun_color[1],
4981 dl.sun_color[2],
4982 dl.sun_intensity,
4983 ],
4984 ambient_color: [
4985 dl.ambient[0],
4986 dl.ambient[1],
4987 dl.ambient[2],
4988 dl.shadow_strength,
4989 ],
4990 sun_flags,
4991 point_light_count: point_count,
4992 shadow_max_steps: dl.shadow_max_steps,
4993 _pad5: 0,
4994 shadow_bias: dl.shadow_bias,
4995 shadow_max_dist: dl.shadow_max_dist,
4996 _pad6: [0.0; 2],
4997 shadow_tint: [dl.shadow_tint[0], dl.shadow_tint[1], dl.shadow_tint[2], 0.0],
4998 style_bands: dl.style_bands,
4999 sprite_cast_count: 0, _pad7: [0; 2],
5001 };
5002 queue.write_buffer(&self.uniform_buf, 0, bytemuck::bytes_of(&uniform));
5003
5004 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
5005 label: Some("roxlap-gpu headless.bg"),
5006 layout: &self.bgl,
5007 entries: &[
5008 wgpu::BindGroupEntry {
5009 binding: 0,
5010 resource: self.uniform_buf.as_entire_binding(),
5011 },
5012 wgpu::BindGroupEntry {
5013 binding: 1,
5014 resource: scene.occupancy_pages[0].as_entire_binding(),
5015 },
5016 wgpu::BindGroupEntry {
5017 binding: 2,
5018 resource: scene.all_color_offsets.as_entire_binding(),
5019 },
5020 wgpu::BindGroupEntry {
5021 binding: 3,
5022 resource: scene.all_colors.as_entire_binding(),
5023 },
5024 wgpu::BindGroupEntry {
5025 binding: 4,
5026 resource: scene.all_chunk_colors_base.as_entire_binding(),
5027 },
5028 wgpu::BindGroupEntry {
5029 binding: 5,
5030 resource: scene.all_chunk_occupancy.as_entire_binding(),
5031 },
5032 wgpu::BindGroupEntry {
5033 binding: 6,
5034 resource: scene.grid_static_meta.as_entire_binding(),
5035 },
5036 wgpu::BindGroupEntry {
5037 binding: 7,
5038 resource: scene.all_slot_chunk_idx.as_entire_binding(),
5039 },
5040 wgpu::BindGroupEntry {
5041 binding: 8,
5042 resource: self.framebuffer.as_entire_binding(),
5043 },
5044 wgpu::BindGroupEntry {
5045 binding: 9,
5046 resource: wgpu::BindingResource::TextureView(&self.sky_view),
5047 },
5048 wgpu::BindGroupEntry {
5049 binding: 10,
5050 resource: wgpu::BindingResource::Sampler(&self.sky_sampler),
5051 },
5052 wgpu::BindGroupEntry {
5053 binding: 11,
5054 resource: self.depth_buffer.as_entire_binding(),
5055 },
5056 wgpu::BindGroupEntry {
5057 binding: 12,
5058 resource: scene.occupancy_pages[1].as_entire_binding(),
5059 },
5060 wgpu::BindGroupEntry {
5061 binding: 13,
5062 resource: scene.occupancy_pages[2].as_entire_binding(),
5063 },
5064 wgpu::BindGroupEntry {
5065 binding: 14,
5066 resource: scene.occupancy_pages[3].as_entire_binding(),
5067 },
5068 wgpu::BindGroupEntry {
5069 binding: 15,
5070 resource: grid_cameras.as_entire_binding(),
5071 },
5072 wgpu::BindGroupEntry {
5073 binding: 16,
5074 resource: dummy_pal.as_entire_binding(),
5075 },
5076 wgpu::BindGroupEntry {
5077 binding: 17,
5078 resource: dummy_map.as_entire_binding(),
5079 },
5080 wgpu::BindGroupEntry {
5083 binding: 18,
5084 resource: dummy_point_lights.as_entire_binding(),
5085 },
5086 ],
5087 });
5088
5089 let mut enc =
5090 device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
5091 {
5092 let mut pass = enc.begin_compute_pass(&wgpu::ComputePassDescriptor {
5093 label: Some("roxlap-gpu headless.pass"),
5094 timestamp_writes: None,
5095 });
5096 pass.set_pipeline(&self.pipeline);
5097 pass.set_bind_group(0, &bg, &[]);
5098 pass.dispatch_workgroups(self.width.div_ceil(8), self.height.div_ceil(8), 1);
5099 }
5100 enc.copy_buffer_to_buffer(
5101 &self.framebuffer,
5102 0,
5103 &self.readback,
5104 0,
5105 u64::from(self.width) * u64::from(self.height) * 4,
5106 );
5107 queue.submit(Some(enc.finish()));
5108
5109 let slice = self.readback.slice(..);
5110 let (tx, rx) = std::sync::mpsc::channel();
5111 slice.map_async(wgpu::MapMode::Read, move |r| {
5112 let _ = tx.send(r);
5113 });
5114 device.poll(wgpu::PollType::wait_indefinitely()).ok();
5115 rx.recv().expect("map_async channel").expect("map_async");
5116
5117 let data = slice.get_mapped_range();
5118 let out: Vec<u32> = data
5122 .chunks_exact(4)
5123 .map(|px| u32::from_le_bytes([px[0], px[1], px[2], px[3]]))
5124 .collect();
5125 drop(data);
5126 self.readback.unmap();
5127 out
5128 }
5129}
5130
5131fn bgl_uniform_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
5132 wgpu::BindGroupLayoutEntry {
5133 binding,
5134 visibility: wgpu::ShaderStages::COMPUTE,
5135 ty: wgpu::BindingType::Buffer {
5136 ty: wgpu::BufferBindingType::Uniform,
5137 has_dynamic_offset: false,
5138 min_binding_size: None,
5139 },
5140 count: None,
5141 }
5142}
5143
5144fn bgl_storage_entry(binding: u32, read_only: bool) -> wgpu::BindGroupLayoutEntry {
5145 wgpu::BindGroupLayoutEntry {
5146 binding,
5147 visibility: wgpu::ShaderStages::COMPUTE,
5148 ty: wgpu::BindingType::Buffer {
5149 ty: wgpu::BufferBindingType::Storage { read_only },
5150 has_dynamic_offset: false,
5151 min_binding_size: None,
5152 },
5153 count: None,
5154 }
5155}
5156
5157fn create_sky_texture(
5162 device: &wgpu::Device,
5163 width: u32,
5164 height: u32,
5165 _initial_pixels: &[u8],
5166) -> (wgpu::Texture, wgpu::TextureView) {
5167 let tex = device.create_texture(&wgpu::TextureDescriptor {
5168 label: Some("roxlap-gpu sky_texture"),
5169 size: wgpu::Extent3d {
5170 width,
5171 height,
5172 depth_or_array_layers: 1,
5173 },
5174 mip_level_count: 1,
5175 sample_count: 1,
5176 dimension: wgpu::TextureDimension::D2,
5177 format: wgpu::TextureFormat::Rgba8Unorm,
5178 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
5179 view_formats: &[],
5180 });
5181 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
5182 (tex, view)
5183}
5184
5185pub(crate) fn pick_required_limits(adapter_limits: &wgpu::Limits) -> wgpu::Limits {
5193 wgpu::Limits {
5194 max_storage_buffer_binding_size: adapter_limits.max_storage_buffer_binding_size,
5195 max_buffer_size: adapter_limits.max_buffer_size,
5196 max_storage_buffers_per_shader_stage: adapter_limits
5205 .max_storage_buffers_per_shader_stage
5206 .min(SPRITE_SHADOW_MIN_STORAGE_BUFFERS),
5207 ..wgpu::Limits::default()
5208 }
5209}
5210
5211fn sprite_shader_source(capable: bool) -> String {
5218 let base = include_str!("../shaders/sprite_model_dda.wgsl");
5219 if !capable {
5220 return base.to_string();
5221 }
5222 let snippet = include_str!("../shaders/sprite_terrain_shadow.wgsl");
5223 const BEGIN: &str = "//XS4_STUB_BEGIN";
5224 const END: &str = "//XS4_STUB_END";
5225 let (Some(b), Some(e)) = (base.find(BEGIN), base.find(END)) else {
5226 panic!("sprite_model_dda.wgsl: XS4 stub markers not found");
5228 };
5229 let e_end = e + END.len();
5230 let mut out = String::with_capacity(base.len() + snippet.len());
5231 out.push_str(&base[..b]);
5232 out.push_str(snippet);
5233 out.push_str(&base[e_end..]);
5234 out
5235}
5236
5237fn scene_shader_source(capable: bool) -> String {
5243 let base = include_str!("../shaders/scene_dda.wgsl");
5244 if !capable {
5245 return base.to_string();
5246 }
5247 let snippet = include_str!("../shaders/scene_sprite_shadow.wgsl");
5248 const BEGIN: &str = "//XS4C_STUB_BEGIN";
5249 const END: &str = "//XS4C_STUB_END";
5250 let (Some(b), Some(e)) = (base.find(BEGIN), base.find(END)) else {
5251 panic!("scene_dda.wgsl: XS4C stub markers not found");
5252 };
5253 let e_end = e + END.len();
5254 let mut out = String::with_capacity(base.len() + snippet.len());
5255 out.push_str(&base[..b]);
5256 out.push_str(snippet);
5257 out.push_str(&base[e_end..]);
5258 out
5259}
5260
5261pub(crate) const SPRITE_SHADOW_MIN_STORAGE_BUFFERS: u32 = 22;
5266
5267fn pick_present_mode(modes: &[wgpu::PresentMode]) -> wgpu::PresentMode {
5268 for &m in &[wgpu::PresentMode::Mailbox, wgpu::PresentMode::Immediate] {
5271 if modes.contains(&m) {
5272 return m;
5273 }
5274 }
5275 wgpu::PresentMode::Fifo
5276}
5277
5278#[must_use]
5284#[allow(clippy::too_many_arguments)]
5285pub fn pinhole_pixel_ray(
5286 right: [f64; 3],
5287 down: [f64; 3],
5288 forward: [f64; 3],
5289 x: f64,
5290 y: f64,
5291 w: f64,
5292 h: f64,
5293 fov_y_rad: f64,
5294) -> [f64; 3] {
5295 let half_h = (fov_y_rad * 0.5).tan();
5296 let half_w = half_h * (w / h);
5297 let ndc_x = (x + 0.5) / w * 2.0 - 1.0;
5298 let ndc_y_top = 1.0 - (y + 0.5) / h * 2.0;
5299 let (kx, ky) = (ndc_x * half_w, ndc_y_top * half_h);
5300 [
5301 forward[0] + kx * right[0] - ky * down[0],
5302 forward[1] + kx * right[1] - ky * down[1],
5303 forward[2] + kx * right[2] - ky * down[2],
5304 ]
5305}
5306
5307#[cfg(test)]
5308mod pixel_ray_tests {
5309 use super::pinhole_pixel_ray;
5310
5311 const RIGHT: [f64; 3] = [1.0, 0.0, 0.0];
5312 const DOWN: [f64; 3] = [0.0, 1.0, 0.0];
5313 const FWD: [f64; 3] = [0.0, 0.0, 1.0]; #[test]
5317 fn centre_pixel_is_forward() {
5318 let d = pinhole_pixel_ray(
5319 RIGHT,
5320 DOWN,
5321 FWD,
5322 639.5,
5323 359.5,
5324 1280.0,
5325 720.0,
5326 60_f64.to_radians(),
5327 );
5328 assert!(
5329 d[0].abs() < 1e-9 && d[1].abs() < 1e-9,
5330 "centre ≈ forward, got {d:?}"
5331 );
5332 assert!((d[2] - 1.0).abs() < 1e-9);
5333 }
5334
5335 #[test]
5338 fn right_edge_tilts_by_half_w() {
5339 let fov = 60_f64.to_radians();
5340 let d = pinhole_pixel_ray(RIGHT, DOWN, FWD, 1279.5, 359.5, 1280.0, 720.0, fov);
5341 let half_w = (fov * 0.5).tan() * (1280.0 / 720.0);
5342 assert!((d[0] - half_w).abs() < 1e-6, "x={}, half_w={half_w}", d[0]);
5343 assert!(d[0] > 0.0, "right edge tilts +right");
5344 }
5345
5346 #[test]
5351 fn wgsl_shaders_validate() {
5352 let shaders: &[(&str, &str)] = &[
5353 (
5354 "sprite_model_dda.wgsl",
5355 include_str!("../shaders/sprite_model_dda.wgsl"),
5356 ),
5357 ("scene_dda.wgsl", include_str!("../shaders/scene_dda.wgsl")),
5358 ("blit.wgsl", include_str!("../shaders/blit.wgsl")),
5359 ("chunk_dda.wgsl", include_str!("../shaders/chunk_dda.wgsl")),
5360 ("grid_dda.wgsl", include_str!("../shaders/grid_dda.wgsl")),
5361 (
5362 "scene_blit.wgsl",
5363 include_str!("../shaders/scene_blit.wgsl"),
5364 ),
5365 (
5366 "scene_resolve.wgsl",
5367 include_str!("../shaders/scene_resolve.wgsl"),
5368 ),
5369 ("line.wgsl", include_str!("../shaders/line.wgsl")),
5370 ("image.wgsl", include_str!("../shaders/image.wgsl")),
5371 ];
5372 let mut validator = naga::valid::Validator::new(
5373 naga::valid::ValidationFlags::all(),
5374 naga::valid::Capabilities::all(),
5375 );
5376 for (name, src) in shaders {
5377 let module = naga::front::wgsl::parse_str(src).unwrap_or_else(|e| {
5378 panic!("{name}: WGSL parse failed:\n{}", e.emit_to_string(src))
5379 });
5380 validator
5381 .validate(&module)
5382 .unwrap_or_else(|e| panic!("{name}: WGSL validation failed: {e:?}"));
5383 }
5384 let capable = super::sprite_shader_source(true);
5388 let module = naga::front::wgsl::parse_str(&capable).unwrap_or_else(|e| {
5389 panic!(
5390 "sprite_model_dda.wgsl (capable): parse failed:\n{}",
5391 e.emit_to_string(&capable)
5392 )
5393 });
5394 validator.validate(&module).unwrap_or_else(|e| {
5395 panic!("sprite_model_dda.wgsl (capable): validation failed: {e:?}")
5396 });
5397 let scene_cap = super::scene_shader_source(true);
5399 let module = naga::front::wgsl::parse_str(&scene_cap).unwrap_or_else(|e| {
5400 panic!(
5401 "scene_dda.wgsl (capable): parse failed:\n{}",
5402 e.emit_to_string(&scene_cap)
5403 )
5404 });
5405 validator
5406 .validate(&module)
5407 .unwrap_or_else(|e| panic!("scene_dda.wgsl (capable): validation failed: {e:?}"));
5408 }
5409
5410 #[test]
5415 fn image_vertices_carry_forward_w_and_euclidean_depth() {
5416 let cam = crate::GpuLineCamera {
5417 pos: [0.0, 0.0, 0.0],
5418 right: [1.0, 0.0, 0.0],
5419 down: [0.0, 1.0, 0.0],
5420 forward: [0.0, 0.0, 1.0],
5421 };
5422 let quad = crate::GpuImageQuad {
5424 corners: [
5425 [-1.0, -1.0, 10.0], [1.0, -1.0, 10.0], [-1.0, 1.0, 10.0], [1.0, 1.0, 10.0], ],
5430 image: 0,
5431 tint: [1.0, 1.0, 1.0, 1.0],
5432 depth_test: true,
5433 alpha_cutoff: 0.0,
5434 };
5435 let verts = crate::build_image_vertices(&cam, &quad, 800, 600, 60_f32.to_radians(), false);
5436 assert_eq!(verts.len(), 6, "two triangles, no near-clip");
5437 for v in &verts {
5438 assert!((v.w - 10.0).abs() < 1e-4, "w == forward distance");
5439 assert!(v.depth >= 10.0, "euclidean depth >= forward distance");
5440 assert_eq!(v.depth_test, 1.0);
5441 }
5442 }
5443}