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}
896
897#[derive(Clone, Default)]
903pub struct SceneLights {
904 pub enabled: bool,
910 pub grid_sun_dirs: Vec<[f32; 3]>,
912 pub sun_color: [f32; 3],
913 pub sun_intensity: f32,
914 pub sun_casts_shadow: bool,
915 pub grid_point_lights: Vec<Vec<GpuLight>>,
918 pub ambient: [f32; 3],
920 pub shadow_strength: f32,
921 pub shadow_bias: f32,
922 pub shadow_max_dist: f32,
923 pub shadow_max_steps: u32,
924 pub world_sun_dir: [f32; 3],
929 pub world_points: Vec<GpuLight>,
932 pub style_bands: u32,
935 pub shadow_tint: [f32; 3],
937}
938
939#[repr(C)]
942#[derive(Clone, Copy, Pod, Zeroable)]
943struct GpuPointLight {
944 pos: [f32; 3],
945 radius: f32,
946 color: [f32; 3],
947 intensity: f32,
948 casts_shadow: u32,
949 _pad: [u32; 3],
950}
951
952fn upload_grid_point_lights(device: &wgpu::Device, lights: &[GpuPointLight]) -> wgpu::Buffer {
956 use wgpu::util::DeviceExt;
957 let one = [GpuPointLight::zeroed()];
958 let src: &[GpuPointLight] = if lights.is_empty() { &one } else { lights };
959 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
960 label: Some("roxlap-gpu scene_dda.grid_point_lights"),
961 contents: bytemuck::cast_slice(src),
962 usage: wgpu::BufferUsages::STORAGE,
963 })
964}
965
966fn pack_scene_lights(
974 device: &wgpu::Device,
975 lights: &SceneLights,
976 grid_count: usize,
977 cam_vec: &mut [SceneDdaPerGridCamera],
978) -> (wgpu::Buffer, u32, u32) {
979 let sun_enabled = !lights.grid_sun_dirs.is_empty();
980 if sun_enabled {
981 for (g, cam) in cam_vec.iter_mut().enumerate() {
982 let d = lights.grid_sun_dirs.get(g).copied().unwrap_or([0.0; 3]);
983 cam.sun_dir = [d[0], d[1], d[2], 0.0];
984 }
985 }
986 let mut point_count = lights
988 .grid_point_lights
989 .first()
990 .map_or(0, std::vec::Vec::len);
991 if point_count > MAX_POINT_LIGHTS {
992 eprintln!(
993 "roxlap-gpu: {point_count} point lights > MAX_POINT_LIGHTS ({MAX_POINT_LIGHTS}); dropping the excess"
994 );
995 point_count = MAX_POINT_LIGHTS;
996 }
997 let mut budget = MAX_SHADOW_CASTERS;
1003 if sun_enabled && lights.sun_casts_shadow {
1004 budget = budget.saturating_sub(1);
1005 }
1006 let mut allow_shadow = vec![false; point_count];
1007 let mut demoted = 0usize;
1008 if let Some(rep) = lights.grid_point_lights.first() {
1009 for (i, slot) in allow_shadow.iter_mut().enumerate() {
1010 if rep.get(i).is_some_and(|l| l.casts_shadow) {
1011 if budget > 0 {
1012 *slot = true;
1013 budget -= 1;
1014 } else {
1015 demoted += 1;
1016 }
1017 }
1018 }
1019 }
1020 if demoted > 0 {
1021 eprintln!(
1022 "roxlap-gpu: {demoted} shadow-casting point lights > MAX_SHADOW_CASTERS ({MAX_SHADOW_CASTERS}); demoting the excess to shadowless"
1023 );
1024 }
1025 let mut packed: Vec<GpuPointLight> = Vec::with_capacity(grid_count * point_count);
1027 for g in 0..grid_count {
1028 let row = lights.grid_point_lights.get(g);
1029 for (i, &allow) in allow_shadow.iter().enumerate() {
1030 let p = row.and_then(|r| r.get(i));
1031 packed.push(p.map_or(GpuPointLight::zeroed(), |l| GpuPointLight {
1032 pos: l.position,
1033 radius: l.radius,
1034 color: l.color,
1035 intensity: l.intensity,
1036 casts_shadow: u32::from(l.casts_shadow && allow),
1037 _pad: [0; 3],
1038 }));
1039 }
1040 }
1041 let buf = upload_grid_point_lights(device, &packed);
1042 let sun_flags = u32::from(sun_enabled)
1043 | (u32::from(sun_enabled && lights.sun_casts_shadow) << 1)
1044 | (u32::from(lights.enabled) << 2);
1045 (buf, sun_flags, point_count as u32)
1046}
1047
1048fn upload_grid_cameras(device: &wgpu::Device, cams: &[SceneDdaPerGridCamera]) -> wgpu::Buffer {
1055 use wgpu::util::DeviceExt;
1056 let one = [SceneDdaPerGridCamera::zeroed()];
1057 let src: &[SceneDdaPerGridCamera] = if cams.is_empty() { &one } else { cams };
1058 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1059 label: Some("roxlap-gpu scene_dda.grid_cameras"),
1060 contents: bytemuck::cast_slice(src),
1061 usage: wgpu::BufferUsages::STORAGE,
1062 })
1063}
1064
1065const _: () = assert!(scene::MAX_OCC_PAGES == 4);
1070
1071#[repr(C)]
1072#[derive(Clone, Copy, Pod, Zeroable)]
1073struct SceneDdaPerGridCamera {
1074 pos: [f32; 3],
1075 _pad0: f32,
1076 right: [f32; 3],
1077 _pad1: f32,
1078 down: [f32; 3],
1079 _pad2: f32,
1080 forward: [f32; 3],
1081 _pad3: f32,
1082 sun_dir: [f32; 4],
1087 world_origin: [f32; 4],
1093 rot0: [f32; 4],
1094 rot1: [f32; 4],
1095 rot2: [f32; 4],
1096}
1097
1098impl SceneDdaPerGridCamera {
1099 fn from_camera(c: &Camera) -> Self {
1100 Self {
1101 pos: c.position,
1102 _pad0: 0.0,
1103 right: c.right,
1104 _pad1: 0.0,
1105 down: c.down,
1106 _pad2: 0.0,
1107 forward: c.forward,
1108 _pad3: 0.0,
1109 sun_dir: [0.0; 4],
1110 world_origin: [0.0; 4],
1113 rot0: [1.0, 0.0, 0.0, 0.0],
1114 rot1: [0.0, 1.0, 0.0, 0.0],
1115 rot2: [0.0, 0.0, 1.0, 0.0],
1116 }
1117 }
1118
1119 fn set_world_transform(&mut self, t: &GridWorldTransform) {
1123 self.world_origin = [t.origin[0], t.origin[1], t.origin[2], 0.0];
1124 self.rot0 = [t.rot_cols[0][0], t.rot_cols[0][1], t.rot_cols[0][2], 0.0];
1125 self.rot1 = [t.rot_cols[1][0], t.rot_cols[1][1], t.rot_cols[1][2], 0.0];
1126 self.rot2 = [t.rot_cols[2][0], t.rot_cols[2][1], t.rot_cols[2][2], 0.0];
1127 }
1128}
1129
1130#[derive(Clone, Copy)]
1135pub struct GridWorldTransform {
1136 pub origin: [f32; 3],
1137 pub rot_cols: [[f32; 3]; 3],
1138}
1139
1140impl Default for GridWorldTransform {
1141 fn default() -> Self {
1142 Self {
1143 origin: [0.0; 3],
1144 rot_cols: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
1145 }
1146 }
1147}
1148
1149#[repr(C)]
1150#[derive(Clone, Copy, Pod, Zeroable)]
1151struct SceneDdaUniform {
1152 fov_y_rad: f32,
1153 grid_count: u32,
1154 max_outer_steps: u32,
1155 _pad0: u32,
1156 screen_size: [u32; 2],
1157 _pad1: [u32; 2],
1158 fog_color: [f32; 4],
1162 fog_far: f32,
1163 write_depth: u32,
1166 occ_page_words: u32,
1170 occ_num_pages: u32,
1173 mip_scan_dist: f32,
1178 terrain_has_translucent: u32,
1181 terrain_map_count: u32,
1183 _pad4: u32,
1184 sky_cam: SceneDdaPerGridCamera,
1189 side_shades0: [i32; 4],
1194 side_shades1: [i32; 4],
1195 sun_color: [f32; 4],
1198 ambient_color: [f32; 4],
1200 sun_flags: u32,
1202 point_light_count: u32,
1204 shadow_max_steps: u32,
1206 _pad5: u32,
1207 shadow_bias: f32,
1209 shadow_max_dist: f32,
1211 _pad6: [f32; 2],
1212 shadow_tint: [f32; 4],
1214 style_bands: u32,
1216 sprite_cast_count: u32,
1220 _pad7: [u32; 2],
1221}
1222
1223#[repr(C)]
1224#[derive(Clone, Copy, Pod, Zeroable)]
1225struct GridDdaUniform {
1226 camera_pos: [f32; 3],
1227 _pad0: f32,
1228 camera_right: [f32; 3],
1229 _pad1: f32,
1230 camera_down: [f32; 3],
1231 _pad2: f32,
1232 camera_forward: [f32; 3],
1233 fov_y_rad: f32,
1234 screen_size: [u32; 2],
1235 vsid: u32,
1236 max_outer_steps: u32,
1237 chunks_dims: [u32; 3],
1238 _pad3: u32,
1239 origin_chunk: [i32; 3],
1240 _pad4: u32,
1241}
1242
1243#[repr(C)]
1244#[derive(Clone, Copy, Pod, Zeroable)]
1245struct ChunkDdaUniform {
1246 camera_pos: [f32; 3],
1247 _pad0: f32,
1248 camera_right: [f32; 3],
1249 _pad1: f32,
1250 camera_down: [f32; 3],
1251 _pad2: f32,
1252 camera_forward: [f32; 3],
1253 fov_y_rad: f32,
1254 screen_size: [u32; 2],
1255 vsid: u32,
1256 max_scan_dist: u32,
1257}
1258
1259impl GpuRenderer {
1260 pub async fn new<W>(
1275 window: Arc<W>,
1276 size: (u32, u32),
1277 settings: GpuRendererSettings,
1278 ) -> Result<Self, GpuInitError>
1279 where
1280 W: HasWindowHandle + HasDisplayHandle + Send + Sync + 'static,
1281 {
1282 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
1283 let surface = instance.create_surface(window.clone())?;
1284 let adapter = Self::request_adapter(&instance, Some(&surface), settings).await?;
1285 let (device, queue) = Self::request_device(&adapter).await?;
1286 Ok(Self::finish_init(
1287 &adapter, device, queue, surface, size, settings,
1288 ))
1289 }
1290
1291 #[cfg(target_arch = "wasm32")]
1308 pub async fn new_from_canvas(
1309 canvas: web_sys::HtmlCanvasElement,
1310 size: (u32, u32),
1311 settings: GpuRendererSettings,
1312 ) -> Result<Self, GpuInitError> {
1313 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
1314 let adapter = Self::request_adapter(&instance, None, settings).await?;
1320 let (device, queue) = Self::request_device(&adapter).await?;
1321 let surface = instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas))?;
1322 Ok(Self::finish_init(
1323 &adapter, device, queue, surface, size, settings,
1324 ))
1325 }
1326
1327 async fn request_adapter(
1333 instance: &wgpu::Instance,
1334 compatible_surface: Option<&wgpu::Surface<'static>>,
1335 settings: GpuRendererSettings,
1336 ) -> Result<wgpu::Adapter, GpuInitError> {
1337 let power_preference = match settings.power_preference {
1338 PowerPreference::Low => wgpu::PowerPreference::LowPower,
1339 PowerPreference::High => wgpu::PowerPreference::HighPerformance,
1340 };
1341 instance
1342 .request_adapter(&wgpu::RequestAdapterOptions {
1343 power_preference,
1344 compatible_surface,
1345 force_fallback_adapter: false,
1346 })
1347 .await
1348 .map_err(|_| GpuInitError::NoAdapter)
1349 }
1350
1351 async fn request_device(
1358 adapter: &wgpu::Adapter,
1359 ) -> Result<(wgpu::Device, wgpu::Queue), GpuInitError> {
1360 Ok(adapter
1361 .request_device(&wgpu::DeviceDescriptor {
1362 label: Some("roxlap-gpu device"),
1363 required_features: wgpu::Features::empty(),
1364 required_limits: pick_required_limits(&adapter.limits()),
1365 experimental_features: wgpu::ExperimentalFeatures::disabled(),
1366 memory_hints: wgpu::MemoryHints::default(),
1367 trace: wgpu::Trace::Off,
1368 })
1369 .await?)
1370 }
1371
1372 fn finish_init(
1377 adapter: &wgpu::Adapter,
1378 device: wgpu::Device,
1379 queue: wgpu::Queue,
1380 surface: wgpu::Surface<'static>,
1381 size: (u32, u32),
1382 settings: GpuRendererSettings,
1383 ) -> Self {
1384 let info = adapter.get_info();
1385 let adapter_info = format!(
1386 "{name} ({backend:?}, {device_type:?})",
1387 name = info.name,
1388 backend = info.backend,
1389 device_type = info.device_type,
1390 );
1391
1392 let caps = surface.get_capabilities(adapter);
1393 let surface_format = caps
1405 .formats
1406 .iter()
1407 .copied()
1408 .find(|f| {
1409 matches!(
1410 f,
1411 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Rgba8Unorm
1412 )
1413 })
1414 .or_else(|| caps.formats.iter().copied().find(|f| !f.is_srgb()))
1415 .unwrap_or(caps.formats[0]);
1416 let present_mode = if settings.uncapped_present {
1417 pick_present_mode(&caps.present_modes)
1418 } else {
1419 wgpu::PresentMode::Fifo
1420 };
1421 eprintln!(
1426 "roxlap-gpu: present mode = {present_mode:?} (available: {:?})",
1427 caps.present_modes,
1428 );
1429 let (init_w, init_h) = size;
1430 let surface_config = wgpu::SurfaceConfiguration {
1431 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1432 format: surface_format,
1433 width: init_w.max(1),
1434 height: init_h.max(1),
1435 present_mode,
1436 alpha_mode: caps.alpha_modes[0],
1437 view_formats: vec![],
1438 desired_maximum_frame_latency: 2,
1439 };
1440 surface.configure(&device, &surface_config);
1441
1442 let default_sky_pixel = [0x80u8, 0x80, 0x80, 0xff];
1447 let (sky_texture, sky_view) = create_sky_texture(&device, 1, 1, &default_sky_pixel);
1448 queue.write_texture(
1449 wgpu::TexelCopyTextureInfo {
1450 texture: &sky_texture,
1451 mip_level: 0,
1452 origin: wgpu::Origin3d::ZERO,
1453 aspect: wgpu::TextureAspect::All,
1454 },
1455 &default_sky_pixel,
1456 wgpu::TexelCopyBufferLayout {
1457 offset: 0,
1458 bytes_per_row: Some(4),
1459 rows_per_image: Some(1),
1460 },
1461 wgpu::Extent3d {
1462 width: 1,
1463 height: 1,
1464 depth_or_array_layers: 1,
1465 },
1466 );
1467 let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1468 label: Some("roxlap-gpu sky_sampler"),
1469 address_mode_u: wgpu::AddressMode::Repeat,
1473 address_mode_v: wgpu::AddressMode::Repeat,
1474 address_mode_w: wgpu::AddressMode::ClampToEdge,
1475 mag_filter: wgpu::FilterMode::Linear,
1476 min_filter: wgpu::FilterMode::Linear,
1477 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
1478 ..Default::default()
1479 });
1480
1481 let sprite_shadows_capable = device.limits().max_storage_buffers_per_shader_stage
1485 >= SPRITE_SHADOW_MIN_STORAGE_BUFFERS;
1486
1487 Self {
1488 surface,
1489 surface_config,
1490 device,
1491 queue,
1492 adapter_info,
1493 clear_colour: settings.clear_colour,
1494 frame_count: 0,
1495 flip_x: false,
1496 render_res: RenderResolution::Native,
1497 ssaa: 1,
1498 posterize: None,
1499 chunk_dda: None,
1500 grid_dda: None,
1501 scene_dda: None,
1502 scene_materials: Box::new(
1503 [MaterialGpu {
1504 alpha: 1.0,
1505 mode: 0,
1506 }; 256],
1507 ),
1508 scene_terrain_map: Vec::new(),
1509 scene_terrain_translucent: false,
1510 scene_depth_valid: false,
1511 sky_texture,
1512 sky_view,
1513 sky_sampler,
1514 fog_color: [0.66, 0.74, 0.88],
1519 fog_near: 0.0,
1520 fog_far: 1.0e30,
1521 sprite_registry: None,
1522 sprite_model_dda: None,
1523 sprite_shadows_capable,
1524 sprite_materials: Box::new(
1525 [MaterialGpu {
1526 alpha: 1.0,
1527 mode: 0,
1528 }; 256],
1529 ),
1530 sprite_has_translucent: false,
1531 sprite_lod_px: 4.0,
1535 scene_mip_scan_dist: 64.0,
1537 scene_side_shades: [[0; 4]; 2],
1538 scene_lights: SceneLights::default(),
1539 last_fov_y_rad: 0.0,
1540 pending_frame: None,
1541 line_resources: None,
1542 line_vbuf: None,
1543 line_vbuf_cap: 0,
1544 image_resources: None,
1545 image_vbuf: None,
1546 image_vbuf_cap: 0,
1547 images: Vec::new(),
1548 #[cfg(feature = "hud")]
1549 egui_renderer: None,
1550 }
1551 }
1552
1553 #[cfg(not(target_arch = "wasm32"))]
1559 pub fn new_blocking<W>(
1560 window: Arc<W>,
1561 size: (u32, u32),
1562 settings: GpuRendererSettings,
1563 ) -> Result<Self, GpuInitError>
1564 where
1565 W: HasWindowHandle + HasDisplayHandle + Send + Sync + 'static,
1566 {
1567 pollster::block_on(Self::new(window, size, settings))
1568 }
1569
1570 pub fn adapter_info(&self) -> &str {
1573 &self.adapter_info
1574 }
1575
1576 pub fn device(&self) -> &wgpu::Device {
1579 &self.device
1580 }
1581
1582 #[must_use]
1587 pub fn sprite_shadows_capable(&self) -> bool {
1588 self.sprite_shadows_capable
1589 }
1590
1591 pub fn queue(&self) -> &wgpu::Queue {
1594 &self.queue
1595 }
1596
1597 pub fn set_flip_x(&mut self, flip: bool) {
1607 self.flip_x = flip;
1608 }
1609
1610 pub fn set_sky_panorama(&mut self, rgba: &[u8], width: u32, height: u32) {
1614 assert_eq!(
1615 rgba.len(),
1616 (width as usize) * (height as usize) * 4,
1617 "set_sky_panorama: expected w*h*4 bytes, got {}",
1618 rgba.len(),
1619 );
1620 let (tex, view) = create_sky_texture(&self.device, width, height, rgba);
1621 self.queue.write_texture(
1624 wgpu::TexelCopyTextureInfo {
1625 texture: &tex,
1626 mip_level: 0,
1627 origin: wgpu::Origin3d::ZERO,
1628 aspect: wgpu::TextureAspect::All,
1629 },
1630 rgba,
1631 wgpu::TexelCopyBufferLayout {
1632 offset: 0,
1633 bytes_per_row: Some(width * 4),
1634 rows_per_image: Some(height),
1635 },
1636 wgpu::Extent3d {
1637 width,
1638 height,
1639 depth_or_array_layers: 1,
1640 },
1641 );
1642 self.sky_texture = tex;
1643 self.sky_view = view;
1644 }
1645
1646 pub fn set_fog(&mut self, color: [f32; 3], near: f32, far: f32) {
1652 self.fog_color = color;
1653 self.fog_near = near;
1654 self.fog_far = far.max(near + 1.0);
1655 }
1656
1657 pub fn resize(&mut self, width: u32, height: u32) {
1661 if width == 0 || height == 0 {
1662 return;
1663 }
1664 self.surface_config.width = width;
1665 self.surface_config.height = height;
1666 self.surface.configure(&self.device, &self.surface_config);
1667 self.chunk_dda = None;
1668 self.grid_dda = None;
1669 self.scene_dda = None;
1670 }
1671
1672 pub fn set_render_resolution(&mut self, res: RenderResolution) {
1676 self.render_res = res;
1677 }
1678
1679 pub fn set_ssaa(&mut self, factor: u8) {
1681 self.ssaa = u32::from(factor).clamp(1, 4);
1682 }
1683
1684 pub fn set_posterize(&mut self, cfg: Option<PosterizeGpu>) {
1687 self.posterize = cfg;
1688 }
1689
1690 #[must_use]
1694 pub fn logical_dims(&self) -> (u32, u32) {
1695 self.render_res.logical_for(self.surface_dims())
1696 }
1697
1698 #[must_use]
1701 pub fn render_dims(&self) -> (u32, u32) {
1702 let (lw, lh) = self.logical_dims();
1703 (lw * self.ssaa, lh * self.ssaa)
1704 }
1705
1706 #[must_use]
1708 pub fn surface_dims(&self) -> (u32, u32) {
1709 (self.surface_config.width, self.surface_config.height)
1710 }
1711
1712 fn acquire_frame(&self) -> Option<wgpu::SurfaceTexture> {
1718 use wgpu::CurrentSurfaceTexture as C;
1719 match self.surface.get_current_texture() {
1720 C::Success(t) | C::Suboptimal(t) => Some(t),
1721 C::Outdated | C::Lost => {
1722 self.surface.configure(&self.device, &self.surface_config);
1723 None
1724 }
1725 C::Timeout | C::Occluded | C::Validation => None,
1726 }
1727 }
1728
1729 pub fn render(&mut self) {
1733 let Some(surf_tex) = self.acquire_frame() else {
1734 return;
1735 };
1736 let view = surf_tex
1737 .texture
1738 .create_view(&wgpu::TextureViewDescriptor::default());
1739
1740 let phase = f64::from(self.frame_count % 1257) * 0.005;
1744 let [r, g, b] = self.clear_colour;
1745 let drift = (phase.sin() * 0.04 + 0.04).clamp(0.0, 0.1);
1746 let clear = wgpu::Color {
1747 r: (r + drift).clamp(0.0, 1.0),
1748 g: (g + drift * 0.5).clamp(0.0, 1.0),
1749 b: (b + drift * 0.25).clamp(0.0, 1.0),
1750 a: 1.0,
1751 };
1752
1753 let mut encoder = self
1754 .device
1755 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1756 label: Some("roxlap-gpu encoder"),
1757 });
1758 {
1759 let _rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1760 label: Some("roxlap-gpu clear"),
1761 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1762 view: &view,
1763 depth_slice: None,
1764 resolve_target: None,
1765 ops: wgpu::Operations {
1766 load: wgpu::LoadOp::Clear(clear),
1767 store: wgpu::StoreOp::Store,
1768 },
1769 })],
1770 depth_stencil_attachment: None,
1771 timestamp_writes: None,
1772 occlusion_query_set: None,
1773 multiview_mask: None,
1774 });
1775 }
1776 self.queue.submit(std::iter::once(encoder.finish()));
1777 surf_tex.present();
1778 self.frame_count = self.frame_count.wrapping_add(1);
1779 }
1780
1781 pub fn render_chunk(
1793 &mut self,
1794 resident: &GpuChunkResident,
1795 camera: &Camera,
1796 max_scan_dist: u32,
1797 ) {
1798 let Some(surf_tex) = self.acquire_frame() else {
1799 return;
1800 };
1801 let surf_view = surf_tex
1802 .texture
1803 .create_view(&wgpu::TextureViewDescriptor::default());
1804
1805 let surface_w = self.surface_config.width;
1806 let surface_h = self.surface_config.height;
1807 let surface_format = self.surface_config.format;
1808
1809 let needs_build = match &self.chunk_dda {
1812 Some(r) => r.storage_size != (surface_w, surface_h),
1813 None => true,
1814 };
1815 if needs_build {
1816 self.chunk_dda = Some(self.build_chunk_dda(surface_w, surface_h, surface_format));
1817 }
1818 let dda = self.chunk_dda.as_ref().expect("just built");
1819
1820 let uniform = ChunkDdaUniform {
1822 camera_pos: camera.position,
1823 _pad0: 0.0,
1824 camera_right: camera.right,
1825 _pad1: 0.0,
1826 camera_down: camera.down,
1827 _pad2: 0.0,
1828 camera_forward: camera.forward,
1829 fov_y_rad: camera.fov_y_rad,
1830 screen_size: [surface_w, surface_h],
1831 vsid: resident.vsid,
1832 max_scan_dist,
1833 };
1834 self.queue
1835 .write_buffer(&dda.uniform_buf, 0, bytemuck::bytes_of(&uniform));
1836
1837 let dda_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1841 label: Some("roxlap-gpu chunk_dda.bg"),
1842 layout: &dda.bgl_dda,
1843 entries: &[
1844 wgpu::BindGroupEntry {
1845 binding: 0,
1846 resource: dda.uniform_buf.as_entire_binding(),
1847 },
1848 wgpu::BindGroupEntry {
1849 binding: 1,
1850 resource: resident.occupancy.as_entire_binding(),
1851 },
1852 wgpu::BindGroupEntry {
1853 binding: 2,
1854 resource: resident.color_offsets.as_entire_binding(),
1855 },
1856 wgpu::BindGroupEntry {
1857 binding: 3,
1858 resource: resident.colors.as_entire_binding(),
1859 },
1860 wgpu::BindGroupEntry {
1861 binding: 4,
1862 resource: wgpu::BindingResource::TextureView(&dda.storage_view),
1863 },
1864 ],
1865 });
1866
1867 let mut encoder = self
1868 .device
1869 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1870 label: Some("roxlap-gpu chunk encoder"),
1871 });
1872 {
1873 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1874 label: Some("roxlap-gpu chunk_dda compute"),
1875 timestamp_writes: None,
1876 });
1877 cpass.set_pipeline(&dda.pipeline_dda);
1878 cpass.set_bind_group(0, &dda_bg, &[]);
1879 cpass.dispatch_workgroups(surface_w.div_ceil(8), surface_h.div_ceil(8), 1);
1880 }
1881 {
1882 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1883 label: Some("roxlap-gpu chunk_dda blit"),
1884 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1885 view: &surf_view,
1886 depth_slice: None,
1887 resolve_target: None,
1888 ops: wgpu::Operations {
1889 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1890 store: wgpu::StoreOp::Store,
1891 },
1892 })],
1893 depth_stencil_attachment: None,
1894 timestamp_writes: None,
1895 occlusion_query_set: None,
1896 multiview_mask: None,
1897 });
1898 rpass.set_pipeline(&dda.pipeline_blit);
1899 rpass.set_bind_group(0, &dda.blit_bg, &[]);
1900 rpass.draw(0..3, 0..1);
1901 }
1902 self.queue.submit(std::iter::once(encoder.finish()));
1903 surf_tex.present();
1904 self.frame_count = self.frame_count.wrapping_add(1);
1905 }
1906
1907 fn build_chunk_dda(
1908 &self,
1909 width: u32,
1910 height: u32,
1911 surface_format: wgpu::TextureFormat,
1912 ) -> ChunkDdaResources {
1913 let storage_tex = self.device.create_texture(&wgpu::TextureDescriptor {
1914 label: Some("roxlap-gpu chunk_dda.storage"),
1915 size: wgpu::Extent3d {
1916 width,
1917 height,
1918 depth_or_array_layers: 1,
1919 },
1920 mip_level_count: 1,
1921 sample_count: 1,
1922 dimension: wgpu::TextureDimension::D2,
1923 format: wgpu::TextureFormat::Rgba8Unorm,
1924 usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
1925 view_formats: &[],
1926 });
1927 let storage_view = storage_tex.create_view(&wgpu::TextureViewDescriptor::default());
1928
1929 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
1930 label: Some("roxlap-gpu chunk_dda.uniform"),
1931 size: std::mem::size_of::<ChunkDdaUniform>() as u64,
1932 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1933 mapped_at_creation: false,
1934 });
1935
1936 let dda_shader = self
1937 .device
1938 .create_shader_module(wgpu::ShaderModuleDescriptor {
1939 label: Some("chunk_dda.wgsl"),
1940 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/chunk_dda.wgsl").into()),
1941 });
1942 let bgl_dda = self
1943 .device
1944 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1945 label: Some("roxlap-gpu chunk_dda.bgl"),
1946 entries: &[
1947 bgl_uniform_entry(0),
1948 bgl_storage_entry(1, true),
1949 bgl_storage_entry(2, true),
1950 bgl_storage_entry(3, true),
1951 wgpu::BindGroupLayoutEntry {
1952 binding: 4,
1953 visibility: wgpu::ShaderStages::COMPUTE,
1954 ty: wgpu::BindingType::StorageTexture {
1955 access: wgpu::StorageTextureAccess::WriteOnly,
1956 format: wgpu::TextureFormat::Rgba8Unorm,
1957 view_dimension: wgpu::TextureViewDimension::D2,
1958 },
1959 count: None,
1960 },
1961 ],
1962 });
1963 let dda_pl = self
1964 .device
1965 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1966 label: Some("roxlap-gpu chunk_dda.layout"),
1967 bind_group_layouts: &[Some(&bgl_dda)],
1968 immediate_size: 0,
1969 });
1970 let pipeline_dda = self
1971 .device
1972 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1973 label: Some("roxlap-gpu chunk_dda.pipeline"),
1974 layout: Some(&dda_pl),
1975 module: &dda_shader,
1976 entry_point: Some("render_chunk"),
1977 compilation_options: wgpu::PipelineCompilationOptions::default(),
1978 cache: None,
1979 });
1980
1981 let blit_shader = self
1984 .device
1985 .create_shader_module(wgpu::ShaderModuleDescriptor {
1986 label: Some("blit.wgsl"),
1987 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/blit.wgsl").into()),
1988 });
1989 let bgl_blit = self
1990 .device
1991 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1992 label: Some("roxlap-gpu chunk_dda.blit_bgl"),
1993 entries: &[
1994 wgpu::BindGroupLayoutEntry {
1995 binding: 0,
1996 visibility: wgpu::ShaderStages::FRAGMENT,
1997 ty: wgpu::BindingType::Texture {
1998 sample_type: wgpu::TextureSampleType::Float { filterable: false },
1999 view_dimension: wgpu::TextureViewDimension::D2,
2000 multisampled: false,
2001 },
2002 count: None,
2003 },
2004 wgpu::BindGroupLayoutEntry {
2005 binding: 1,
2006 visibility: wgpu::ShaderStages::FRAGMENT,
2007 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
2008 count: None,
2009 },
2010 ],
2011 });
2012 let blit_pl = self
2013 .device
2014 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2015 label: Some("roxlap-gpu chunk_dda.blit_layout"),
2016 bind_group_layouts: &[Some(&bgl_blit)],
2017 immediate_size: 0,
2018 });
2019 let pipeline_blit = self
2020 .device
2021 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
2022 label: Some("roxlap-gpu chunk_dda.blit_pipeline"),
2023 layout: Some(&blit_pl),
2024 vertex: wgpu::VertexState {
2025 module: &blit_shader,
2026 entry_point: Some("vs_main"),
2027 compilation_options: wgpu::PipelineCompilationOptions::default(),
2028 buffers: &[],
2029 },
2030 fragment: Some(wgpu::FragmentState {
2031 module: &blit_shader,
2032 entry_point: Some("fs_main"),
2033 compilation_options: wgpu::PipelineCompilationOptions::default(),
2034 targets: &[Some(wgpu::ColorTargetState {
2035 format: surface_format,
2036 blend: None,
2037 write_mask: wgpu::ColorWrites::ALL,
2038 })],
2039 }),
2040 primitive: wgpu::PrimitiveState::default(),
2041 depth_stencil: None,
2042 multisample: wgpu::MultisampleState::default(),
2043 multiview_mask: None,
2044 cache: None,
2045 });
2046 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
2047 label: Some("roxlap-gpu chunk_dda.blit_sampler"),
2048 address_mode_u: wgpu::AddressMode::ClampToEdge,
2049 address_mode_v: wgpu::AddressMode::ClampToEdge,
2050 address_mode_w: wgpu::AddressMode::ClampToEdge,
2051 mag_filter: wgpu::FilterMode::Nearest,
2052 min_filter: wgpu::FilterMode::Nearest,
2053 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
2054 ..Default::default()
2055 });
2056 let blit_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2057 label: Some("roxlap-gpu chunk_dda.blit_bg"),
2058 layout: &bgl_blit,
2059 entries: &[
2060 wgpu::BindGroupEntry {
2061 binding: 0,
2062 resource: wgpu::BindingResource::TextureView(&storage_view),
2063 },
2064 wgpu::BindGroupEntry {
2065 binding: 1,
2066 resource: wgpu::BindingResource::Sampler(&sampler),
2067 },
2068 ],
2069 });
2070
2071 ChunkDdaResources {
2072 storage_size: (width, height),
2073 storage_view,
2074 uniform_buf,
2075 bgl_dda,
2076 pipeline_dda,
2077 blit_bg,
2078 pipeline_blit,
2079 _sampler: sampler,
2080 }
2081 }
2082
2083 pub fn render_grid(&mut self, grid: &GpuGridResident, camera: &Camera, max_outer_steps: u32) {
2093 let Some(surf_tex) = self.acquire_frame() else {
2094 return;
2095 };
2096 let surf_view = surf_tex
2097 .texture
2098 .create_view(&wgpu::TextureViewDescriptor::default());
2099
2100 let surface_w = self.surface_config.width;
2101 let surface_h = self.surface_config.height;
2102 let surface_format = self.surface_config.format;
2103
2104 let needs_build = match &self.grid_dda {
2105 Some(r) => r.storage_size != (surface_w, surface_h),
2106 None => true,
2107 };
2108 if needs_build {
2109 self.grid_dda = Some(self.build_grid_dda(surface_w, surface_h, surface_format));
2110 }
2111 let dda = self.grid_dda.as_ref().expect("just built");
2112
2113 let uniform = GridDdaUniform {
2114 camera_pos: camera.position,
2115 _pad0: 0.0,
2116 camera_right: camera.right,
2117 _pad1: 0.0,
2118 camera_down: camera.down,
2119 _pad2: 0.0,
2120 camera_forward: camera.forward,
2121 fov_y_rad: camera.fov_y_rad,
2122 screen_size: [surface_w, surface_h],
2123 vsid: grid.vsid,
2124 max_outer_steps,
2125 chunks_dims: grid.chunks_dims,
2126 _pad3: 0,
2127 origin_chunk: grid.origin_chunk,
2128 _pad4: 0,
2129 };
2130 self.queue
2131 .write_buffer(&dda.uniform_buf, 0, bytemuck::bytes_of(&uniform));
2132
2133 let dda_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2134 label: Some("roxlap-gpu grid_dda.bg"),
2135 layout: &dda.bgl_dda,
2136 entries: &[
2137 wgpu::BindGroupEntry {
2138 binding: 0,
2139 resource: dda.uniform_buf.as_entire_binding(),
2140 },
2141 wgpu::BindGroupEntry {
2142 binding: 1,
2143 resource: grid.occupancy.as_entire_binding(),
2144 },
2145 wgpu::BindGroupEntry {
2146 binding: 2,
2147 resource: grid.color_offsets.as_entire_binding(),
2148 },
2149 wgpu::BindGroupEntry {
2150 binding: 3,
2151 resource: grid.colors.as_entire_binding(),
2152 },
2153 wgpu::BindGroupEntry {
2154 binding: 4,
2155 resource: grid.chunk_colors_base.as_entire_binding(),
2156 },
2157 wgpu::BindGroupEntry {
2158 binding: 5,
2159 resource: grid.chunk_occupancy.as_entire_binding(),
2160 },
2161 wgpu::BindGroupEntry {
2162 binding: 6,
2163 resource: wgpu::BindingResource::TextureView(&dda.storage_view),
2164 },
2165 ],
2166 });
2167
2168 let mut encoder = self
2169 .device
2170 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2171 label: Some("roxlap-gpu grid encoder"),
2172 });
2173 {
2174 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
2175 label: Some("roxlap-gpu grid_dda compute"),
2176 timestamp_writes: None,
2177 });
2178 cpass.set_pipeline(&dda.pipeline_dda);
2179 cpass.set_bind_group(0, &dda_bg, &[]);
2180 cpass.dispatch_workgroups(surface_w.div_ceil(8), surface_h.div_ceil(8), 1);
2181 }
2182 {
2183 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2184 label: Some("roxlap-gpu grid_dda blit"),
2185 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2186 view: &surf_view,
2187 depth_slice: None,
2188 resolve_target: None,
2189 ops: wgpu::Operations {
2190 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
2191 store: wgpu::StoreOp::Store,
2192 },
2193 })],
2194 depth_stencil_attachment: None,
2195 timestamp_writes: None,
2196 occlusion_query_set: None,
2197 multiview_mask: None,
2198 });
2199 rpass.set_pipeline(&dda.pipeline_blit);
2200 rpass.set_bind_group(0, &dda.blit_bg, &[]);
2201 rpass.draw(0..3, 0..1);
2202 }
2203 self.queue.submit(std::iter::once(encoder.finish()));
2204 surf_tex.present();
2205 self.frame_count = self.frame_count.wrapping_add(1);
2206 }
2207
2208 fn build_grid_dda(
2209 &self,
2210 width: u32,
2211 height: u32,
2212 surface_format: wgpu::TextureFormat,
2213 ) -> GridDdaResources {
2214 let storage_tex = self.device.create_texture(&wgpu::TextureDescriptor {
2215 label: Some("roxlap-gpu grid_dda.storage"),
2216 size: wgpu::Extent3d {
2217 width,
2218 height,
2219 depth_or_array_layers: 1,
2220 },
2221 mip_level_count: 1,
2222 sample_count: 1,
2223 dimension: wgpu::TextureDimension::D2,
2224 format: wgpu::TextureFormat::Rgba8Unorm,
2225 usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
2226 view_formats: &[],
2227 });
2228 let storage_view = storage_tex.create_view(&wgpu::TextureViewDescriptor::default());
2229
2230 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
2231 label: Some("roxlap-gpu grid_dda.uniform"),
2232 size: std::mem::size_of::<GridDdaUniform>() as u64,
2233 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2234 mapped_at_creation: false,
2235 });
2236
2237 let dda_shader = self
2238 .device
2239 .create_shader_module(wgpu::ShaderModuleDescriptor {
2240 label: Some("grid_dda.wgsl"),
2241 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/grid_dda.wgsl").into()),
2242 });
2243 let bgl_dda = self
2244 .device
2245 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2246 label: Some("roxlap-gpu grid_dda.bgl"),
2247 entries: &[
2248 bgl_uniform_entry(0),
2249 bgl_storage_entry(1, true),
2250 bgl_storage_entry(2, true),
2251 bgl_storage_entry(3, true),
2252 bgl_storage_entry(4, true),
2253 bgl_storage_entry(5, true),
2254 wgpu::BindGroupLayoutEntry {
2255 binding: 6,
2256 visibility: wgpu::ShaderStages::COMPUTE,
2257 ty: wgpu::BindingType::StorageTexture {
2258 access: wgpu::StorageTextureAccess::WriteOnly,
2259 format: wgpu::TextureFormat::Rgba8Unorm,
2260 view_dimension: wgpu::TextureViewDimension::D2,
2261 },
2262 count: None,
2263 },
2264 ],
2265 });
2266 let dda_pl = self
2267 .device
2268 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2269 label: Some("roxlap-gpu grid_dda.layout"),
2270 bind_group_layouts: &[Some(&bgl_dda)],
2271 immediate_size: 0,
2272 });
2273 let pipeline_dda = self
2274 .device
2275 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
2276 label: Some("roxlap-gpu grid_dda.pipeline"),
2277 layout: Some(&dda_pl),
2278 module: &dda_shader,
2279 entry_point: Some("render_grid"),
2280 compilation_options: wgpu::PipelineCompilationOptions::default(),
2281 cache: None,
2282 });
2283
2284 let blit_shader = self
2285 .device
2286 .create_shader_module(wgpu::ShaderModuleDescriptor {
2287 label: Some("blit.wgsl"),
2288 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/blit.wgsl").into()),
2289 });
2290 let bgl_blit = self
2291 .device
2292 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2293 label: Some("roxlap-gpu grid_dda.blit_bgl"),
2294 entries: &[
2295 wgpu::BindGroupLayoutEntry {
2296 binding: 0,
2297 visibility: wgpu::ShaderStages::FRAGMENT,
2298 ty: wgpu::BindingType::Texture {
2299 sample_type: wgpu::TextureSampleType::Float { filterable: false },
2300 view_dimension: wgpu::TextureViewDimension::D2,
2301 multisampled: false,
2302 },
2303 count: None,
2304 },
2305 wgpu::BindGroupLayoutEntry {
2306 binding: 1,
2307 visibility: wgpu::ShaderStages::FRAGMENT,
2308 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
2309 count: None,
2310 },
2311 ],
2312 });
2313 let blit_pl = self
2314 .device
2315 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2316 label: Some("roxlap-gpu grid_dda.blit_layout"),
2317 bind_group_layouts: &[Some(&bgl_blit)],
2318 immediate_size: 0,
2319 });
2320 let pipeline_blit = self
2321 .device
2322 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
2323 label: Some("roxlap-gpu grid_dda.blit_pipeline"),
2324 layout: Some(&blit_pl),
2325 vertex: wgpu::VertexState {
2326 module: &blit_shader,
2327 entry_point: Some("vs_main"),
2328 compilation_options: wgpu::PipelineCompilationOptions::default(),
2329 buffers: &[],
2330 },
2331 fragment: Some(wgpu::FragmentState {
2332 module: &blit_shader,
2333 entry_point: Some("fs_main"),
2334 compilation_options: wgpu::PipelineCompilationOptions::default(),
2335 targets: &[Some(wgpu::ColorTargetState {
2336 format: surface_format,
2337 blend: None,
2338 write_mask: wgpu::ColorWrites::ALL,
2339 })],
2340 }),
2341 primitive: wgpu::PrimitiveState::default(),
2342 depth_stencil: None,
2343 multisample: wgpu::MultisampleState::default(),
2344 multiview_mask: None,
2345 cache: None,
2346 });
2347 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
2348 label: Some("roxlap-gpu grid_dda.blit_sampler"),
2349 address_mode_u: wgpu::AddressMode::ClampToEdge,
2350 address_mode_v: wgpu::AddressMode::ClampToEdge,
2351 address_mode_w: wgpu::AddressMode::ClampToEdge,
2352 mag_filter: wgpu::FilterMode::Nearest,
2353 min_filter: wgpu::FilterMode::Nearest,
2354 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
2355 ..Default::default()
2356 });
2357 let blit_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2358 label: Some("roxlap-gpu grid_dda.blit_bg"),
2359 layout: &bgl_blit,
2360 entries: &[
2361 wgpu::BindGroupEntry {
2362 binding: 0,
2363 resource: wgpu::BindingResource::TextureView(&storage_view),
2364 },
2365 wgpu::BindGroupEntry {
2366 binding: 1,
2367 resource: wgpu::BindingResource::Sampler(&sampler),
2368 },
2369 ],
2370 });
2371
2372 GridDdaResources {
2373 storage_size: (width, height),
2374 storage_view,
2375 uniform_buf,
2376 bgl_dda,
2377 pipeline_dda,
2378 blit_bg,
2379 pipeline_blit,
2380 _sampler: sampler,
2381 }
2382 }
2383
2384 pub fn render_scene(
2400 &mut self,
2401 scene: &GpuSceneResident,
2402 cameras: &[Camera],
2403 grid_world: &[GridWorldTransform],
2406 sprite_camera: &Camera,
2407 fov_y_rad: f32,
2408 max_outer_steps: u32,
2409 ) {
2410 assert_eq!(
2411 cameras.len(),
2412 scene.grid_count as usize,
2413 "render_scene: {} cameras supplied, scene has {} grids",
2414 cameras.len(),
2415 scene.grid_count,
2416 );
2417 self.last_fov_y_rad = fov_y_rad; self.pending_frame = None;
2423 let Some(surf_tex) = self.acquire_frame() else {
2424 return;
2425 };
2426 let surf_view = surf_tex
2427 .texture
2428 .create_view(&wgpu::TextureViewDescriptor::default());
2429
2430 let surface_w = self.surface_config.width;
2431 let surface_h = self.surface_config.height;
2432 let surface_format = self.surface_config.format;
2433 let (logical_w, logical_h) = self.logical_dims();
2439 let (render_w, render_h) = self.render_dims();
2440
2441 let needs_build = match &self.scene_dda {
2442 Some(r) => {
2443 r.storage_size != (render_w, render_h) || r.logical_size != (logical_w, logical_h)
2444 }
2445 None => true,
2446 };
2447 if needs_build {
2448 self.scene_dda = Some(self.build_scene_dda(
2449 render_w,
2450 render_h,
2451 logical_w,
2452 logical_h,
2453 surface_w,
2454 surface_h,
2455 surface_format,
2456 ));
2457 }
2458 if self.sprite_registry.is_some() && self.sprite_model_dda.is_none() {
2463 self.sprite_model_dda = Some(self.build_sprite_model_dda());
2464 }
2465 let sprite_pass: Option<(u32, u32)> = if let Some(reg) = self.sprite_registry.as_mut() {
2470 if reg.instance_capacity > 0 {
2471 let cam = sprite_camera;
2474 #[allow(clippy::cast_precision_loss)]
2477 let aspect = render_w as f32 / render_h as f32;
2478 let half_h = (fov_y_rad * 0.5).tan();
2479 let frustum = sprite_model::ViewFrustum {
2480 pos: cam.position,
2481 right: cam.right,
2482 down: cam.down,
2483 forward: cam.forward,
2484 half_w: half_h * aspect,
2485 half_h,
2486 far: 1.0e9,
2487 };
2488 let (visible, tiles_x, _tiles_y) = reg.cull_bin_upload(
2489 &self.device,
2490 &self.queue,
2491 &frustum,
2492 render_w,
2493 render_h,
2494 SPRITE_TILE_SIZE,
2495 self.sprite_lod_px,
2496 );
2497 (visible > 0).then_some((visible, tiles_x))
2498 } else {
2499 None
2500 }
2501 } else {
2502 None
2503 };
2504 let dda = self.scene_dda.as_ref().expect("just built");
2505
2506 self.queue.write_buffer(
2511 &dda.blit_dims,
2512 16,
2513 bytemuck::bytes_of(&[u32::from(self.flip_x), 0u32]),
2514 );
2515 let (plevels, pdither) = match self.posterize {
2519 Some(p) => (p.levels, p.dither),
2520 None => ([1u32; 3], 0u32),
2521 };
2522 self.queue.write_buffer(
2523 &dda.resolve_dims,
2524 20,
2525 bytemuck::bytes_of(&[plevels[0], plevels[1], plevels[2], pdither]),
2526 );
2527
2528 let mut cam_vec: Vec<SceneDdaPerGridCamera> = cameras
2531 .iter()
2532 .map(SceneDdaPerGridCamera::from_camera)
2533 .collect();
2534 for (c, t) in cam_vec.iter_mut().zip(grid_world.iter()) {
2536 c.set_world_transform(t);
2537 }
2538
2539 let lights = self.scene_lights.clone();
2544 let (grid_point_lights, sun_flags, point_count) = pack_scene_lights(
2545 &self.device,
2546 &lights,
2547 scene.grid_count as usize,
2548 &mut cam_vec,
2549 );
2550 let grid_cameras = upload_grid_cameras(&self.device, &cam_vec);
2551
2552 let uniform = SceneDdaUniform {
2553 fov_y_rad,
2554 grid_count: scene.grid_count,
2555 max_outer_steps,
2556 _pad0: 0,
2557 screen_size: [render_w, render_h],
2558 _pad1: [0; 2],
2559 fog_color: [
2560 self.fog_color[0],
2561 self.fog_color[1],
2562 self.fog_color[2],
2563 self.fog_near,
2564 ],
2565 fog_far: self.fog_far,
2566 write_depth: 1,
2570 occ_page_words: scene.occupancy_page_words,
2571 occ_num_pages: scene.occupancy_num_pages,
2572 mip_scan_dist: self.scene_mip_scan_dist,
2573 terrain_has_translucent: u32::from(self.scene_terrain_translucent),
2574 terrain_map_count: self.scene_terrain_map.len() as u32,
2575 _pad4: 0,
2576 sky_cam: SceneDdaPerGridCamera::from_camera(sprite_camera),
2579 side_shades0: self.scene_side_shades[0],
2580 side_shades1: self.scene_side_shades[1],
2581 sun_color: [
2582 lights.sun_color[0],
2583 lights.sun_color[1],
2584 lights.sun_color[2],
2585 lights.sun_intensity,
2586 ],
2587 ambient_color: [
2588 lights.ambient[0],
2589 lights.ambient[1],
2590 lights.ambient[2],
2591 lights.shadow_strength,
2592 ],
2593 sun_flags,
2594 point_light_count: point_count,
2595 shadow_max_steps: lights.shadow_max_steps,
2596 _pad5: 0,
2597 shadow_bias: lights.shadow_bias,
2598 shadow_max_dist: lights.shadow_max_dist,
2599 _pad6: [0.0; 2],
2600 shadow_tint: [
2601 lights.shadow_tint[0],
2602 lights.shadow_tint[1],
2603 lights.shadow_tint[2],
2604 0.0,
2605 ],
2606 style_bands: lights.style_bands,
2607 sprite_cast_count: if self.sprite_shadows_capable {
2611 sprite_pass.map_or(0, |(visible, _)| visible)
2612 } else {
2613 0
2614 },
2615 _pad7: [0; 2],
2616 };
2617 self.queue
2618 .write_buffer(&dda.uniform_buf, 0, bytemuck::bytes_of(&uniform));
2619
2620 let mut dda_entries = vec![
2621 wgpu::BindGroupEntry {
2622 binding: 0,
2623 resource: dda.uniform_buf.as_entire_binding(),
2624 },
2625 wgpu::BindGroupEntry {
2628 binding: 1,
2629 resource: scene.occupancy_pages[0].as_entire_binding(),
2630 },
2631 wgpu::BindGroupEntry {
2632 binding: 2,
2633 resource: scene.all_color_offsets.as_entire_binding(),
2634 },
2635 wgpu::BindGroupEntry {
2636 binding: 3,
2637 resource: scene.all_colors.as_entire_binding(),
2638 },
2639 wgpu::BindGroupEntry {
2640 binding: 4,
2641 resource: scene.all_chunk_colors_base.as_entire_binding(),
2642 },
2643 wgpu::BindGroupEntry {
2644 binding: 5,
2645 resource: scene.all_chunk_occupancy.as_entire_binding(),
2646 },
2647 wgpu::BindGroupEntry {
2648 binding: 6,
2649 resource: scene.grid_static_meta.as_entire_binding(),
2650 },
2651 wgpu::BindGroupEntry {
2652 binding: 7,
2653 resource: scene.all_slot_chunk_idx.as_entire_binding(),
2654 },
2655 wgpu::BindGroupEntry {
2656 binding: 8,
2657 resource: dda.framebuffer.as_entire_binding(),
2658 },
2659 wgpu::BindGroupEntry {
2660 binding: 9,
2661 resource: wgpu::BindingResource::TextureView(&self.sky_view),
2662 },
2663 wgpu::BindGroupEntry {
2664 binding: 10,
2665 resource: wgpu::BindingResource::Sampler(&self.sky_sampler),
2666 },
2667 wgpu::BindGroupEntry {
2668 binding: 11,
2669 resource: dda.depth_buffer.as_entire_binding(),
2670 },
2671 wgpu::BindGroupEntry {
2672 binding: 12,
2673 resource: scene.occupancy_pages[1].as_entire_binding(),
2674 },
2675 wgpu::BindGroupEntry {
2676 binding: 13,
2677 resource: scene.occupancy_pages[2].as_entire_binding(),
2678 },
2679 wgpu::BindGroupEntry {
2680 binding: 14,
2681 resource: scene.occupancy_pages[3].as_entire_binding(),
2682 },
2683 wgpu::BindGroupEntry {
2684 binding: 15,
2685 resource: grid_cameras.as_entire_binding(),
2686 },
2687 wgpu::BindGroupEntry {
2688 binding: 16,
2689 resource: dda.materials_pal_buf.as_entire_binding(),
2690 },
2691 wgpu::BindGroupEntry {
2692 binding: 17,
2693 resource: dda.terrain_map_buf.as_entire_binding(),
2694 },
2695 wgpu::BindGroupEntry {
2698 binding: 18,
2699 resource: grid_point_lights.as_entire_binding(),
2700 },
2701 ];
2702 if self.sprite_shadows_capable {
2706 let dummy = dda
2707 .sprite_cast_dummy
2708 .as_ref()
2709 .expect("capable scene_dda has a sprite-cast dummy");
2710 let (insts, models, occ) = match &self.sprite_registry {
2711 Some(reg) => (®.instances, ®.model_meta, ®.occupancy),
2712 None => (dummy, dummy, dummy),
2713 };
2714 dda_entries.push(wgpu::BindGroupEntry {
2715 binding: 19,
2716 resource: insts.as_entire_binding(),
2717 });
2718 dda_entries.push(wgpu::BindGroupEntry {
2719 binding: 20,
2720 resource: models.as_entire_binding(),
2721 });
2722 dda_entries.push(wgpu::BindGroupEntry {
2723 binding: 21,
2724 resource: occ.as_entire_binding(),
2725 });
2726 }
2727 let dda_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2728 label: Some("roxlap-gpu scene_dda.bg"),
2729 layout: &dda.bgl_dda,
2730 entries: &dda_entries,
2731 });
2732
2733 let sprite_model_bg = match (&self.sprite_model_dda, &self.sprite_registry, sprite_pass) {
2740 (Some(smd), Some(reg), Some((visible, tiles_x))) => {
2741 let cam = sprite_camera;
2744 let dl = &self.scene_lights;
2747 let sprite_sun_enabled = dl.world_sun_dir != [0.0; 3];
2748 let sprite_pts: Vec<GpuPointLight> = dl
2749 .world_points
2750 .iter()
2751 .take(MAX_POINT_LIGHTS)
2752 .map(|l| GpuPointLight {
2753 pos: l.position,
2754 radius: l.radius,
2755 color: l.color,
2756 intensity: l.intensity,
2757 casts_shadow: u32::from(l.casts_shadow),
2760 _pad: [0; 3],
2761 })
2762 .collect();
2763 let sprite_point_count = sprite_pts.len() as u32;
2764 let sprite_point_buf = upload_grid_point_lights(&self.device, &sprite_pts);
2765 let sprite_sun_flags = u32::from(sprite_sun_enabled)
2768 | (u32::from(dl.sun_casts_shadow) << 1)
2769 | (u32::from(dl.enabled) << 2);
2770 let uni = SpriteModelUniform {
2771 cam_pos: cam.position,
2772 _p0: 0.0,
2773 cam_right: cam.right,
2774 _p1: 0.0,
2775 cam_down: cam.down,
2776 _p2: 0.0,
2777 cam_forward: cam.forward,
2778 _p3: 0.0,
2779 fog_color: [
2780 self.fog_color[0],
2781 self.fog_color[1],
2782 self.fog_color[2],
2783 self.fog_near,
2784 ],
2785 screen_size: [render_w, render_h],
2786 instance_count: visible,
2787 fog_far: self.fog_far,
2788 fov_y_rad,
2789 tiles_x,
2790 tile_size: SPRITE_TILE_SIZE,
2791 has_translucent: u32::from(self.sprite_has_translucent),
2792 sun_dir: [
2793 dl.world_sun_dir[0],
2794 dl.world_sun_dir[1],
2795 dl.world_sun_dir[2],
2796 0.0,
2797 ],
2798 sun_color: [
2799 dl.sun_color[0],
2800 dl.sun_color[1],
2801 dl.sun_color[2],
2802 dl.sun_intensity,
2803 ],
2804 ambient_color: [dl.ambient[0], dl.ambient[1], dl.ambient[2], 0.0],
2805 sun_flags: sprite_sun_flags,
2806 point_light_count: sprite_point_count,
2807 _pad_dl: [0; 2],
2808 shadow_tint: [dl.shadow_tint[0], dl.shadow_tint[1], dl.shadow_tint[2], 0.0],
2809 style_bands: dl.style_bands,
2810 occ_num_pages: scene.occupancy_num_pages,
2815 occ_page_words: scene.occupancy_page_words,
2816 grid_count: scene.grid_count,
2817 max_outer_steps,
2818 shadow_max_steps: dl.shadow_max_steps,
2819 shadow_bias: dl.shadow_bias,
2820 shadow_max_dist: dl.shadow_max_dist,
2821 shadow_strength: dl.shadow_strength,
2822 _pad_xs: [0; 3],
2823 };
2824 self.queue
2825 .write_buffer(&smd.uniform_buf, 0, bytemuck::bytes_of(&uni));
2826 let mut sprite_entries = vec![
2827 wgpu::BindGroupEntry {
2828 binding: 0,
2829 resource: smd.uniform_buf.as_entire_binding(),
2830 },
2831 wgpu::BindGroupEntry {
2832 binding: 1,
2833 resource: reg.occupancy.as_entire_binding(),
2834 },
2835 wgpu::BindGroupEntry {
2836 binding: 2,
2837 resource: reg.colors.as_entire_binding(),
2838 },
2839 wgpu::BindGroupEntry {
2840 binding: 3,
2841 resource: reg.color_offsets.as_entire_binding(),
2842 },
2843 wgpu::BindGroupEntry {
2844 binding: 4,
2845 resource: reg.model_meta.as_entire_binding(),
2846 },
2847 wgpu::BindGroupEntry {
2848 binding: 5,
2849 resource: reg.instances.as_entire_binding(),
2850 },
2851 wgpu::BindGroupEntry {
2852 binding: 6,
2853 resource: dda.depth_buffer.as_entire_binding(),
2854 },
2855 wgpu::BindGroupEntry {
2856 binding: 7,
2857 resource: dda.framebuffer.as_entire_binding(),
2858 },
2859 wgpu::BindGroupEntry {
2860 binding: 8,
2861 resource: reg.tile_ranges.as_entire_binding(),
2862 },
2863 wgpu::BindGroupEntry {
2864 binding: 9,
2865 resource: reg.tile_instances.as_entire_binding(),
2866 },
2867 wgpu::BindGroupEntry {
2868 binding: 10,
2869 resource: reg.dirs.as_entire_binding(),
2870 },
2871 wgpu::BindGroupEntry {
2872 binding: 11,
2873 resource: reg.colmul.as_entire_binding(),
2874 },
2875 wgpu::BindGroupEntry {
2876 binding: 12,
2877 resource: smd.materials_buf.as_entire_binding(),
2878 },
2879 wgpu::BindGroupEntry {
2880 binding: 13,
2881 resource: reg.materials_vox.as_entire_binding(),
2882 },
2883 wgpu::BindGroupEntry {
2886 binding: 15,
2887 resource: sprite_point_buf.as_entire_binding(),
2888 },
2889 ];
2890 if self.sprite_shadows_capable {
2895 let terrain: [(u32, &wgpu::Buffer); 8] = [
2896 (16, &scene.occupancy_pages[0]),
2897 (17, &scene.occupancy_pages[1]),
2898 (18, &scene.occupancy_pages[2]),
2899 (19, &scene.occupancy_pages[3]),
2900 (20, &scene.all_chunk_occupancy),
2901 (21, &scene.all_slot_chunk_idx),
2902 (22, &scene.grid_static_meta),
2903 (23, &grid_cameras),
2904 ];
2905 for (binding, buf) in terrain {
2906 sprite_entries.push(wgpu::BindGroupEntry {
2907 binding,
2908 resource: buf.as_entire_binding(),
2909 });
2910 }
2911 }
2912 Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2913 label: Some("roxlap-gpu sprite_model_dda.bg"),
2914 layout: &smd.bgl,
2915 entries: &sprite_entries,
2916 }))
2917 }
2918 _ => None,
2919 };
2920
2921 let mut encoder = self
2922 .device
2923 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2924 label: Some("roxlap-gpu scene encoder"),
2925 });
2926 {
2927 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
2928 label: Some("roxlap-gpu scene_dda compute"),
2929 timestamp_writes: None,
2930 });
2931 cpass.set_pipeline(&dda.pipeline_dda);
2932 cpass.set_bind_group(0, &dda_bg, &[]);
2933 cpass.dispatch_workgroups(render_w.div_ceil(8), render_h.div_ceil(8), 1);
2934 }
2935 if let (Some(smd), Some(bg)) = (&self.sprite_model_dda, &sprite_model_bg) {
2939 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
2940 label: Some("roxlap-gpu sprite_model_dda"),
2941 timestamp_writes: None,
2942 });
2943 cpass.set_pipeline(&smd.pipeline);
2944 cpass.set_bind_group(0, bg, &[]);
2945 cpass.dispatch_workgroups(render_w.div_ceil(8), render_h.div_ceil(8), 1);
2946 }
2947 {
2951 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
2952 label: Some("roxlap-gpu scene_dda resolve"),
2953 timestamp_writes: None,
2954 });
2955 cpass.set_pipeline(&dda.pipeline_resolve);
2956 cpass.set_bind_group(0, &dda.resolve_bg, &[]);
2957 cpass.dispatch_workgroups(logical_w.div_ceil(8), logical_h.div_ceil(8), 1);
2958 }
2959 {
2960 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2961 label: Some("roxlap-gpu scene_dda blit"),
2962 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2963 view: &surf_view,
2964 depth_slice: None,
2965 resolve_target: None,
2966 ops: wgpu::Operations {
2967 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
2968 store: wgpu::StoreOp::Store,
2969 },
2970 })],
2971 depth_stencil_attachment: None,
2972 timestamp_writes: None,
2973 occlusion_query_set: None,
2974 multiview_mask: None,
2975 });
2976 rpass.set_pipeline(&dda.pipeline_blit);
2977 rpass.set_bind_group(0, &dda.blit_bg, &[]);
2978 rpass.draw(0..3, 0..1);
2979 }
2980 self.queue.submit(std::iter::once(encoder.finish()));
2981 self.scene_depth_valid = true;
2984 self.pending_frame = Some((surf_tex, surf_view));
2986 self.frame_count = self.frame_count.wrapping_add(1);
2987 }
2988
2989 pub fn render_clear_deferred(&mut self) {
2994 self.scene_depth_valid = false;
2998 self.pending_frame = None;
2999 let Some(surf_tex) = self.acquire_frame() else {
3000 return;
3001 };
3002 let view = surf_tex
3003 .texture
3004 .create_view(&wgpu::TextureViewDescriptor::default());
3005 let [r, g, b] = self.clear_colour;
3006 let mut encoder = self
3007 .device
3008 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3009 label: Some("roxlap-gpu clear (deferred)"),
3010 });
3011 {
3012 let _rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3013 label: Some("roxlap-gpu clear (deferred)"),
3014 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3015 view: &view,
3016 depth_slice: None,
3017 resolve_target: None,
3018 ops: wgpu::Operations {
3019 load: wgpu::LoadOp::Clear(wgpu::Color { r, g, b, a: 1.0 }),
3020 store: wgpu::StoreOp::Store,
3021 },
3022 })],
3023 depth_stencil_attachment: None,
3024 timestamp_writes: None,
3025 occlusion_query_set: None,
3026 multiview_mask: None,
3027 });
3028 }
3029 self.queue.submit(std::iter::once(encoder.finish()));
3030 self.pending_frame = Some((surf_tex, view));
3031 }
3032
3033 pub fn present(&mut self) {
3037 if let Some((surf_tex, _view)) = self.pending_frame.take() {
3038 surf_tex.present();
3039 }
3040 }
3041
3042 pub fn wait_idle(&mut self) {
3051 self.pending_frame = None;
3054 #[cfg(not(target_arch = "wasm32"))]
3055 {
3056 self.device.poll(wgpu::PollType::wait_indefinitely()).ok();
3057 }
3058 }
3059
3060 pub fn draw_lines_deferred(&mut self, cam: &GpuLineCamera, lines: &[GpuLine]) {
3070 if self.pending_frame.is_none() || lines.is_empty() {
3071 return;
3072 }
3073 let (w, h) = (self.surface_config.width, self.surface_config.height);
3074 let (rw, rh) = self.render_dims();
3077 let fov = self.last_fov_y_rad;
3078 if w == 0 || h == 0 || fov <= 0.0 {
3079 return; }
3081 let verts = build_line_vertices(cam, lines, rw, rh, fov, self.flip_x);
3082 if verts.is_empty() {
3083 return;
3084 }
3085 self.ensure_line_resources();
3086 let res = self.line_resources.as_ref().expect("just built");
3087
3088 let no_depth = u32::from(self.scene_dda.is_none() || !self.scene_depth_valid);
3095 let params = LineParams {
3096 screen_w: w,
3097 screen_h: h,
3098 depth_bias: LINE_DEPTH_BIAS,
3099 no_depth,
3100 flip_x: u32::from(self.flip_x),
3101 depth_w: rw,
3102 depth_h: rh,
3103 _pad: 0,
3104 };
3105 self.queue
3106 .write_buffer(&res.uniform_buf, 0, bytemuck::bytes_of(¶ms));
3107
3108 let depth_resource = match &self.scene_dda {
3109 Some(dda) => dda.depth_buffer.as_entire_binding(),
3110 None => res.dummy_depth.as_entire_binding(),
3111 };
3112 let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3113 label: Some("roxlap-gpu line.bg"),
3114 layout: &res.bgl,
3115 entries: &[
3116 wgpu::BindGroupEntry {
3117 binding: 0,
3118 resource: res.uniform_buf.as_entire_binding(),
3119 },
3120 wgpu::BindGroupEntry {
3121 binding: 1,
3122 resource: depth_resource,
3123 },
3124 ],
3125 });
3126
3127 let needed = std::mem::size_of_val(verts.as_slice()) as u64;
3131 if self.line_vbuf_cap < needed {
3132 let cap = needed.next_power_of_two().max(4096);
3133 self.line_vbuf = Some(self.device.create_buffer(&wgpu::BufferDescriptor {
3134 label: Some("roxlap-gpu line.vbuf"),
3135 size: cap,
3136 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
3137 mapped_at_creation: false,
3138 }));
3139 self.line_vbuf_cap = cap;
3140 }
3141 let vbuf = self.line_vbuf.as_ref().expect("ensured above");
3142 self.queue
3143 .write_buffer(vbuf, 0, bytemuck::cast_slice(&verts));
3144
3145 let view = &self.pending_frame.as_ref().expect("checked above").1;
3146 let mut encoder = self
3147 .device
3148 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3149 label: Some("roxlap-gpu lines"),
3150 });
3151 {
3152 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3155 label: Some("roxlap-gpu line paint"),
3156 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3157 view,
3158 depth_slice: None,
3159 resolve_target: None,
3160 ops: wgpu::Operations {
3161 load: wgpu::LoadOp::Load,
3162 store: wgpu::StoreOp::Store,
3163 },
3164 })],
3165 depth_stencil_attachment: None,
3166 timestamp_writes: None,
3167 occlusion_query_set: None,
3168 multiview_mask: None,
3169 });
3170 pass.set_pipeline(&res.pipeline);
3171 pass.set_bind_group(0, &bg, &[]);
3172 pass.set_vertex_buffer(0, vbuf.slice(..));
3173 pass.draw(0..verts.len() as u32, 0..1);
3174 }
3175 self.queue.submit(std::iter::once(encoder.finish()));
3176 }
3178
3179 fn ensure_line_resources(&mut self) {
3184 if self.line_resources.is_some() {
3185 return;
3186 }
3187 let shader = self
3188 .device
3189 .create_shader_module(wgpu::ShaderModuleDescriptor {
3190 label: Some("line.wgsl"),
3191 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/line.wgsl").into()),
3192 });
3193 let bgl = self
3194 .device
3195 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3196 label: Some("roxlap-gpu line.bgl"),
3197 entries: &[
3198 wgpu::BindGroupLayoutEntry {
3199 binding: 0,
3200 visibility: wgpu::ShaderStages::FRAGMENT,
3201 ty: wgpu::BindingType::Buffer {
3202 ty: wgpu::BufferBindingType::Uniform,
3203 has_dynamic_offset: false,
3204 min_binding_size: None,
3205 },
3206 count: None,
3207 },
3208 wgpu::BindGroupLayoutEntry {
3209 binding: 1,
3210 visibility: wgpu::ShaderStages::FRAGMENT,
3211 ty: wgpu::BindingType::Buffer {
3212 ty: wgpu::BufferBindingType::Storage { read_only: true },
3213 has_dynamic_offset: false,
3214 min_binding_size: None,
3215 },
3216 count: None,
3217 },
3218 ],
3219 });
3220 let layout = self
3221 .device
3222 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
3223 label: Some("roxlap-gpu line.layout"),
3224 bind_group_layouts: &[Some(&bgl)],
3225 immediate_size: 0,
3226 });
3227 let pipeline = self
3228 .device
3229 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
3230 label: Some("roxlap-gpu line.pipeline"),
3231 layout: Some(&layout),
3232 vertex: wgpu::VertexState {
3233 module: &shader,
3234 entry_point: Some("vs_main"),
3235 compilation_options: wgpu::PipelineCompilationOptions::default(),
3236 buffers: &[wgpu::VertexBufferLayout {
3237 array_stride: std::mem::size_of::<LineVertex>() as u64,
3238 step_mode: wgpu::VertexStepMode::Vertex,
3239 attributes: &wgpu::vertex_attr_array![
3240 0 => Float32x2, 1 => Float32, 2 => Float32, 3 => Float32x4, ],
3245 }],
3246 },
3247 fragment: Some(wgpu::FragmentState {
3248 module: &shader,
3249 entry_point: Some("fs_main"),
3250 compilation_options: wgpu::PipelineCompilationOptions::default(),
3251 targets: &[Some(wgpu::ColorTargetState {
3252 format: self.surface_config.format,
3253 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
3254 write_mask: wgpu::ColorWrites::ALL,
3255 })],
3256 }),
3257 primitive: wgpu::PrimitiveState {
3258 cull_mode: None,
3259 ..Default::default()
3260 },
3261 depth_stencil: None,
3262 multisample: wgpu::MultisampleState::default(),
3263 multiview_mask: None,
3264 cache: None,
3265 });
3266 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3267 label: Some("roxlap-gpu line.uniform"),
3268 size: std::mem::size_of::<LineParams>() as u64,
3269 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3270 mapped_at_creation: false,
3271 });
3272 let dummy_depth = self.device.create_buffer(&wgpu::BufferDescriptor {
3273 label: Some("roxlap-gpu line.dummy_depth"),
3274 size: 4,
3275 usage: wgpu::BufferUsages::STORAGE,
3276 mapped_at_creation: false,
3277 });
3278 self.line_resources = Some(LineResources {
3279 pipeline,
3280 bgl,
3281 uniform_buf,
3282 dummy_depth,
3283 });
3284 }
3285
3286 pub fn upload_image(&mut self, rgba: &[u8], width: u32, height: u32) -> usize {
3292 if width == 0 || height == 0 || rgba.len() != (width as usize) * (height as usize) * 4 {
3293 return 0;
3294 }
3295 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
3296 label: Some("roxlap-gpu image_sprite"),
3297 size: wgpu::Extent3d {
3298 width,
3299 height,
3300 depth_or_array_layers: 1,
3301 },
3302 mip_level_count: 1,
3303 sample_count: 1,
3304 dimension: wgpu::TextureDimension::D2,
3305 format: wgpu::TextureFormat::Rgba8Unorm,
3306 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3307 view_formats: &[],
3308 });
3309 self.queue.write_texture(
3310 wgpu::TexelCopyTextureInfo {
3311 texture: &texture,
3312 mip_level: 0,
3313 origin: wgpu::Origin3d::ZERO,
3314 aspect: wgpu::TextureAspect::All,
3315 },
3316 rgba,
3317 wgpu::TexelCopyBufferLayout {
3318 offset: 0,
3319 bytes_per_row: Some(width * 4),
3320 rows_per_image: Some(height),
3321 },
3322 wgpu::Extent3d {
3323 width,
3324 height,
3325 depth_or_array_layers: 1,
3326 },
3327 );
3328 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
3329 let resident = ImageResident {
3330 view,
3331 _texture: texture,
3332 };
3333 if let Some(slot) = self.images.iter().position(Option::is_none) {
3334 self.images[slot] = Some(resident);
3335 slot
3336 } else {
3337 self.images.push(Some(resident));
3338 self.images.len() - 1
3339 }
3340 }
3341
3342 pub fn drop_image(&mut self, id: usize) {
3345 if let Some(slot) = self.images.get_mut(id) {
3346 *slot = None;
3347 }
3348 }
3349
3350 pub fn draw_images_deferred(&mut self, cam: &GpuLineCamera, quads: &[GpuImageQuad]) {
3360 if self.pending_frame.is_none() || quads.is_empty() {
3361 return;
3362 }
3363 let (w, h) = (self.surface_config.width, self.surface_config.height);
3364 let (rw, rh) = self.render_dims();
3367 let fov = self.last_fov_y_rad;
3368 if w == 0 || h == 0 || fov <= 0.0 {
3369 return;
3370 }
3371
3372 let mut verts: Vec<ImageVertex> = Vec::new();
3375 let mut draws: Vec<(u32, u32, usize)> = Vec::new();
3376 for quad in quads {
3377 if !matches!(self.images.get(quad.image), Some(Some(_))) {
3378 continue; }
3380 let v = build_image_vertices(cam, quad, rw, rh, fov, self.flip_x);
3381 if v.is_empty() {
3382 continue;
3383 }
3384 let start = verts.len() as u32;
3385 verts.extend_from_slice(&v);
3386 draws.push((start, verts.len() as u32, quad.image));
3387 }
3388 if draws.is_empty() {
3389 return;
3390 }
3391
3392 self.ensure_image_resources();
3393 let no_depth = u32::from(self.scene_dda.is_none() || !self.scene_depth_valid);
3396 let params = LineParams {
3397 screen_w: w,
3398 screen_h: h,
3399 depth_bias: LINE_DEPTH_BIAS,
3400 no_depth,
3401 flip_x: u32::from(self.flip_x),
3402 depth_w: rw,
3403 depth_h: rh,
3404 _pad: 0,
3405 };
3406 {
3407 let res = self.image_resources.as_ref().expect("just built");
3408 self.queue
3409 .write_buffer(&res.uniform_buf, 0, bytemuck::bytes_of(¶ms));
3410 }
3411
3412 let needed = std::mem::size_of_val(verts.as_slice()) as u64;
3414 if self.image_vbuf_cap < needed {
3415 let cap = needed.next_power_of_two().max(4096);
3416 self.image_vbuf = Some(self.device.create_buffer(&wgpu::BufferDescriptor {
3417 label: Some("roxlap-gpu image.vbuf"),
3418 size: cap,
3419 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
3420 mapped_at_creation: false,
3421 }));
3422 self.image_vbuf_cap = cap;
3423 }
3424 let vbuf = self.image_vbuf.as_ref().expect("ensured above");
3425 self.queue
3426 .write_buffer(vbuf, 0, bytemuck::cast_slice(&verts));
3427
3428 let res = self.image_resources.as_ref().expect("just built");
3430 let depth_resource = match &self.scene_dda {
3431 Some(dda) => dda.depth_buffer.as_entire_binding(),
3432 None => res.dummy_depth.as_entire_binding(),
3433 };
3434 let bind_groups: Vec<wgpu::BindGroup> = draws
3435 .iter()
3436 .map(|&(_, _, image_id)| {
3437 let resident = self.images[image_id].as_ref().expect("checked present");
3438 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3439 label: Some("roxlap-gpu image.bg"),
3440 layout: &res.bgl,
3441 entries: &[
3442 wgpu::BindGroupEntry {
3443 binding: 0,
3444 resource: res.uniform_buf.as_entire_binding(),
3445 },
3446 wgpu::BindGroupEntry {
3447 binding: 1,
3448 resource: depth_resource.clone(),
3449 },
3450 wgpu::BindGroupEntry {
3451 binding: 2,
3452 resource: wgpu::BindingResource::TextureView(&resident.view),
3453 },
3454 wgpu::BindGroupEntry {
3455 binding: 3,
3456 resource: wgpu::BindingResource::Sampler(&res.sampler),
3457 },
3458 ],
3459 })
3460 })
3461 .collect();
3462
3463 let view = &self.pending_frame.as_ref().expect("checked above").1;
3464 let mut encoder = self
3465 .device
3466 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3467 label: Some("roxlap-gpu images"),
3468 });
3469 {
3470 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3471 label: Some("roxlap-gpu image paint"),
3472 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3473 view,
3474 depth_slice: None,
3475 resolve_target: None,
3476 ops: wgpu::Operations {
3477 load: wgpu::LoadOp::Load,
3478 store: wgpu::StoreOp::Store,
3479 },
3480 })],
3481 depth_stencil_attachment: None,
3482 timestamp_writes: None,
3483 occlusion_query_set: None,
3484 multiview_mask: None,
3485 });
3486 pass.set_pipeline(&res.pipeline);
3487 pass.set_vertex_buffer(0, vbuf.slice(..));
3488 for (&(start, end, _), bg) in draws.iter().zip(&bind_groups) {
3489 pass.set_bind_group(0, bg, &[]);
3490 pass.draw(start..end, 0..1);
3491 }
3492 }
3493 self.queue.submit(std::iter::once(encoder.finish()));
3494 }
3496
3497 fn ensure_image_resources(&mut self) {
3501 if self.image_resources.is_some() {
3502 return;
3503 }
3504 let shader = self
3505 .device
3506 .create_shader_module(wgpu::ShaderModuleDescriptor {
3507 label: Some("image.wgsl"),
3508 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/image.wgsl").into()),
3509 });
3510 let bgl = self
3511 .device
3512 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3513 label: Some("roxlap-gpu image.bgl"),
3514 entries: &[
3515 wgpu::BindGroupLayoutEntry {
3516 binding: 0,
3517 visibility: wgpu::ShaderStages::FRAGMENT,
3518 ty: wgpu::BindingType::Buffer {
3519 ty: wgpu::BufferBindingType::Uniform,
3520 has_dynamic_offset: false,
3521 min_binding_size: None,
3522 },
3523 count: None,
3524 },
3525 wgpu::BindGroupLayoutEntry {
3526 binding: 1,
3527 visibility: wgpu::ShaderStages::FRAGMENT,
3528 ty: wgpu::BindingType::Buffer {
3529 ty: wgpu::BufferBindingType::Storage { read_only: true },
3530 has_dynamic_offset: false,
3531 min_binding_size: None,
3532 },
3533 count: None,
3534 },
3535 wgpu::BindGroupLayoutEntry {
3536 binding: 2,
3537 visibility: wgpu::ShaderStages::FRAGMENT,
3538 ty: wgpu::BindingType::Texture {
3539 sample_type: wgpu::TextureSampleType::Float { filterable: true },
3540 view_dimension: wgpu::TextureViewDimension::D2,
3541 multisampled: false,
3542 },
3543 count: None,
3544 },
3545 wgpu::BindGroupLayoutEntry {
3546 binding: 3,
3547 visibility: wgpu::ShaderStages::FRAGMENT,
3548 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
3549 count: None,
3550 },
3551 ],
3552 });
3553 let layout = self
3554 .device
3555 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
3556 label: Some("roxlap-gpu image.layout"),
3557 bind_group_layouts: &[Some(&bgl)],
3558 immediate_size: 0,
3559 });
3560 let pipeline = self
3561 .device
3562 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
3563 label: Some("roxlap-gpu image.pipeline"),
3564 layout: Some(&layout),
3565 vertex: wgpu::VertexState {
3566 module: &shader,
3567 entry_point: Some("vs_main"),
3568 compilation_options: wgpu::PipelineCompilationOptions::default(),
3569 buffers: &[wgpu::VertexBufferLayout {
3570 array_stride: std::mem::size_of::<ImageVertex>() as u64,
3571 step_mode: wgpu::VertexStepMode::Vertex,
3572 attributes: &wgpu::vertex_attr_array![
3573 0 => Float32x2, 1 => Float32, 2 => Float32, 3 => Float32, 4 => Float32, 5 => Float32x2, 6 => Float32x4, ],
3581 }],
3582 },
3583 fragment: Some(wgpu::FragmentState {
3584 module: &shader,
3585 entry_point: Some("fs_main"),
3586 compilation_options: wgpu::PipelineCompilationOptions::default(),
3587 targets: &[Some(wgpu::ColorTargetState {
3588 format: self.surface_config.format,
3589 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
3590 write_mask: wgpu::ColorWrites::ALL,
3591 })],
3592 }),
3593 primitive: wgpu::PrimitiveState {
3594 cull_mode: None,
3595 ..Default::default()
3596 },
3597 depth_stencil: None,
3598 multisample: wgpu::MultisampleState::default(),
3599 multiview_mask: None,
3600 cache: None,
3601 });
3602 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3603 label: Some("roxlap-gpu image.uniform"),
3604 size: std::mem::size_of::<LineParams>() as u64,
3605 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3606 mapped_at_creation: false,
3607 });
3608 let dummy_depth = self.device.create_buffer(&wgpu::BufferDescriptor {
3609 label: Some("roxlap-gpu image.dummy_depth"),
3610 size: 4,
3611 usage: wgpu::BufferUsages::STORAGE,
3612 mapped_at_creation: false,
3613 });
3614 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
3615 label: Some("roxlap-gpu image.sampler"),
3616 address_mode_u: wgpu::AddressMode::ClampToEdge,
3619 address_mode_v: wgpu::AddressMode::ClampToEdge,
3620 address_mode_w: wgpu::AddressMode::ClampToEdge,
3621 mag_filter: wgpu::FilterMode::Nearest,
3622 min_filter: wgpu::FilterMode::Nearest,
3623 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
3624 ..Default::default()
3625 });
3626 self.image_resources = Some(ImageResources {
3627 pipeline,
3628 bgl,
3629 uniform_buf,
3630 dummy_depth,
3631 sampler,
3632 });
3633 }
3634
3635 #[must_use]
3640 pub fn project_point(
3641 &self,
3642 cam_pos: [f32; 3],
3643 right: [f32; 3],
3644 down: [f32; 3],
3645 forward: [f32; 3],
3646 world: [f32; 3],
3647 ) -> Option<(f32, f32)> {
3648 let dda = self.scene_dda.as_ref()?;
3649 let (w, h) = dda.storage_size;
3650 if w == 0 || h == 0 || self.last_fov_y_rad <= 0.0 {
3651 return None;
3652 }
3653 let d = [
3654 world[0] - cam_pos[0],
3655 world[1] - cam_pos[1],
3656 world[2] - cam_pos[2],
3657 ];
3658 let cz = forward[0] * d[0] + forward[1] * d[1] + forward[2] * d[2];
3659 if cz < LINE_NEAR_Z {
3660 return None;
3661 }
3662 let cx = right[0] * d[0] + right[1] * d[1] + right[2] * d[2];
3663 let cy = down[0] * d[0] + down[1] * d[1] + down[2] * d[2];
3664 let half_h = (self.last_fov_y_rad * 0.5).tan();
3665 let half_w = half_h * (w as f32 / h as f32);
3666 let ndc_x = (cx / cz) / half_w;
3667 let ndc_y = -(cy / cz) / half_h;
3668 let sx = (ndc_x * 0.5 + 0.5) * w as f32;
3669 let sy = (0.5 - ndc_y * 0.5) * h as f32;
3670 Some((sx, sy))
3671 }
3672
3673 #[cfg(feature = "hud")]
3682 pub fn paint_egui(
3683 &mut self,
3684 jobs: &[egui::ClippedPrimitive],
3685 textures: &egui::TexturesDelta,
3686 pixels_per_point: f32,
3687 ) {
3688 let Some((surf_tex, surf_view)) = self.pending_frame.take() else {
3689 return;
3690 };
3691 let format = self.surface_config.format;
3692 let egui_rend = self.egui_renderer.get_or_insert_with(|| {
3693 egui_wgpu::Renderer::new(
3694 &self.device,
3695 format,
3696 egui_wgpu::RendererOptions {
3697 msaa_samples: 1,
3698 depth_stencil_format: None,
3699 dithering: false,
3700 ..Default::default()
3701 },
3702 )
3703 });
3704
3705 let screen = egui_wgpu::ScreenDescriptor {
3706 size_in_pixels: [self.surface_config.width, self.surface_config.height],
3707 pixels_per_point,
3708 };
3709 for (id, delta) in &textures.set {
3710 egui_rend.update_texture(&self.device, &self.queue, *id, delta);
3711 }
3712 let mut encoder = self
3713 .device
3714 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3715 label: Some("roxlap-gpu egui"),
3716 });
3717 let user_bufs =
3718 egui_rend.update_buffers(&self.device, &self.queue, &mut encoder, jobs, &screen);
3719 {
3720 let mut pass = encoder
3722 .begin_render_pass(&wgpu::RenderPassDescriptor {
3723 label: Some("roxlap-gpu egui paint"),
3724 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3725 view: &surf_view,
3726 depth_slice: None,
3727 resolve_target: None,
3728 ops: wgpu::Operations {
3729 load: wgpu::LoadOp::Load,
3730 store: wgpu::StoreOp::Store,
3731 },
3732 })],
3733 depth_stencil_attachment: None,
3734 timestamp_writes: None,
3735 occlusion_query_set: None,
3736 multiview_mask: None,
3737 })
3738 .forget_lifetime();
3740 egui_rend.render(&mut pass, jobs, &screen);
3741 }
3742 for id in &textures.free {
3743 egui_rend.free_texture(id);
3744 }
3745 self.queue.submit(
3746 user_bufs
3747 .into_iter()
3748 .chain(std::iter::once(encoder.finish())),
3749 );
3750 surf_tex.present();
3751 }
3752
3753 fn build_scene_dda(
3754 &self,
3755 width: u32,
3756 height: u32,
3757 logical_w: u32,
3758 logical_h: u32,
3759 surface_w: u32,
3760 surface_h: u32,
3761 surface_format: wgpu::TextureFormat,
3762 ) -> SceneDdaResources {
3763 let framebuffer = self.device.create_buffer(&wgpu::BufferDescriptor {
3769 label: Some("roxlap-gpu scene_dda.framebuffer"),
3770 size: u64::from(width) * u64::from(height) * 4,
3771 usage: wgpu::BufferUsages::STORAGE,
3772 mapped_at_creation: false,
3773 });
3774 let resolve_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3777 label: Some("roxlap-gpu scene_dda.resolve_buf"),
3778 size: u64::from(logical_w) * u64::from(logical_h) * 4,
3779 usage: wgpu::BufferUsages::STORAGE,
3780 mapped_at_creation: false,
3781 });
3782 let resolve_dims = self.device.create_buffer(&wgpu::BufferDescriptor {
3786 label: Some("roxlap-gpu scene_dda.resolve_dims"),
3787 size: 48,
3788 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3789 mapped_at_creation: false,
3790 });
3791 self.queue.write_buffer(
3792 &resolve_dims,
3793 0,
3794 bytemuck::bytes_of(&[width, height, logical_w, logical_h, self.ssaa]),
3795 );
3796 let blit_dims = self.device.create_buffer(&wgpu::BufferDescriptor {
3801 label: Some("roxlap-gpu scene_dda.blit_dims"),
3802 size: 32,
3803 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3804 mapped_at_creation: false,
3805 });
3806 self.queue.write_buffer(
3807 &blit_dims,
3808 0,
3809 bytemuck::bytes_of(&[
3810 logical_w,
3811 logical_h,
3812 surface_w,
3813 surface_h,
3814 u32::from(self.flip_x),
3815 0u32,
3816 0u32,
3817 0u32,
3818 ]),
3819 );
3820
3821 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3822 label: Some("roxlap-gpu scene_dda.uniform"),
3823 size: std::mem::size_of::<SceneDdaUniform>() as u64,
3824 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3825 mapped_at_creation: false,
3826 });
3827
3828 let depth_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
3832 label: Some("roxlap-gpu scene_dda.depth"),
3833 size: u64::from(width) * u64::from(height) * 4,
3834 usage: wgpu::BufferUsages::STORAGE
3836 | wgpu::BufferUsages::COPY_DST
3837 | wgpu::BufferUsages::COPY_SRC,
3838 mapped_at_creation: false,
3839 });
3840 let depth_readback = self.device.create_buffer(&wgpu::BufferDescriptor {
3841 label: Some("roxlap-gpu scene_dda.depth_readback"),
3842 size: u64::from(width) * u64::from(height) * 4,
3843 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
3844 mapped_at_creation: false,
3845 });
3846 let capable = self.sprite_shadows_capable;
3850 let dda_shader = self
3851 .device
3852 .create_shader_module(wgpu::ShaderModuleDescriptor {
3853 label: Some("scene_dda.wgsl"),
3854 source: wgpu::ShaderSource::Wgsl(scene_shader_source(capable).into()),
3855 });
3856 let mut dda_entries = vec![
3857 bgl_uniform_entry(0),
3858 bgl_storage_entry(1, true),
3859 bgl_storage_entry(2, true),
3860 bgl_storage_entry(3, true),
3861 bgl_storage_entry(4, true),
3862 bgl_storage_entry(5, true),
3863 bgl_storage_entry(6, true),
3864 bgl_storage_entry(7, true),
3865 bgl_storage_entry(8, false),
3868 wgpu::BindGroupLayoutEntry {
3870 binding: 9,
3871 visibility: wgpu::ShaderStages::COMPUTE,
3872 ty: wgpu::BindingType::Texture {
3873 sample_type: wgpu::TextureSampleType::Float { filterable: true },
3874 view_dimension: wgpu::TextureViewDimension::D2,
3875 multisampled: false,
3876 },
3877 count: None,
3878 },
3879 wgpu::BindGroupLayoutEntry {
3880 binding: 10,
3881 visibility: wgpu::ShaderStages::COMPUTE,
3882 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
3883 count: None,
3884 },
3885 bgl_storage_entry(11, false),
3887 bgl_storage_entry(12, true),
3890 bgl_storage_entry(13, true),
3891 bgl_storage_entry(14, true),
3892 bgl_storage_entry(15, true),
3894 bgl_storage_entry(16, true),
3896 bgl_storage_entry(17, true),
3897 bgl_storage_entry(18, true),
3901 ];
3902 if capable {
3903 dda_entries.push(bgl_storage_entry(19, true)); dda_entries.push(bgl_storage_entry(20, true)); dda_entries.push(bgl_storage_entry(21, true)); }
3908 let bgl_dda = self
3909 .device
3910 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3911 label: Some("roxlap-gpu scene_dda.bgl"),
3912 entries: &dda_entries,
3913 });
3914 let dda_pl = self
3915 .device
3916 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
3917 label: Some("roxlap-gpu scene_dda.layout"),
3918 bind_group_layouts: &[Some(&bgl_dda)],
3919 immediate_size: 0,
3920 });
3921 let pipeline_dda = self
3922 .device
3923 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
3924 label: Some("roxlap-gpu scene_dda.pipeline"),
3925 layout: Some(&dda_pl),
3926 module: &dda_shader,
3927 entry_point: Some("render_scene"),
3928 compilation_options: wgpu::PipelineCompilationOptions::default(),
3929 cache: None,
3930 });
3931
3932 let resolve_shader = self
3935 .device
3936 .create_shader_module(wgpu::ShaderModuleDescriptor {
3937 label: Some("scene_resolve.wgsl"),
3938 source: wgpu::ShaderSource::Wgsl(
3939 include_str!("../shaders/scene_resolve.wgsl").into(),
3940 ),
3941 });
3942 let bgl_resolve = self
3943 .device
3944 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3945 label: Some("roxlap-gpu scene_dda.resolve_bgl"),
3946 entries: &[
3947 bgl_storage_entry(0, true), bgl_storage_entry(1, false), bgl_uniform_entry(2), ],
3951 });
3952 let resolve_pl = self
3953 .device
3954 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
3955 label: Some("roxlap-gpu scene_dda.resolve_layout"),
3956 bind_group_layouts: &[Some(&bgl_resolve)],
3957 immediate_size: 0,
3958 });
3959 let pipeline_resolve =
3960 self.device
3961 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
3962 label: Some("roxlap-gpu scene_dda.resolve_pipeline"),
3963 layout: Some(&resolve_pl),
3964 module: &resolve_shader,
3965 entry_point: Some("main"),
3966 compilation_options: wgpu::PipelineCompilationOptions::default(),
3967 cache: None,
3968 });
3969 let resolve_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3970 label: Some("roxlap-gpu scene_dda.resolve_bg"),
3971 layout: &bgl_resolve,
3972 entries: &[
3973 wgpu::BindGroupEntry {
3974 binding: 0,
3975 resource: framebuffer.as_entire_binding(),
3976 },
3977 wgpu::BindGroupEntry {
3978 binding: 1,
3979 resource: resolve_buf.as_entire_binding(),
3980 },
3981 wgpu::BindGroupEntry {
3982 binding: 2,
3983 resource: resolve_dims.as_entire_binding(),
3984 },
3985 ],
3986 });
3987
3988 let blit_shader = self
3989 .device
3990 .create_shader_module(wgpu::ShaderModuleDescriptor {
3991 label: Some("scene_blit.wgsl"),
3992 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/scene_blit.wgsl").into()),
3993 });
3994 let bgl_blit = self
3995 .device
3996 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3997 label: Some("roxlap-gpu scene_dda.blit_bgl"),
3998 entries: &[
3999 wgpu::BindGroupLayoutEntry {
4001 binding: 0,
4002 visibility: wgpu::ShaderStages::FRAGMENT,
4003 ty: wgpu::BindingType::Buffer {
4004 ty: wgpu::BufferBindingType::Storage { read_only: true },
4005 has_dynamic_offset: false,
4006 min_binding_size: None,
4007 },
4008 count: None,
4009 },
4010 wgpu::BindGroupLayoutEntry {
4012 binding: 1,
4013 visibility: wgpu::ShaderStages::FRAGMENT,
4014 ty: wgpu::BindingType::Buffer {
4015 ty: wgpu::BufferBindingType::Uniform,
4016 has_dynamic_offset: false,
4017 min_binding_size: None,
4018 },
4019 count: None,
4020 },
4021 ],
4022 });
4023 let blit_pl = self
4024 .device
4025 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
4026 label: Some("roxlap-gpu scene_dda.blit_layout"),
4027 bind_group_layouts: &[Some(&bgl_blit)],
4028 immediate_size: 0,
4029 });
4030 let pipeline_blit = self
4031 .device
4032 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
4033 label: Some("roxlap-gpu scene_dda.blit_pipeline"),
4034 layout: Some(&blit_pl),
4035 vertex: wgpu::VertexState {
4036 module: &blit_shader,
4037 entry_point: Some("vs_main"),
4038 compilation_options: wgpu::PipelineCompilationOptions::default(),
4039 buffers: &[],
4040 },
4041 fragment: Some(wgpu::FragmentState {
4042 module: &blit_shader,
4043 entry_point: Some("fs_main"),
4044 compilation_options: wgpu::PipelineCompilationOptions::default(),
4045 targets: &[Some(wgpu::ColorTargetState {
4046 format: surface_format,
4047 blend: None,
4048 write_mask: wgpu::ColorWrites::ALL,
4049 })],
4050 }),
4051 primitive: wgpu::PrimitiveState::default(),
4052 depth_stencil: None,
4053 multisample: wgpu::MultisampleState::default(),
4054 multiview_mask: None,
4055 cache: None,
4056 });
4057 let blit_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4058 label: Some("roxlap-gpu scene_dda.blit_bg"),
4059 layout: &bgl_blit,
4060 entries: &[
4061 wgpu::BindGroupEntry {
4062 binding: 0,
4063 resource: resolve_buf.as_entire_binding(),
4065 },
4066 wgpu::BindGroupEntry {
4067 binding: 1,
4068 resource: blit_dims.as_entire_binding(),
4069 },
4070 ],
4071 });
4072
4073 let (materials_pal_buf, terrain_map_buf) = {
4077 use wgpu::util::DeviceExt;
4078 let pal = self
4079 .device
4080 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
4081 label: Some("roxlap-gpu scene_dda.materials_pal"),
4082 contents: bytemuck::cast_slice(self.scene_materials.as_slice()),
4083 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
4084 });
4085 let mut rows = [[0u32; 2]; 256];
4088 for (slot, &row) in rows.iter_mut().zip(self.scene_terrain_map.iter()) {
4089 *slot = row;
4090 }
4091 let map = self
4092 .device
4093 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
4094 label: Some("roxlap-gpu scene_dda.terrain_map"),
4095 contents: bytemuck::cast_slice(&rows),
4096 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
4097 });
4098 (pal, map)
4099 };
4100
4101 SceneDdaResources {
4102 storage_size: (width, height),
4103 logical_size: (logical_w, logical_h),
4104 framebuffer,
4105 uniform_buf,
4106 bgl_dda,
4107 pipeline_dda,
4108 pipeline_resolve,
4109 resolve_bg,
4110 resolve_dims,
4111 blit_bg,
4112 pipeline_blit,
4113 blit_dims,
4114 depth_buffer,
4115 depth_readback,
4116 materials_pal_buf,
4117 terrain_map_buf,
4118 sprite_cast_dummy: capable.then(|| {
4121 self.device.create_buffer(&wgpu::BufferDescriptor {
4122 label: Some("roxlap-gpu scene_dda.sprite_cast_dummy"),
4123 size: 80,
4124 usage: wgpu::BufferUsages::STORAGE,
4125 mapped_at_creation: false,
4126 })
4127 }),
4128 }
4129 }
4130
4131 #[must_use]
4156 pub fn read_depth_pixel(&self, x: u32, y: u32) -> Option<f32> {
4157 let dda = self.scene_dda.as_ref()?;
4158 let (w, h) = dda.storage_size;
4159 if x >= w || y >= h {
4160 return None;
4161 }
4162 let mut enc = self
4163 .device
4164 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
4165 label: Some("roxlap-gpu depth readback"),
4166 });
4167 let size = u64::from(w) * u64::from(h) * 4;
4168 enc.copy_buffer_to_buffer(&dda.depth_buffer, 0, &dda.depth_readback, 0, size);
4169 self.queue.submit(std::iter::once(enc.finish()));
4170
4171 let slice = dda.depth_readback.slice(..);
4172 let (tx, rx) = std::sync::mpsc::channel();
4173 slice.map_async(wgpu::MapMode::Read, move |r| {
4174 let _ = tx.send(r);
4175 });
4176 self.device.poll(wgpu::PollType::wait_indefinitely()).ok();
4177 rx.recv().ok()?.ok()?;
4178
4179 let t = {
4180 let data = slice.get_mapped_range();
4181 let idx = ((y * w + x) * 4) as usize;
4182 let bytes: [u8; 4] = data[idx..idx + 4].try_into().ok()?;
4183 f32::from_le_bytes(bytes)
4184 };
4185 dda.depth_readback.unmap();
4186
4187 if !t.is_finite() || t >= 1.0e29 {
4189 return None;
4190 }
4191 Some(t)
4192 }
4193
4194 #[must_use]
4201 pub fn pixel_ray(
4202 &self,
4203 right: [f64; 3],
4204 down: [f64; 3],
4205 forward: [f64; 3],
4206 x: f64,
4207 y: f64,
4208 ) -> Option<[f64; 3]> {
4209 let dda = self.scene_dda.as_ref()?;
4210 let (w, h) = dda.storage_size;
4211 if w == 0 || h == 0 || self.last_fov_y_rad <= 0.0 {
4212 return None;
4213 }
4214 Some(pinhole_pixel_ray(
4215 right,
4216 down,
4217 forward,
4218 x,
4219 y,
4220 f64::from(w),
4221 f64::from(h),
4222 f64::from(self.last_fov_y_rad),
4223 ))
4224 }
4225
4226 pub fn set_sprite_instances(
4229 &mut self,
4230 registry: &sprite_model::SpriteModelRegistry,
4231 instances: &[sprite_model::SpriteInstance],
4232 ) {
4233 if instances.is_empty() {
4234 self.sprite_registry = None;
4235 return;
4236 }
4237 self.sprite_registry = Some(sprite_model::SpriteRegistryResident::upload(
4238 &self.device,
4239 registry,
4240 instances,
4241 ));
4242 }
4243
4244 pub fn append_sprite_instances(
4259 &mut self,
4260 registry: &sprite_model::SpriteModelRegistry,
4261 instances: &[sprite_model::SpriteInstance],
4262 ) -> u32 {
4263 match self.sprite_registry.as_mut() {
4264 Some(reg) => reg.append_instances(&self.device, registry, instances),
4265 None => {
4266 self.set_sprite_instances(registry, instances);
4267 0
4268 }
4269 }
4270 }
4271
4272 pub fn remove_sprite_instance(&mut self, index: usize) -> Option<usize> {
4279 self.sprite_registry
4280 .as_mut()
4281 .and_then(|reg| reg.remove_instance(index))
4282 }
4283
4284 pub fn flush_writes(&self) {
4311 self.queue.submit(std::iter::empty::<wgpu::CommandBuffer>());
4312 }
4313
4314 pub fn add_sprite_model(
4315 &mut self,
4316 registry: &sprite_model::SpriteModelRegistry,
4317 chain_id: u32,
4318 ) {
4319 match self.sprite_registry.as_mut() {
4320 Some(reg) => reg.add_model(&self.device, &self.queue, registry, chain_id),
4321 None => {
4322 self.sprite_registry = Some(sprite_model::SpriteRegistryResident::upload(
4323 &self.device,
4324 registry,
4325 &[],
4326 ));
4327 }
4328 }
4329 }
4330
4331 pub fn remove_sprite_model(&mut self, chain_id: u32) {
4342 if let Some(reg) = self.sprite_registry.as_mut() {
4343 reg.remove_model(chain_id);
4344 }
4345 }
4346
4347 pub fn compact_sprite_models(&mut self, registry: &sprite_model::SpriteModelRegistry) {
4353 if let Some(reg) = self.sprite_registry.as_mut() {
4354 reg.compact(&self.device, &self.queue, registry);
4355 }
4356 }
4357
4358 #[must_use]
4360 pub fn sprite_model_count(&self) -> usize {
4361 self.sprite_registry
4362 .as_ref()
4363 .map_or(0, sprite_model::SpriteRegistryResident::live_model_count)
4364 }
4365
4366 #[must_use]
4370 pub fn dead_sprite_model_count(&self) -> usize {
4371 self.sprite_registry
4372 .as_ref()
4373 .map_or(0, sprite_model::SpriteRegistryResident::dead_model_count)
4374 }
4375
4376 #[must_use]
4378 pub fn sprite_instance_count(&self) -> usize {
4379 self.sprite_registry
4380 .as_ref()
4381 .map_or(0, sprite_model::SpriteRegistryResident::instance_count)
4382 }
4383
4384 pub fn update_sprite_instance_transforms(
4389 &mut self,
4390 instances: &[sprite_model::SpriteInstance],
4391 ) {
4392 if let Some(reg) = self.sprite_registry.as_mut() {
4393 reg.update_transforms(instances);
4394 }
4395 }
4396
4397 pub fn update_sprite_model(
4403 &mut self,
4404 registry: &sprite_model::SpriteModelRegistry,
4405 chain_id: u32,
4406 ) {
4407 if let Some(reg) = self.sprite_registry.as_mut() {
4408 reg.update_model(&self.device, &self.queue, registry, chain_id);
4409 }
4410 }
4411
4412 pub fn set_sprite_instance_model(
4419 &mut self,
4420 registry: &sprite_model::SpriteModelRegistry,
4421 index: usize,
4422 chain_id: u32,
4423 ) {
4424 if let Some(reg) = self.sprite_registry.as_mut() {
4425 reg.set_instance_model(registry, index, chain_id);
4426 }
4427 }
4428
4429 pub fn set_sprite_instance_colmul(&mut self, tables: &[[u64; 256]]) {
4436 if let Some(reg) = self.sprite_registry.as_mut() {
4437 reg.set_instance_colmul(tables);
4438 }
4439 }
4440
4441 pub fn set_sprite_lod_px(&mut self, px: f32) {
4447 self.sprite_lod_px = px.max(0.25);
4448 }
4449
4450 pub fn set_scene_mip_scan_dist(&mut self, dist: f32) {
4458 self.scene_mip_scan_dist = dist.max(0.0);
4459 }
4460
4461 pub fn set_scene_side_shades(&mut self, s: [i8; 6]) {
4469 let v = |i: usize| i32::from(s[i] as u8);
4473 self.scene_side_shades = [[v(0), v(1), v(2), v(3)], [v(4), v(5), 0, 0]];
4474 }
4475
4476 pub fn set_scene_lights(&mut self, lights: SceneLights) {
4482 self.scene_lights = lights;
4483 }
4484
4485 fn build_sprite_model_dda(&self) -> SpriteModelDdaResources {
4488 let capable = self.sprite_shadows_capable;
4493 let src = sprite_shader_source(capable);
4494 let shader = self
4495 .device
4496 .create_shader_module(wgpu::ShaderModuleDescriptor {
4497 label: Some("sprite_model_dda.wgsl"),
4498 source: wgpu::ShaderSource::Wgsl(src.into()),
4499 });
4500 let mut entries = vec![
4501 bgl_uniform_entry(0),
4502 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), ];
4517 if capable {
4518 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)); }
4528 let bgl = self
4529 .device
4530 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
4531 label: Some("roxlap-gpu sprite_model_dda.bgl"),
4532 entries: &entries,
4533 });
4534 let pl = self
4535 .device
4536 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
4537 label: Some("roxlap-gpu sprite_model_dda.layout"),
4538 bind_group_layouts: &[Some(&bgl)],
4539 immediate_size: 0,
4540 });
4541 let pipeline = self
4542 .device
4543 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
4544 label: Some("roxlap-gpu sprite_model_dda.pipeline"),
4545 layout: Some(&pl),
4546 module: &shader,
4547 entry_point: Some("march"),
4548 compilation_options: wgpu::PipelineCompilationOptions::default(),
4549 cache: None,
4550 });
4551 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
4552 label: Some("roxlap-gpu sprite_model_dda.uniform"),
4553 size: std::mem::size_of::<SpriteModelUniform>() as u64,
4554 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
4555 mapped_at_creation: false,
4556 });
4557 let materials_buf = {
4560 use wgpu::util::DeviceExt;
4561 self.device
4562 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
4563 label: Some("roxlap-gpu sprite_model_dda.materials"),
4564 contents: bytemuck::cast_slice(self.sprite_materials.as_slice()),
4565 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
4566 })
4567 };
4568 SpriteModelDdaResources {
4569 bgl,
4570 pipeline,
4571 uniform_buf,
4572 materials_buf,
4573 }
4574 }
4575
4576 pub fn set_sprite_materials(&mut self, table: &roxlap_formats::material::MaterialTable) {
4583 let (palette, any_translucent) = material_palette(table);
4584 self.sprite_materials = palette;
4585 self.sprite_has_translucent = any_translucent;
4586 if let Some(smd) = &self.sprite_model_dda {
4587 self.queue.write_buffer(
4588 &smd.materials_buf,
4589 0,
4590 bytemuck::cast_slice(self.sprite_materials.as_slice()),
4591 );
4592 }
4593 }
4594
4595 pub fn set_scene_terrain_materials(
4600 &mut self,
4601 table: &roxlap_formats::material::MaterialTable,
4602 map: &[(u32, u8)],
4603 ) {
4604 let (palette, _) = material_palette(table);
4605 self.scene_materials = palette;
4606 self.scene_terrain_map = map
4607 .iter()
4608 .take(256)
4609 .map(|&(c, m)| [c & 0x00ff_ffff, u32::from(m)])
4610 .collect();
4611 self.scene_terrain_translucent = map.iter().any(|&(_, m)| !table.get(m).is_opaque());
4612 if let Some(dda) = &self.scene_dda {
4613 self.queue.write_buffer(
4614 &dda.materials_pal_buf,
4615 0,
4616 bytemuck::cast_slice(self.scene_materials.as_slice()),
4617 );
4618 if !self.scene_terrain_map.is_empty() {
4619 self.queue.write_buffer(
4620 &dda.terrain_map_buf,
4621 0,
4622 bytemuck::cast_slice(&self.scene_terrain_map),
4623 );
4624 }
4625 }
4626 }
4627}
4628
4629pub struct HeadlessSceneRenderer {
4636 width: u32,
4637 height: u32,
4638 framebuffer: wgpu::Buffer,
4641 depth_buffer: wgpu::Buffer,
4642 uniform_buf: wgpu::Buffer,
4643 _sky_texture: wgpu::Texture,
4644 sky_view: wgpu::TextureView,
4645 sky_sampler: wgpu::Sampler,
4646 bgl: wgpu::BindGroupLayout,
4647 pipeline: wgpu::ComputePipeline,
4648 readback: wgpu::Buffer,
4649 side_shades: [[i32; 4]; 2],
4653 lights: SceneLights,
4657}
4658
4659impl HeadlessSceneRenderer {
4660 #[must_use]
4665 pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, width: u32, height: u32) -> Self {
4666 let framebuffer = device.create_buffer(&wgpu::BufferDescriptor {
4667 label: Some("roxlap-gpu headless.framebuffer"),
4668 size: u64::from(width) * u64::from(height) * 4,
4669 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
4670 mapped_at_creation: false,
4671 });
4672
4673 let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
4674 label: Some("roxlap-gpu headless.uniform"),
4675 size: std::mem::size_of::<SceneDdaUniform>() as u64,
4676 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
4677 mapped_at_creation: false,
4678 });
4679 let depth_buffer = device.create_buffer(&wgpu::BufferDescriptor {
4680 label: Some("roxlap-gpu headless.depth"),
4681 size: u64::from(width) * u64::from(height) * 4,
4682 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
4683 mapped_at_creation: false,
4684 });
4685
4686 let default_sky_pixel = [120u8, 150, 220, 255];
4687 let (sky_texture, sky_view) = create_sky_texture(device, 1, 1, &default_sky_pixel);
4688 queue.write_texture(
4692 wgpu::TexelCopyTextureInfo {
4693 texture: &sky_texture,
4694 mip_level: 0,
4695 origin: wgpu::Origin3d::ZERO,
4696 aspect: wgpu::TextureAspect::All,
4697 },
4698 &default_sky_pixel,
4699 wgpu::TexelCopyBufferLayout {
4700 offset: 0,
4701 bytes_per_row: Some(4),
4702 rows_per_image: Some(1),
4703 },
4704 wgpu::Extent3d {
4705 width: 1,
4706 height: 1,
4707 depth_or_array_layers: 1,
4708 },
4709 );
4710 let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
4711 label: Some("roxlap-gpu headless.sky_sampler"),
4712 address_mode_u: wgpu::AddressMode::Repeat,
4713 address_mode_v: wgpu::AddressMode::Repeat,
4714 mag_filter: wgpu::FilterMode::Linear,
4715 min_filter: wgpu::FilterMode::Linear,
4716 ..Default::default()
4717 });
4718
4719 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
4720 label: Some("scene_dda.wgsl (headless)"),
4721 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/scene_dda.wgsl").into()),
4722 });
4723 let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
4724 label: Some("roxlap-gpu headless.bgl"),
4725 entries: &[
4726 bgl_uniform_entry(0),
4727 bgl_storage_entry(1, true),
4728 bgl_storage_entry(2, true),
4729 bgl_storage_entry(3, true),
4730 bgl_storage_entry(4, true),
4731 bgl_storage_entry(5, true),
4732 bgl_storage_entry(6, true),
4733 bgl_storage_entry(7, true),
4734 bgl_storage_entry(8, false),
4736 wgpu::BindGroupLayoutEntry {
4737 binding: 9,
4738 visibility: wgpu::ShaderStages::COMPUTE,
4739 ty: wgpu::BindingType::Texture {
4740 sample_type: wgpu::TextureSampleType::Float { filterable: true },
4741 view_dimension: wgpu::TextureViewDimension::D2,
4742 multisampled: false,
4743 },
4744 count: None,
4745 },
4746 wgpu::BindGroupLayoutEntry {
4747 binding: 10,
4748 visibility: wgpu::ShaderStages::COMPUTE,
4749 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
4750 count: None,
4751 },
4752 bgl_storage_entry(11, false),
4753 bgl_storage_entry(12, true),
4754 bgl_storage_entry(13, true),
4755 bgl_storage_entry(14, true),
4756 bgl_storage_entry(15, true),
4758 bgl_storage_entry(16, true),
4760 bgl_storage_entry(17, true),
4761 bgl_storage_entry(18, true),
4764 ],
4765 });
4766 let pl = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
4767 label: Some("roxlap-gpu headless.layout"),
4768 bind_group_layouts: &[Some(&bgl)],
4769 immediate_size: 0,
4770 });
4771 let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
4772 label: Some("roxlap-gpu headless.pipeline"),
4773 layout: Some(&pl),
4774 module: &shader,
4775 entry_point: Some("render_scene"),
4776 compilation_options: wgpu::PipelineCompilationOptions::default(),
4777 cache: None,
4778 });
4779
4780 let readback = device.create_buffer(&wgpu::BufferDescriptor {
4783 label: Some("roxlap-gpu headless.readback"),
4784 size: u64::from(width) * u64::from(height) * 4,
4785 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
4786 mapped_at_creation: false,
4787 });
4788
4789 Self {
4790 width,
4791 height,
4792 framebuffer,
4793 depth_buffer,
4794 uniform_buf,
4795 _sky_texture: sky_texture,
4796 sky_view,
4797 sky_sampler,
4798 bgl,
4799 pipeline,
4800 readback,
4801 side_shades: [[0; 4]; 2],
4802 lights: SceneLights::default(),
4803 }
4804 }
4805
4806 pub fn set_scene_lights(&mut self, lights: SceneLights) {
4810 self.lights = lights;
4811 }
4812
4813 pub fn set_side_shades(&mut self, s: [i8; 6]) {
4818 let v = |i: usize| i32::from(s[i] as u8);
4819 self.side_shades = [[v(0), v(1), v(2), v(3)], [v(4), v(5), 0, 0]];
4820 }
4821
4822 #[must_use]
4834 #[allow(clippy::too_many_arguments)]
4835 pub fn render(
4836 &self,
4837 device: &wgpu::Device,
4838 queue: &wgpu::Queue,
4839 scene: &GpuSceneResident,
4840 cameras: &[Camera],
4841 fov_y_rad: f32,
4842 max_outer_steps: u32,
4843 mip_scan_dist: f32,
4844 ) -> Vec<u32> {
4845 self.render_with_transforms(
4846 device,
4847 queue,
4848 scene,
4849 cameras,
4850 &[],
4851 fov_y_rad,
4852 max_outer_steps,
4853 mip_scan_dist,
4854 )
4855 }
4856
4857 #[must_use]
4861 #[allow(clippy::too_many_arguments)]
4862 pub fn render_with_transforms(
4863 &self,
4864 device: &wgpu::Device,
4865 queue: &wgpu::Queue,
4866 scene: &GpuSceneResident,
4867 cameras: &[Camera],
4868 grid_world: &[GridWorldTransform],
4869 fov_y_rad: f32,
4870 max_outer_steps: u32,
4871 mip_scan_dist: f32,
4872 ) -> Vec<u32> {
4873 assert_eq!(
4874 cameras.len(),
4875 scene.grid_count as usize,
4876 "headless render: {} cameras for {} grids",
4877 cameras.len(),
4878 scene.grid_count,
4879 );
4880
4881 let mut cam_vec: Vec<SceneDdaPerGridCamera> = cameras
4882 .iter()
4883 .map(SceneDdaPerGridCamera::from_camera)
4884 .collect();
4885 for (c, t) in cam_vec.iter_mut().zip(grid_world.iter()) {
4887 c.set_world_transform(t);
4888 }
4889 let (dummy_pal, dummy_map) = {
4892 use wgpu::util::DeviceExt;
4893 let pal: Vec<MaterialGpu> = vec![
4894 MaterialGpu {
4895 alpha: 1.0,
4896 mode: 0
4897 };
4898 256
4899 ];
4900 let p = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
4901 label: Some("roxlap-gpu headless.materials_pal"),
4902 contents: bytemuck::cast_slice(&pal),
4903 usage: wgpu::BufferUsages::STORAGE,
4904 });
4905 let m = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
4906 label: Some("roxlap-gpu headless.terrain_map"),
4907 contents: bytemuck::cast_slice(&[[0u32; 2]]),
4908 usage: wgpu::BufferUsages::STORAGE,
4909 });
4910 (p, m)
4911 };
4912 let dl = self.lights.clone();
4917 let (dummy_point_lights, sun_flags, point_count) =
4918 pack_scene_lights(device, &dl, scene.grid_count as usize, &mut cam_vec);
4919 let grid_cameras = upload_grid_cameras(device, &cam_vec);
4920 let uniform = SceneDdaUniform {
4921 fov_y_rad,
4922 grid_count: scene.grid_count,
4923 max_outer_steps,
4924 _pad0: 0,
4925 screen_size: [self.width, self.height],
4926 _pad1: [0; 2],
4927 fog_color: [0.0, 0.0, 0.0, 1.0e29],
4929 fog_far: 1.0e30,
4930 write_depth: 0,
4931 occ_page_words: scene.occupancy_page_words,
4932 occ_num_pages: scene.occupancy_num_pages,
4933 mip_scan_dist,
4934 terrain_has_translucent: 0, terrain_map_count: 0,
4936 _pad4: 0,
4937 sky_cam: SceneDdaPerGridCamera::from_camera(&cameras.first().copied().unwrap_or(
4941 Camera {
4942 position: [0.0; 3],
4943 right: [1.0, 0.0, 0.0],
4944 down: [0.0, 0.0, 1.0],
4945 forward: [0.0, 1.0, 0.0],
4946 fov_y_rad,
4947 },
4948 )),
4949 side_shades0: self.side_shades[0],
4950 side_shades1: self.side_shades[1],
4951 sun_color: [
4953 dl.sun_color[0],
4954 dl.sun_color[1],
4955 dl.sun_color[2],
4956 dl.sun_intensity,
4957 ],
4958 ambient_color: [
4959 dl.ambient[0],
4960 dl.ambient[1],
4961 dl.ambient[2],
4962 dl.shadow_strength,
4963 ],
4964 sun_flags,
4965 point_light_count: point_count,
4966 shadow_max_steps: dl.shadow_max_steps,
4967 _pad5: 0,
4968 shadow_bias: dl.shadow_bias,
4969 shadow_max_dist: dl.shadow_max_dist,
4970 _pad6: [0.0; 2],
4971 shadow_tint: [dl.shadow_tint[0], dl.shadow_tint[1], dl.shadow_tint[2], 0.0],
4972 style_bands: dl.style_bands,
4973 sprite_cast_count: 0, _pad7: [0; 2],
4975 };
4976 queue.write_buffer(&self.uniform_buf, 0, bytemuck::bytes_of(&uniform));
4977
4978 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
4979 label: Some("roxlap-gpu headless.bg"),
4980 layout: &self.bgl,
4981 entries: &[
4982 wgpu::BindGroupEntry {
4983 binding: 0,
4984 resource: self.uniform_buf.as_entire_binding(),
4985 },
4986 wgpu::BindGroupEntry {
4987 binding: 1,
4988 resource: scene.occupancy_pages[0].as_entire_binding(),
4989 },
4990 wgpu::BindGroupEntry {
4991 binding: 2,
4992 resource: scene.all_color_offsets.as_entire_binding(),
4993 },
4994 wgpu::BindGroupEntry {
4995 binding: 3,
4996 resource: scene.all_colors.as_entire_binding(),
4997 },
4998 wgpu::BindGroupEntry {
4999 binding: 4,
5000 resource: scene.all_chunk_colors_base.as_entire_binding(),
5001 },
5002 wgpu::BindGroupEntry {
5003 binding: 5,
5004 resource: scene.all_chunk_occupancy.as_entire_binding(),
5005 },
5006 wgpu::BindGroupEntry {
5007 binding: 6,
5008 resource: scene.grid_static_meta.as_entire_binding(),
5009 },
5010 wgpu::BindGroupEntry {
5011 binding: 7,
5012 resource: scene.all_slot_chunk_idx.as_entire_binding(),
5013 },
5014 wgpu::BindGroupEntry {
5015 binding: 8,
5016 resource: self.framebuffer.as_entire_binding(),
5017 },
5018 wgpu::BindGroupEntry {
5019 binding: 9,
5020 resource: wgpu::BindingResource::TextureView(&self.sky_view),
5021 },
5022 wgpu::BindGroupEntry {
5023 binding: 10,
5024 resource: wgpu::BindingResource::Sampler(&self.sky_sampler),
5025 },
5026 wgpu::BindGroupEntry {
5027 binding: 11,
5028 resource: self.depth_buffer.as_entire_binding(),
5029 },
5030 wgpu::BindGroupEntry {
5031 binding: 12,
5032 resource: scene.occupancy_pages[1].as_entire_binding(),
5033 },
5034 wgpu::BindGroupEntry {
5035 binding: 13,
5036 resource: scene.occupancy_pages[2].as_entire_binding(),
5037 },
5038 wgpu::BindGroupEntry {
5039 binding: 14,
5040 resource: scene.occupancy_pages[3].as_entire_binding(),
5041 },
5042 wgpu::BindGroupEntry {
5043 binding: 15,
5044 resource: grid_cameras.as_entire_binding(),
5045 },
5046 wgpu::BindGroupEntry {
5047 binding: 16,
5048 resource: dummy_pal.as_entire_binding(),
5049 },
5050 wgpu::BindGroupEntry {
5051 binding: 17,
5052 resource: dummy_map.as_entire_binding(),
5053 },
5054 wgpu::BindGroupEntry {
5057 binding: 18,
5058 resource: dummy_point_lights.as_entire_binding(),
5059 },
5060 ],
5061 });
5062
5063 let mut enc =
5064 device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
5065 {
5066 let mut pass = enc.begin_compute_pass(&wgpu::ComputePassDescriptor {
5067 label: Some("roxlap-gpu headless.pass"),
5068 timestamp_writes: None,
5069 });
5070 pass.set_pipeline(&self.pipeline);
5071 pass.set_bind_group(0, &bg, &[]);
5072 pass.dispatch_workgroups(self.width.div_ceil(8), self.height.div_ceil(8), 1);
5073 }
5074 enc.copy_buffer_to_buffer(
5075 &self.framebuffer,
5076 0,
5077 &self.readback,
5078 0,
5079 u64::from(self.width) * u64::from(self.height) * 4,
5080 );
5081 queue.submit(Some(enc.finish()));
5082
5083 let slice = self.readback.slice(..);
5084 let (tx, rx) = std::sync::mpsc::channel();
5085 slice.map_async(wgpu::MapMode::Read, move |r| {
5086 let _ = tx.send(r);
5087 });
5088 device.poll(wgpu::PollType::wait_indefinitely()).ok();
5089 rx.recv().expect("map_async channel").expect("map_async");
5090
5091 let data = slice.get_mapped_range();
5092 let out: Vec<u32> = data
5096 .chunks_exact(4)
5097 .map(|px| u32::from_le_bytes([px[0], px[1], px[2], px[3]]))
5098 .collect();
5099 drop(data);
5100 self.readback.unmap();
5101 out
5102 }
5103}
5104
5105fn bgl_uniform_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
5106 wgpu::BindGroupLayoutEntry {
5107 binding,
5108 visibility: wgpu::ShaderStages::COMPUTE,
5109 ty: wgpu::BindingType::Buffer {
5110 ty: wgpu::BufferBindingType::Uniform,
5111 has_dynamic_offset: false,
5112 min_binding_size: None,
5113 },
5114 count: None,
5115 }
5116}
5117
5118fn bgl_storage_entry(binding: u32, read_only: bool) -> wgpu::BindGroupLayoutEntry {
5119 wgpu::BindGroupLayoutEntry {
5120 binding,
5121 visibility: wgpu::ShaderStages::COMPUTE,
5122 ty: wgpu::BindingType::Buffer {
5123 ty: wgpu::BufferBindingType::Storage { read_only },
5124 has_dynamic_offset: false,
5125 min_binding_size: None,
5126 },
5127 count: None,
5128 }
5129}
5130
5131fn create_sky_texture(
5136 device: &wgpu::Device,
5137 width: u32,
5138 height: u32,
5139 _initial_pixels: &[u8],
5140) -> (wgpu::Texture, wgpu::TextureView) {
5141 let tex = device.create_texture(&wgpu::TextureDescriptor {
5142 label: Some("roxlap-gpu sky_texture"),
5143 size: wgpu::Extent3d {
5144 width,
5145 height,
5146 depth_or_array_layers: 1,
5147 },
5148 mip_level_count: 1,
5149 sample_count: 1,
5150 dimension: wgpu::TextureDimension::D2,
5151 format: wgpu::TextureFormat::Rgba8Unorm,
5152 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
5153 view_formats: &[],
5154 });
5155 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
5156 (tex, view)
5157}
5158
5159pub(crate) fn pick_required_limits(adapter_limits: &wgpu::Limits) -> wgpu::Limits {
5167 wgpu::Limits {
5168 max_storage_buffer_binding_size: adapter_limits.max_storage_buffer_binding_size,
5169 max_buffer_size: adapter_limits.max_buffer_size,
5170 max_storage_buffers_per_shader_stage: adapter_limits
5179 .max_storage_buffers_per_shader_stage
5180 .min(SPRITE_SHADOW_MIN_STORAGE_BUFFERS),
5181 ..wgpu::Limits::default()
5182 }
5183}
5184
5185fn sprite_shader_source(capable: bool) -> String {
5192 let base = include_str!("../shaders/sprite_model_dda.wgsl");
5193 if !capable {
5194 return base.to_string();
5195 }
5196 let snippet = include_str!("../shaders/sprite_terrain_shadow.wgsl");
5197 const BEGIN: &str = "//XS4_STUB_BEGIN";
5198 const END: &str = "//XS4_STUB_END";
5199 let (Some(b), Some(e)) = (base.find(BEGIN), base.find(END)) else {
5200 panic!("sprite_model_dda.wgsl: XS4 stub markers not found");
5202 };
5203 let e_end = e + END.len();
5204 let mut out = String::with_capacity(base.len() + snippet.len());
5205 out.push_str(&base[..b]);
5206 out.push_str(snippet);
5207 out.push_str(&base[e_end..]);
5208 out
5209}
5210
5211fn scene_shader_source(capable: bool) -> String {
5217 let base = include_str!("../shaders/scene_dda.wgsl");
5218 if !capable {
5219 return base.to_string();
5220 }
5221 let snippet = include_str!("../shaders/scene_sprite_shadow.wgsl");
5222 const BEGIN: &str = "//XS4C_STUB_BEGIN";
5223 const END: &str = "//XS4C_STUB_END";
5224 let (Some(b), Some(e)) = (base.find(BEGIN), base.find(END)) else {
5225 panic!("scene_dda.wgsl: XS4C stub markers not found");
5226 };
5227 let e_end = e + END.len();
5228 let mut out = String::with_capacity(base.len() + snippet.len());
5229 out.push_str(&base[..b]);
5230 out.push_str(snippet);
5231 out.push_str(&base[e_end..]);
5232 out
5233}
5234
5235pub(crate) const SPRITE_SHADOW_MIN_STORAGE_BUFFERS: u32 = 22;
5240
5241fn pick_present_mode(modes: &[wgpu::PresentMode]) -> wgpu::PresentMode {
5242 for &m in &[wgpu::PresentMode::Mailbox, wgpu::PresentMode::Immediate] {
5245 if modes.contains(&m) {
5246 return m;
5247 }
5248 }
5249 wgpu::PresentMode::Fifo
5250}
5251
5252#[must_use]
5258#[allow(clippy::too_many_arguments)]
5259pub fn pinhole_pixel_ray(
5260 right: [f64; 3],
5261 down: [f64; 3],
5262 forward: [f64; 3],
5263 x: f64,
5264 y: f64,
5265 w: f64,
5266 h: f64,
5267 fov_y_rad: f64,
5268) -> [f64; 3] {
5269 let half_h = (fov_y_rad * 0.5).tan();
5270 let half_w = half_h * (w / h);
5271 let ndc_x = (x + 0.5) / w * 2.0 - 1.0;
5272 let ndc_y_top = 1.0 - (y + 0.5) / h * 2.0;
5273 let (kx, ky) = (ndc_x * half_w, ndc_y_top * half_h);
5274 [
5275 forward[0] + kx * right[0] - ky * down[0],
5276 forward[1] + kx * right[1] - ky * down[1],
5277 forward[2] + kx * right[2] - ky * down[2],
5278 ]
5279}
5280
5281#[cfg(test)]
5282mod pixel_ray_tests {
5283 use super::pinhole_pixel_ray;
5284
5285 const RIGHT: [f64; 3] = [1.0, 0.0, 0.0];
5286 const DOWN: [f64; 3] = [0.0, 1.0, 0.0];
5287 const FWD: [f64; 3] = [0.0, 0.0, 1.0]; #[test]
5291 fn centre_pixel_is_forward() {
5292 let d = pinhole_pixel_ray(
5293 RIGHT,
5294 DOWN,
5295 FWD,
5296 639.5,
5297 359.5,
5298 1280.0,
5299 720.0,
5300 60_f64.to_radians(),
5301 );
5302 assert!(
5303 d[0].abs() < 1e-9 && d[1].abs() < 1e-9,
5304 "centre ≈ forward, got {d:?}"
5305 );
5306 assert!((d[2] - 1.0).abs() < 1e-9);
5307 }
5308
5309 #[test]
5312 fn right_edge_tilts_by_half_w() {
5313 let fov = 60_f64.to_radians();
5314 let d = pinhole_pixel_ray(RIGHT, DOWN, FWD, 1279.5, 359.5, 1280.0, 720.0, fov);
5315 let half_w = (fov * 0.5).tan() * (1280.0 / 720.0);
5316 assert!((d[0] - half_w).abs() < 1e-6, "x={}, half_w={half_w}", d[0]);
5317 assert!(d[0] > 0.0, "right edge tilts +right");
5318 }
5319
5320 #[test]
5325 fn wgsl_shaders_validate() {
5326 let shaders: &[(&str, &str)] = &[
5327 (
5328 "sprite_model_dda.wgsl",
5329 include_str!("../shaders/sprite_model_dda.wgsl"),
5330 ),
5331 ("scene_dda.wgsl", include_str!("../shaders/scene_dda.wgsl")),
5332 ("blit.wgsl", include_str!("../shaders/blit.wgsl")),
5333 ("chunk_dda.wgsl", include_str!("../shaders/chunk_dda.wgsl")),
5334 ("grid_dda.wgsl", include_str!("../shaders/grid_dda.wgsl")),
5335 (
5336 "scene_blit.wgsl",
5337 include_str!("../shaders/scene_blit.wgsl"),
5338 ),
5339 (
5340 "scene_resolve.wgsl",
5341 include_str!("../shaders/scene_resolve.wgsl"),
5342 ),
5343 ("line.wgsl", include_str!("../shaders/line.wgsl")),
5344 ("image.wgsl", include_str!("../shaders/image.wgsl")),
5345 ];
5346 let mut validator = naga::valid::Validator::new(
5347 naga::valid::ValidationFlags::all(),
5348 naga::valid::Capabilities::all(),
5349 );
5350 for (name, src) in shaders {
5351 let module = naga::front::wgsl::parse_str(src).unwrap_or_else(|e| {
5352 panic!("{name}: WGSL parse failed:\n{}", e.emit_to_string(src))
5353 });
5354 validator
5355 .validate(&module)
5356 .unwrap_or_else(|e| panic!("{name}: WGSL validation failed: {e:?}"));
5357 }
5358 let capable = super::sprite_shader_source(true);
5362 let module = naga::front::wgsl::parse_str(&capable).unwrap_or_else(|e| {
5363 panic!(
5364 "sprite_model_dda.wgsl (capable): parse failed:\n{}",
5365 e.emit_to_string(&capable)
5366 )
5367 });
5368 validator.validate(&module).unwrap_or_else(|e| {
5369 panic!("sprite_model_dda.wgsl (capable): validation failed: {e:?}")
5370 });
5371 let scene_cap = super::scene_shader_source(true);
5373 let module = naga::front::wgsl::parse_str(&scene_cap).unwrap_or_else(|e| {
5374 panic!(
5375 "scene_dda.wgsl (capable): parse failed:\n{}",
5376 e.emit_to_string(&scene_cap)
5377 )
5378 });
5379 validator
5380 .validate(&module)
5381 .unwrap_or_else(|e| panic!("scene_dda.wgsl (capable): validation failed: {e:?}"));
5382 }
5383
5384 #[test]
5389 fn image_vertices_carry_forward_w_and_euclidean_depth() {
5390 let cam = crate::GpuLineCamera {
5391 pos: [0.0, 0.0, 0.0],
5392 right: [1.0, 0.0, 0.0],
5393 down: [0.0, 1.0, 0.0],
5394 forward: [0.0, 0.0, 1.0],
5395 };
5396 let quad = crate::GpuImageQuad {
5398 corners: [
5399 [-1.0, -1.0, 10.0], [1.0, -1.0, 10.0], [-1.0, 1.0, 10.0], [1.0, 1.0, 10.0], ],
5404 image: 0,
5405 tint: [1.0, 1.0, 1.0, 1.0],
5406 depth_test: true,
5407 alpha_cutoff: 0.0,
5408 };
5409 let verts = crate::build_image_vertices(&cam, &quad, 800, 600, 60_f32.to_radians(), false);
5410 assert_eq!(verts.len(), 6, "two triangles, no near-clip");
5411 for v in &verts {
5412 assert!((v.w - 10.0).abs() < 1e-4, "w == forward distance");
5413 assert!(v.depth >= 10.0, "euclidean depth >= forward distance");
5414 assert_eq!(v.depth_test, 1.0);
5415 }
5416 }
5417}