1#![allow(clippy::must_use_candidate, clippy::too_many_lines)]
33
34pub mod camera;
35pub mod decompress;
36pub mod grid;
37pub mod headless;
38pub mod resident;
39pub mod scene;
40pub mod sprite_model;
41
42pub use camera::Camera;
43pub use decompress::{decompress_chunk, ChunkUpload, BEDROCK_RGB, CHUNK_Z};
44pub use grid::{bounding_box_of, GpuGridResident, GridUpload};
45pub use headless::HeadlessGpu;
46pub use resident::GpuChunkResident;
47pub use scene::{
48 GpuSceneResident, GridRuntimeTransform, GridStaticMeta, RefreshOutcome, SceneUpload,
49 MAX_SCENE_GRIDS,
50};
51pub use sprite_model::{
52 build_sprite_model, SpriteInstance, SpriteInstanceTransform, SpriteModel, SpriteModelRegistry,
53 SpriteRegistryResident,
54};
55
56use std::sync::Arc;
57
58use bytemuck::{Pod, Zeroable};
59use winit::window::Window;
60
61#[derive(Debug, Clone, Copy)]
66pub struct GpuRendererSettings {
67 pub power_preference: PowerPreference,
68 pub clear_colour: [f64; 3],
71 pub uncapped_present: bool,
74}
75
76#[derive(Debug, Clone, Copy)]
77pub enum PowerPreference {
78 Low,
79 High,
80}
81
82impl Default for GpuRendererSettings {
83 fn default() -> Self {
84 Self {
85 power_preference: PowerPreference::High,
86 clear_colour: [0.06, 0.08, 0.12],
87 uncapped_present: true,
88 }
89 }
90}
91
92#[derive(Debug)]
95pub enum GpuInitError {
96 CreateSurface(wgpu::CreateSurfaceError),
97 NoAdapter,
98 RequestDevice(wgpu::RequestDeviceError),
99}
100
101impl std::fmt::Display for GpuInitError {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 match self {
104 Self::CreateSurface(e) => write!(f, "create_surface failed: {e}"),
105 Self::NoAdapter => write!(
106 f,
107 "no compatible adapter — does this system have a Vulkan/Metal/DX12 driver?"
108 ),
109 Self::RequestDevice(e) => write!(f, "request_device failed: {e}"),
110 }
111 }
112}
113
114impl std::error::Error for GpuInitError {
115 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
116 match self {
117 Self::CreateSurface(e) => Some(e),
118 Self::RequestDevice(e) => Some(e),
119 Self::NoAdapter => None,
120 }
121 }
122}
123
124impl From<wgpu::CreateSurfaceError> for GpuInitError {
125 fn from(value: wgpu::CreateSurfaceError) -> Self {
126 Self::CreateSurface(value)
127 }
128}
129
130impl From<wgpu::RequestDeviceError> for GpuInitError {
131 fn from(value: wgpu::RequestDeviceError) -> Self {
132 Self::RequestDevice(value)
133 }
134}
135
136pub struct GpuRenderer {
141 window: Arc<Window>,
142 surface: wgpu::Surface<'static>,
143 surface_config: wgpu::SurfaceConfiguration,
144 device: wgpu::Device,
145 queue: wgpu::Queue,
146 adapter_info: String,
147 clear_colour: [f64; 3],
148 frame_count: u32,
149 chunk_dda: Option<ChunkDdaResources>,
152 grid_dda: Option<GridDdaResources>,
156 scene_dda: Option<SceneDdaResources>,
159 sky_texture: wgpu::Texture,
164 sky_view: wgpu::TextureView,
165 sky_sampler: wgpu::Sampler,
166 fog_color: [f32; 3],
172 fog_near: f32,
173 fog_far: f32,
174 sprite_registry: Option<sprite_model::SpriteRegistryResident>,
179 sprite_model_dda: Option<SpriteModelDdaResources>,
181 sprite_lod_px: f32,
186 scene_mip_scan_dist: f32,
193}
194
195struct ChunkDdaResources {
199 storage_size: (u32, u32),
200 storage_view: wgpu::TextureView,
201 uniform_buf: wgpu::Buffer,
202 bgl_dda: wgpu::BindGroupLayout,
203 pipeline_dda: wgpu::ComputePipeline,
204 blit_bg: wgpu::BindGroup,
205 pipeline_blit: wgpu::RenderPipeline,
206 _sampler: wgpu::Sampler,
209}
210
211struct GridDdaResources {
212 storage_size: (u32, u32),
213 storage_view: wgpu::TextureView,
214 uniform_buf: wgpu::Buffer,
215 bgl_dda: wgpu::BindGroupLayout,
216 pipeline_dda: wgpu::ComputePipeline,
217 blit_bg: wgpu::BindGroup,
218 pipeline_blit: wgpu::RenderPipeline,
219 _sampler: wgpu::Sampler,
220}
221
222struct SceneDdaResources {
223 storage_size: (u32, u32),
224 storage_view: wgpu::TextureView,
225 uniform_buf: wgpu::Buffer,
226 bgl_dda: wgpu::BindGroupLayout,
227 pipeline_dda: wgpu::ComputePipeline,
228 blit_bg: wgpu::BindGroup,
229 pipeline_blit: wgpu::RenderPipeline,
230 _sampler: wgpu::Sampler,
231 depth_buffer: wgpu::Buffer,
236}
237
238struct SpriteModelDdaResources {
242 bgl: wgpu::BindGroupLayout,
243 pipeline: wgpu::ComputePipeline,
244 uniform_buf: wgpu::Buffer,
245}
246
247#[repr(C)]
252#[derive(Clone, Copy, Pod, Zeroable)]
253struct SpriteModelUniform {
254 cam_pos: [f32; 3],
255 _p0: f32,
256 cam_right: [f32; 3],
257 _p1: f32,
258 cam_down: [f32; 3],
259 _p2: f32,
260 cam_forward: [f32; 3],
261 _p3: f32,
262 fog_color: [f32; 4],
263 screen_size: [u32; 2],
264 instance_count: u32,
265 fog_far: f32,
266 fov_y_rad: f32,
267 tiles_x: u32,
268 tile_size: u32,
269 _p6: f32,
270}
271
272const SCENE_MAX_GRIDS: usize = MAX_SCENE_GRIDS as usize;
273
274const SPRITE_TILE_SIZE: u32 = 16;
276
277const _: () = assert!(scene::MAX_OCC_PAGES == 4);
282
283#[repr(C)]
284#[derive(Clone, Copy, Pod, Zeroable)]
285struct SceneDdaPerGridCamera {
286 pos: [f32; 3],
287 _pad0: f32,
288 right: [f32; 3],
289 _pad1: f32,
290 down: [f32; 3],
291 _pad2: f32,
292 forward: [f32; 3],
293 _pad3: f32,
294}
295
296#[repr(C)]
297#[derive(Clone, Copy, Pod, Zeroable)]
298struct SceneDdaUniform {
299 fov_y_rad: f32,
300 grid_count: u32,
301 max_outer_steps: u32,
302 _pad0: u32,
303 screen_size: [u32; 2],
304 _pad1: [u32; 2],
305 cameras: [SceneDdaPerGridCamera; SCENE_MAX_GRIDS],
306 fog_color: [f32; 4],
310 fog_far: f32,
311 write_depth: u32,
314 occ_page_words: u32,
318 occ_num_pages: u32,
321 mip_scan_dist: f32,
326 _pad2: u32,
327 _pad3: u32,
328 _pad4: u32,
329}
330
331#[repr(C)]
332#[derive(Clone, Copy, Pod, Zeroable)]
333struct GridDdaUniform {
334 camera_pos: [f32; 3],
335 _pad0: f32,
336 camera_right: [f32; 3],
337 _pad1: f32,
338 camera_down: [f32; 3],
339 _pad2: f32,
340 camera_forward: [f32; 3],
341 fov_y_rad: f32,
342 screen_size: [u32; 2],
343 vsid: u32,
344 max_outer_steps: u32,
345 chunks_dims: [u32; 3],
346 _pad3: u32,
347 origin_chunk: [i32; 3],
348 _pad4: u32,
349}
350
351#[repr(C)]
352#[derive(Clone, Copy, Pod, Zeroable)]
353struct ChunkDdaUniform {
354 camera_pos: [f32; 3],
355 _pad0: f32,
356 camera_right: [f32; 3],
357 _pad1: f32,
358 camera_down: [f32; 3],
359 _pad2: f32,
360 camera_forward: [f32; 3],
361 fov_y_rad: f32,
362 screen_size: [u32; 2],
363 vsid: u32,
364 max_scan_dist: u32,
365}
366
367impl GpuRenderer {
368 pub async fn new(
376 window: Arc<Window>,
377 settings: GpuRendererSettings,
378 ) -> Result<Self, GpuInitError> {
379 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());
380 let surface = instance.create_surface(window.clone())?;
381 let power_preference = match settings.power_preference {
382 PowerPreference::Low => wgpu::PowerPreference::LowPower,
383 PowerPreference::High => wgpu::PowerPreference::HighPerformance,
384 };
385 let adapter = instance
386 .request_adapter(&wgpu::RequestAdapterOptions {
387 power_preference,
388 compatible_surface: Some(&surface),
389 force_fallback_adapter: false,
390 })
391 .await
392 .ok_or(GpuInitError::NoAdapter)?;
393
394 let info = adapter.get_info();
395 let adapter_info = format!(
396 "{name} ({backend:?}, {device_type:?})",
397 name = info.name,
398 backend = info.backend,
399 device_type = info.device_type,
400 );
401
402 let (device, queue) = adapter
403 .request_device(
404 &wgpu::DeviceDescriptor {
405 label: Some("roxlap-gpu device"),
406 required_features: wgpu::Features::empty(),
407 required_limits: pick_required_limits(&adapter.limits()),
408 memory_hints: wgpu::MemoryHints::default(),
409 },
410 None,
411 )
412 .await?;
413
414 let caps = surface.get_capabilities(&adapter);
415 let surface_format = caps
424 .formats
425 .iter()
426 .copied()
427 .find(|f| !f.is_srgb())
428 .unwrap_or(caps.formats[0]);
429 let present_mode = if settings.uncapped_present {
430 pick_present_mode(&caps.present_modes)
431 } else {
432 wgpu::PresentMode::Fifo
433 };
434 eprintln!(
439 "roxlap-gpu: present mode = {present_mode:?} (available: {:?})",
440 caps.present_modes,
441 );
442 let physical = window.inner_size();
443 let surface_config = wgpu::SurfaceConfiguration {
444 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
445 format: surface_format,
446 width: physical.width.max(1),
447 height: physical.height.max(1),
448 present_mode,
449 alpha_mode: caps.alpha_modes[0],
450 view_formats: vec![],
451 desired_maximum_frame_latency: 2,
452 };
453 surface.configure(&device, &surface_config);
454
455 let default_sky_pixel = [0x80u8, 0x80, 0x80, 0xff];
460 let (sky_texture, sky_view) = create_sky_texture(&device, 1, 1, &default_sky_pixel);
461 queue.write_texture(
462 wgpu::ImageCopyTexture {
463 texture: &sky_texture,
464 mip_level: 0,
465 origin: wgpu::Origin3d::ZERO,
466 aspect: wgpu::TextureAspect::All,
467 },
468 &default_sky_pixel,
469 wgpu::ImageDataLayout {
470 offset: 0,
471 bytes_per_row: Some(4),
472 rows_per_image: Some(1),
473 },
474 wgpu::Extent3d {
475 width: 1,
476 height: 1,
477 depth_or_array_layers: 1,
478 },
479 );
480 let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
481 label: Some("roxlap-gpu sky_sampler"),
482 address_mode_u: wgpu::AddressMode::Repeat,
486 address_mode_v: wgpu::AddressMode::Repeat,
487 address_mode_w: wgpu::AddressMode::ClampToEdge,
488 mag_filter: wgpu::FilterMode::Linear,
489 min_filter: wgpu::FilterMode::Linear,
490 mipmap_filter: wgpu::FilterMode::Nearest,
491 ..Default::default()
492 });
493
494 Ok(Self {
495 window,
496 surface,
497 surface_config,
498 device,
499 queue,
500 adapter_info,
501 clear_colour: settings.clear_colour,
502 frame_count: 0,
503 chunk_dda: None,
504 grid_dda: None,
505 scene_dda: None,
506 sky_texture,
507 sky_view,
508 sky_sampler,
509 fog_color: [0.66, 0.74, 0.88],
514 fog_near: 0.0,
515 fog_far: 1.0e30,
516 sprite_registry: None,
517 sprite_model_dda: None,
518 sprite_lod_px: 4.0,
522 scene_mip_scan_dist: 64.0,
524 })
525 }
526
527 pub fn new_blocking(
533 window: Arc<Window>,
534 settings: GpuRendererSettings,
535 ) -> Result<Self, GpuInitError> {
536 pollster::block_on(Self::new(window, settings))
537 }
538
539 pub fn adapter_info(&self) -> &str {
542 &self.adapter_info
543 }
544
545 pub fn window(&self) -> &Window {
546 &self.window
547 }
548
549 pub fn device(&self) -> &wgpu::Device {
552 &self.device
553 }
554
555 pub fn queue(&self) -> &wgpu::Queue {
558 &self.queue
559 }
560
561 pub fn set_sky_panorama(&mut self, rgba: &[u8], width: u32, height: u32) {
572 assert_eq!(
573 rgba.len(),
574 (width as usize) * (height as usize) * 4,
575 "set_sky_panorama: expected w*h*4 bytes, got {}",
576 rgba.len(),
577 );
578 let (tex, view) = create_sky_texture(&self.device, width, height, rgba);
579 self.queue.write_texture(
582 wgpu::ImageCopyTexture {
583 texture: &tex,
584 mip_level: 0,
585 origin: wgpu::Origin3d::ZERO,
586 aspect: wgpu::TextureAspect::All,
587 },
588 rgba,
589 wgpu::ImageDataLayout {
590 offset: 0,
591 bytes_per_row: Some(width * 4),
592 rows_per_image: Some(height),
593 },
594 wgpu::Extent3d {
595 width,
596 height,
597 depth_or_array_layers: 1,
598 },
599 );
600 self.sky_texture = tex;
601 self.sky_view = view;
602 }
603
604 pub fn set_fog(&mut self, color: [f32; 3], near: f32, far: f32) {
610 self.fog_color = color;
611 self.fog_near = near;
612 self.fog_far = far.max(near + 1.0);
613 }
614
615 pub fn resize(&mut self, width: u32, height: u32) {
619 if width == 0 || height == 0 {
620 return;
621 }
622 self.surface_config.width = width;
623 self.surface_config.height = height;
624 self.surface.configure(&self.device, &self.surface_config);
625 self.chunk_dda = None;
626 self.grid_dda = None;
627 self.scene_dda = None;
628 }
629
630 pub fn render(&mut self) {
634 let surf_tex = match self.surface.get_current_texture() {
635 Ok(t) => t,
636 Err(wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost) => {
637 self.surface.configure(&self.device, &self.surface_config);
638 return;
639 }
640 Err(e) => {
641 eprintln!("roxlap-gpu surface error: {e:?}");
642 return;
643 }
644 };
645 let view = surf_tex
646 .texture
647 .create_view(&wgpu::TextureViewDescriptor::default());
648
649 let phase = f64::from(self.frame_count % 1257) * 0.005;
653 let [r, g, b] = self.clear_colour;
654 let drift = (phase.sin() * 0.04 + 0.04).clamp(0.0, 0.1);
655 let clear = wgpu::Color {
656 r: (r + drift).clamp(0.0, 1.0),
657 g: (g + drift * 0.5).clamp(0.0, 1.0),
658 b: (b + drift * 0.25).clamp(0.0, 1.0),
659 a: 1.0,
660 };
661
662 let mut encoder = self
663 .device
664 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
665 label: Some("roxlap-gpu encoder"),
666 });
667 {
668 let _rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
669 label: Some("roxlap-gpu clear"),
670 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
671 view: &view,
672 resolve_target: None,
673 ops: wgpu::Operations {
674 load: wgpu::LoadOp::Clear(clear),
675 store: wgpu::StoreOp::Store,
676 },
677 })],
678 depth_stencil_attachment: None,
679 timestamp_writes: None,
680 occlusion_query_set: None,
681 });
682 }
683 self.queue.submit(std::iter::once(encoder.finish()));
684 surf_tex.present();
685 self.frame_count = self.frame_count.wrapping_add(1);
686 }
687
688 pub fn render_chunk(
700 &mut self,
701 resident: &GpuChunkResident,
702 camera: &Camera,
703 max_scan_dist: u32,
704 ) {
705 let surf_tex = match self.surface.get_current_texture() {
706 Ok(t) => t,
707 Err(wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost) => {
708 self.surface.configure(&self.device, &self.surface_config);
709 return;
710 }
711 Err(e) => {
712 eprintln!("roxlap-gpu surface error: {e:?}");
713 return;
714 }
715 };
716 let surf_view = surf_tex
717 .texture
718 .create_view(&wgpu::TextureViewDescriptor::default());
719
720 let surface_w = self.surface_config.width;
721 let surface_h = self.surface_config.height;
722 let surface_format = self.surface_config.format;
723
724 let needs_build = match &self.chunk_dda {
727 Some(r) => r.storage_size != (surface_w, surface_h),
728 None => true,
729 };
730 if needs_build {
731 self.chunk_dda = Some(self.build_chunk_dda(surface_w, surface_h, surface_format));
732 }
733 let dda = self.chunk_dda.as_ref().expect("just built");
734
735 let uniform = ChunkDdaUniform {
737 camera_pos: camera.position,
738 _pad0: 0.0,
739 camera_right: camera.right,
740 _pad1: 0.0,
741 camera_down: camera.down,
742 _pad2: 0.0,
743 camera_forward: camera.forward,
744 fov_y_rad: camera.fov_y_rad,
745 screen_size: [surface_w, surface_h],
746 vsid: resident.vsid,
747 max_scan_dist,
748 };
749 self.queue
750 .write_buffer(&dda.uniform_buf, 0, bytemuck::bytes_of(&uniform));
751
752 let dda_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
756 label: Some("roxlap-gpu chunk_dda.bg"),
757 layout: &dda.bgl_dda,
758 entries: &[
759 wgpu::BindGroupEntry {
760 binding: 0,
761 resource: dda.uniform_buf.as_entire_binding(),
762 },
763 wgpu::BindGroupEntry {
764 binding: 1,
765 resource: resident.occupancy.as_entire_binding(),
766 },
767 wgpu::BindGroupEntry {
768 binding: 2,
769 resource: resident.color_offsets.as_entire_binding(),
770 },
771 wgpu::BindGroupEntry {
772 binding: 3,
773 resource: resident.colors.as_entire_binding(),
774 },
775 wgpu::BindGroupEntry {
776 binding: 4,
777 resource: wgpu::BindingResource::TextureView(&dda.storage_view),
778 },
779 ],
780 });
781
782 let mut encoder = self
783 .device
784 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
785 label: Some("roxlap-gpu chunk encoder"),
786 });
787 {
788 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
789 label: Some("roxlap-gpu chunk_dda compute"),
790 timestamp_writes: None,
791 });
792 cpass.set_pipeline(&dda.pipeline_dda);
793 cpass.set_bind_group(0, &dda_bg, &[]);
794 cpass.dispatch_workgroups(surface_w.div_ceil(8), surface_h.div_ceil(8), 1);
795 }
796 {
797 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
798 label: Some("roxlap-gpu chunk_dda blit"),
799 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
800 view: &surf_view,
801 resolve_target: None,
802 ops: wgpu::Operations {
803 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
804 store: wgpu::StoreOp::Store,
805 },
806 })],
807 depth_stencil_attachment: None,
808 timestamp_writes: None,
809 occlusion_query_set: None,
810 });
811 rpass.set_pipeline(&dda.pipeline_blit);
812 rpass.set_bind_group(0, &dda.blit_bg, &[]);
813 rpass.draw(0..3, 0..1);
814 }
815 self.queue.submit(std::iter::once(encoder.finish()));
816 surf_tex.present();
817 self.frame_count = self.frame_count.wrapping_add(1);
818 }
819
820 fn build_chunk_dda(
821 &self,
822 width: u32,
823 height: u32,
824 surface_format: wgpu::TextureFormat,
825 ) -> ChunkDdaResources {
826 let storage_tex = self.device.create_texture(&wgpu::TextureDescriptor {
827 label: Some("roxlap-gpu chunk_dda.storage"),
828 size: wgpu::Extent3d {
829 width,
830 height,
831 depth_or_array_layers: 1,
832 },
833 mip_level_count: 1,
834 sample_count: 1,
835 dimension: wgpu::TextureDimension::D2,
836 format: wgpu::TextureFormat::Rgba8Unorm,
837 usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
838 view_formats: &[],
839 });
840 let storage_view = storage_tex.create_view(&wgpu::TextureViewDescriptor::default());
841
842 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
843 label: Some("roxlap-gpu chunk_dda.uniform"),
844 size: std::mem::size_of::<ChunkDdaUniform>() as u64,
845 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
846 mapped_at_creation: false,
847 });
848
849 let dda_shader = self
850 .device
851 .create_shader_module(wgpu::ShaderModuleDescriptor {
852 label: Some("chunk_dda.wgsl"),
853 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/chunk_dda.wgsl").into()),
854 });
855 let bgl_dda = self
856 .device
857 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
858 label: Some("roxlap-gpu chunk_dda.bgl"),
859 entries: &[
860 bgl_uniform_entry(0),
861 bgl_storage_entry(1, true),
862 bgl_storage_entry(2, true),
863 bgl_storage_entry(3, true),
864 wgpu::BindGroupLayoutEntry {
865 binding: 4,
866 visibility: wgpu::ShaderStages::COMPUTE,
867 ty: wgpu::BindingType::StorageTexture {
868 access: wgpu::StorageTextureAccess::WriteOnly,
869 format: wgpu::TextureFormat::Rgba8Unorm,
870 view_dimension: wgpu::TextureViewDimension::D2,
871 },
872 count: None,
873 },
874 ],
875 });
876 let dda_pl = self
877 .device
878 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
879 label: Some("roxlap-gpu chunk_dda.layout"),
880 bind_group_layouts: &[&bgl_dda],
881 push_constant_ranges: &[],
882 });
883 let pipeline_dda = self
884 .device
885 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
886 label: Some("roxlap-gpu chunk_dda.pipeline"),
887 layout: Some(&dda_pl),
888 module: &dda_shader,
889 entry_point: "render_chunk",
890 compilation_options: wgpu::PipelineCompilationOptions::default(),
891 cache: None,
892 });
893
894 let blit_shader = self
897 .device
898 .create_shader_module(wgpu::ShaderModuleDescriptor {
899 label: Some("blit.wgsl"),
900 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/blit.wgsl").into()),
901 });
902 let bgl_blit = self
903 .device
904 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
905 label: Some("roxlap-gpu chunk_dda.blit_bgl"),
906 entries: &[
907 wgpu::BindGroupLayoutEntry {
908 binding: 0,
909 visibility: wgpu::ShaderStages::FRAGMENT,
910 ty: wgpu::BindingType::Texture {
911 sample_type: wgpu::TextureSampleType::Float { filterable: false },
912 view_dimension: wgpu::TextureViewDimension::D2,
913 multisampled: false,
914 },
915 count: None,
916 },
917 wgpu::BindGroupLayoutEntry {
918 binding: 1,
919 visibility: wgpu::ShaderStages::FRAGMENT,
920 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
921 count: None,
922 },
923 ],
924 });
925 let blit_pl = self
926 .device
927 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
928 label: Some("roxlap-gpu chunk_dda.blit_layout"),
929 bind_group_layouts: &[&bgl_blit],
930 push_constant_ranges: &[],
931 });
932 let pipeline_blit = self
933 .device
934 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
935 label: Some("roxlap-gpu chunk_dda.blit_pipeline"),
936 layout: Some(&blit_pl),
937 vertex: wgpu::VertexState {
938 module: &blit_shader,
939 entry_point: "vs_main",
940 compilation_options: wgpu::PipelineCompilationOptions::default(),
941 buffers: &[],
942 },
943 fragment: Some(wgpu::FragmentState {
944 module: &blit_shader,
945 entry_point: "fs_main",
946 compilation_options: wgpu::PipelineCompilationOptions::default(),
947 targets: &[Some(wgpu::ColorTargetState {
948 format: surface_format,
949 blend: None,
950 write_mask: wgpu::ColorWrites::ALL,
951 })],
952 }),
953 primitive: wgpu::PrimitiveState::default(),
954 depth_stencil: None,
955 multisample: wgpu::MultisampleState::default(),
956 multiview: None,
957 cache: None,
958 });
959 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
960 label: Some("roxlap-gpu chunk_dda.blit_sampler"),
961 address_mode_u: wgpu::AddressMode::ClampToEdge,
962 address_mode_v: wgpu::AddressMode::ClampToEdge,
963 address_mode_w: wgpu::AddressMode::ClampToEdge,
964 mag_filter: wgpu::FilterMode::Nearest,
965 min_filter: wgpu::FilterMode::Nearest,
966 mipmap_filter: wgpu::FilterMode::Nearest,
967 ..Default::default()
968 });
969 let blit_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
970 label: Some("roxlap-gpu chunk_dda.blit_bg"),
971 layout: &bgl_blit,
972 entries: &[
973 wgpu::BindGroupEntry {
974 binding: 0,
975 resource: wgpu::BindingResource::TextureView(&storage_view),
976 },
977 wgpu::BindGroupEntry {
978 binding: 1,
979 resource: wgpu::BindingResource::Sampler(&sampler),
980 },
981 ],
982 });
983
984 ChunkDdaResources {
985 storage_size: (width, height),
986 storage_view,
987 uniform_buf,
988 bgl_dda,
989 pipeline_dda,
990 blit_bg,
991 pipeline_blit,
992 _sampler: sampler,
993 }
994 }
995
996 pub fn render_grid(&mut self, grid: &GpuGridResident, camera: &Camera, max_outer_steps: u32) {
1006 let surf_tex = match self.surface.get_current_texture() {
1007 Ok(t) => t,
1008 Err(wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost) => {
1009 self.surface.configure(&self.device, &self.surface_config);
1010 return;
1011 }
1012 Err(e) => {
1013 eprintln!("roxlap-gpu surface error: {e:?}");
1014 return;
1015 }
1016 };
1017 let surf_view = surf_tex
1018 .texture
1019 .create_view(&wgpu::TextureViewDescriptor::default());
1020
1021 let surface_w = self.surface_config.width;
1022 let surface_h = self.surface_config.height;
1023 let surface_format = self.surface_config.format;
1024
1025 let needs_build = match &self.grid_dda {
1026 Some(r) => r.storage_size != (surface_w, surface_h),
1027 None => true,
1028 };
1029 if needs_build {
1030 self.grid_dda = Some(self.build_grid_dda(surface_w, surface_h, surface_format));
1031 }
1032 let dda = self.grid_dda.as_ref().expect("just built");
1033
1034 let uniform = GridDdaUniform {
1035 camera_pos: camera.position,
1036 _pad0: 0.0,
1037 camera_right: camera.right,
1038 _pad1: 0.0,
1039 camera_down: camera.down,
1040 _pad2: 0.0,
1041 camera_forward: camera.forward,
1042 fov_y_rad: camera.fov_y_rad,
1043 screen_size: [surface_w, surface_h],
1044 vsid: grid.vsid,
1045 max_outer_steps,
1046 chunks_dims: grid.chunks_dims,
1047 _pad3: 0,
1048 origin_chunk: grid.origin_chunk,
1049 _pad4: 0,
1050 };
1051 self.queue
1052 .write_buffer(&dda.uniform_buf, 0, bytemuck::bytes_of(&uniform));
1053
1054 let dda_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1055 label: Some("roxlap-gpu grid_dda.bg"),
1056 layout: &dda.bgl_dda,
1057 entries: &[
1058 wgpu::BindGroupEntry {
1059 binding: 0,
1060 resource: dda.uniform_buf.as_entire_binding(),
1061 },
1062 wgpu::BindGroupEntry {
1063 binding: 1,
1064 resource: grid.occupancy.as_entire_binding(),
1065 },
1066 wgpu::BindGroupEntry {
1067 binding: 2,
1068 resource: grid.color_offsets.as_entire_binding(),
1069 },
1070 wgpu::BindGroupEntry {
1071 binding: 3,
1072 resource: grid.colors.as_entire_binding(),
1073 },
1074 wgpu::BindGroupEntry {
1075 binding: 4,
1076 resource: grid.chunk_colors_base.as_entire_binding(),
1077 },
1078 wgpu::BindGroupEntry {
1079 binding: 5,
1080 resource: grid.chunk_occupancy.as_entire_binding(),
1081 },
1082 wgpu::BindGroupEntry {
1083 binding: 6,
1084 resource: wgpu::BindingResource::TextureView(&dda.storage_view),
1085 },
1086 ],
1087 });
1088
1089 let mut encoder = self
1090 .device
1091 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1092 label: Some("roxlap-gpu grid encoder"),
1093 });
1094 {
1095 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1096 label: Some("roxlap-gpu grid_dda compute"),
1097 timestamp_writes: None,
1098 });
1099 cpass.set_pipeline(&dda.pipeline_dda);
1100 cpass.set_bind_group(0, &dda_bg, &[]);
1101 cpass.dispatch_workgroups(surface_w.div_ceil(8), surface_h.div_ceil(8), 1);
1102 }
1103 {
1104 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1105 label: Some("roxlap-gpu grid_dda blit"),
1106 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1107 view: &surf_view,
1108 resolve_target: None,
1109 ops: wgpu::Operations {
1110 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1111 store: wgpu::StoreOp::Store,
1112 },
1113 })],
1114 depth_stencil_attachment: None,
1115 timestamp_writes: None,
1116 occlusion_query_set: None,
1117 });
1118 rpass.set_pipeline(&dda.pipeline_blit);
1119 rpass.set_bind_group(0, &dda.blit_bg, &[]);
1120 rpass.draw(0..3, 0..1);
1121 }
1122 self.queue.submit(std::iter::once(encoder.finish()));
1123 surf_tex.present();
1124 self.frame_count = self.frame_count.wrapping_add(1);
1125 }
1126
1127 fn build_grid_dda(
1128 &self,
1129 width: u32,
1130 height: u32,
1131 surface_format: wgpu::TextureFormat,
1132 ) -> GridDdaResources {
1133 let storage_tex = self.device.create_texture(&wgpu::TextureDescriptor {
1134 label: Some("roxlap-gpu grid_dda.storage"),
1135 size: wgpu::Extent3d {
1136 width,
1137 height,
1138 depth_or_array_layers: 1,
1139 },
1140 mip_level_count: 1,
1141 sample_count: 1,
1142 dimension: wgpu::TextureDimension::D2,
1143 format: wgpu::TextureFormat::Rgba8Unorm,
1144 usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
1145 view_formats: &[],
1146 });
1147 let storage_view = storage_tex.create_view(&wgpu::TextureViewDescriptor::default());
1148
1149 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
1150 label: Some("roxlap-gpu grid_dda.uniform"),
1151 size: std::mem::size_of::<GridDdaUniform>() as u64,
1152 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1153 mapped_at_creation: false,
1154 });
1155
1156 let dda_shader = self
1157 .device
1158 .create_shader_module(wgpu::ShaderModuleDescriptor {
1159 label: Some("grid_dda.wgsl"),
1160 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/grid_dda.wgsl").into()),
1161 });
1162 let bgl_dda = self
1163 .device
1164 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1165 label: Some("roxlap-gpu grid_dda.bgl"),
1166 entries: &[
1167 bgl_uniform_entry(0),
1168 bgl_storage_entry(1, true),
1169 bgl_storage_entry(2, true),
1170 bgl_storage_entry(3, true),
1171 bgl_storage_entry(4, true),
1172 bgl_storage_entry(5, true),
1173 wgpu::BindGroupLayoutEntry {
1174 binding: 6,
1175 visibility: wgpu::ShaderStages::COMPUTE,
1176 ty: wgpu::BindingType::StorageTexture {
1177 access: wgpu::StorageTextureAccess::WriteOnly,
1178 format: wgpu::TextureFormat::Rgba8Unorm,
1179 view_dimension: wgpu::TextureViewDimension::D2,
1180 },
1181 count: None,
1182 },
1183 ],
1184 });
1185 let dda_pl = self
1186 .device
1187 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1188 label: Some("roxlap-gpu grid_dda.layout"),
1189 bind_group_layouts: &[&bgl_dda],
1190 push_constant_ranges: &[],
1191 });
1192 let pipeline_dda = self
1193 .device
1194 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1195 label: Some("roxlap-gpu grid_dda.pipeline"),
1196 layout: Some(&dda_pl),
1197 module: &dda_shader,
1198 entry_point: "render_grid",
1199 compilation_options: wgpu::PipelineCompilationOptions::default(),
1200 cache: None,
1201 });
1202
1203 let blit_shader = self
1204 .device
1205 .create_shader_module(wgpu::ShaderModuleDescriptor {
1206 label: Some("blit.wgsl"),
1207 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/blit.wgsl").into()),
1208 });
1209 let bgl_blit = self
1210 .device
1211 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1212 label: Some("roxlap-gpu grid_dda.blit_bgl"),
1213 entries: &[
1214 wgpu::BindGroupLayoutEntry {
1215 binding: 0,
1216 visibility: wgpu::ShaderStages::FRAGMENT,
1217 ty: wgpu::BindingType::Texture {
1218 sample_type: wgpu::TextureSampleType::Float { filterable: false },
1219 view_dimension: wgpu::TextureViewDimension::D2,
1220 multisampled: false,
1221 },
1222 count: None,
1223 },
1224 wgpu::BindGroupLayoutEntry {
1225 binding: 1,
1226 visibility: wgpu::ShaderStages::FRAGMENT,
1227 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
1228 count: None,
1229 },
1230 ],
1231 });
1232 let blit_pl = self
1233 .device
1234 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1235 label: Some("roxlap-gpu grid_dda.blit_layout"),
1236 bind_group_layouts: &[&bgl_blit],
1237 push_constant_ranges: &[],
1238 });
1239 let pipeline_blit = self
1240 .device
1241 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1242 label: Some("roxlap-gpu grid_dda.blit_pipeline"),
1243 layout: Some(&blit_pl),
1244 vertex: wgpu::VertexState {
1245 module: &blit_shader,
1246 entry_point: "vs_main",
1247 compilation_options: wgpu::PipelineCompilationOptions::default(),
1248 buffers: &[],
1249 },
1250 fragment: Some(wgpu::FragmentState {
1251 module: &blit_shader,
1252 entry_point: "fs_main",
1253 compilation_options: wgpu::PipelineCompilationOptions::default(),
1254 targets: &[Some(wgpu::ColorTargetState {
1255 format: surface_format,
1256 blend: None,
1257 write_mask: wgpu::ColorWrites::ALL,
1258 })],
1259 }),
1260 primitive: wgpu::PrimitiveState::default(),
1261 depth_stencil: None,
1262 multisample: wgpu::MultisampleState::default(),
1263 multiview: None,
1264 cache: None,
1265 });
1266 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
1267 label: Some("roxlap-gpu grid_dda.blit_sampler"),
1268 address_mode_u: wgpu::AddressMode::ClampToEdge,
1269 address_mode_v: wgpu::AddressMode::ClampToEdge,
1270 address_mode_w: wgpu::AddressMode::ClampToEdge,
1271 mag_filter: wgpu::FilterMode::Nearest,
1272 min_filter: wgpu::FilterMode::Nearest,
1273 mipmap_filter: wgpu::FilterMode::Nearest,
1274 ..Default::default()
1275 });
1276 let blit_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1277 label: Some("roxlap-gpu grid_dda.blit_bg"),
1278 layout: &bgl_blit,
1279 entries: &[
1280 wgpu::BindGroupEntry {
1281 binding: 0,
1282 resource: wgpu::BindingResource::TextureView(&storage_view),
1283 },
1284 wgpu::BindGroupEntry {
1285 binding: 1,
1286 resource: wgpu::BindingResource::Sampler(&sampler),
1287 },
1288 ],
1289 });
1290
1291 GridDdaResources {
1292 storage_size: (width, height),
1293 storage_view,
1294 uniform_buf,
1295 bgl_dda,
1296 pipeline_dda,
1297 blit_bg,
1298 pipeline_blit,
1299 _sampler: sampler,
1300 }
1301 }
1302
1303 pub fn render_scene(
1314 &mut self,
1315 scene: &GpuSceneResident,
1316 cameras: &[Camera],
1317 fov_y_rad: f32,
1318 max_outer_steps: u32,
1319 ) {
1320 assert_eq!(
1321 cameras.len(),
1322 scene.grid_count as usize,
1323 "render_scene: {} cameras supplied, scene has {} grids",
1324 cameras.len(),
1325 scene.grid_count,
1326 );
1327 assert!(
1328 scene.grid_count as usize <= SCENE_MAX_GRIDS,
1329 "render_scene: scene has {} grids, shader supports {}",
1330 scene.grid_count,
1331 SCENE_MAX_GRIDS,
1332 );
1333
1334 let surf_tex = match self.surface.get_current_texture() {
1335 Ok(t) => t,
1336 Err(wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost) => {
1337 self.surface.configure(&self.device, &self.surface_config);
1338 return;
1339 }
1340 Err(e) => {
1341 eprintln!("roxlap-gpu surface error: {e:?}");
1342 return;
1343 }
1344 };
1345 let surf_view = surf_tex
1346 .texture
1347 .create_view(&wgpu::TextureViewDescriptor::default());
1348
1349 let surface_w = self.surface_config.width;
1350 let surface_h = self.surface_config.height;
1351 let surface_format = self.surface_config.format;
1352
1353 let needs_build = match &self.scene_dda {
1354 Some(r) => r.storage_size != (surface_w, surface_h),
1355 None => true,
1356 };
1357 if needs_build {
1358 self.scene_dda = Some(self.build_scene_dda(surface_w, surface_h, surface_format));
1359 }
1360 if self.sprite_registry.is_some() && self.sprite_model_dda.is_none() {
1365 self.sprite_model_dda = Some(self.build_sprite_model_dda());
1366 }
1367 let sprite_pass: Option<(u32, u32)> = if let Some(reg) = self.sprite_registry.as_mut() {
1372 if !cameras.is_empty() && reg.instance_capacity > 0 {
1373 let cam = &cameras[0];
1374 #[allow(clippy::cast_precision_loss)]
1375 let aspect = surface_w as f32 / surface_h as f32;
1376 let half_h = (fov_y_rad * 0.5).tan();
1377 let frustum = sprite_model::ViewFrustum {
1378 pos: cam.position,
1379 right: cam.right,
1380 down: cam.down,
1381 forward: cam.forward,
1382 half_w: half_h * aspect,
1383 half_h,
1384 far: 1.0e9,
1385 };
1386 let (visible, tiles_x, _tiles_y) = reg.cull_bin_upload(
1387 &self.device,
1388 &self.queue,
1389 &frustum,
1390 surface_w,
1391 surface_h,
1392 SPRITE_TILE_SIZE,
1393 self.sprite_lod_px,
1394 );
1395 (visible > 0).then_some((visible, tiles_x))
1396 } else {
1397 None
1398 }
1399 } else {
1400 None
1401 };
1402 let dda = self.scene_dda.as_ref().expect("just built");
1403
1404 let mut cam_array = [SceneDdaPerGridCamera::zeroed(); SCENE_MAX_GRIDS];
1406 for (i, cam) in cameras.iter().enumerate() {
1407 cam_array[i] = SceneDdaPerGridCamera {
1408 pos: cam.position,
1409 _pad0: 0.0,
1410 right: cam.right,
1411 _pad1: 0.0,
1412 down: cam.down,
1413 _pad2: 0.0,
1414 forward: cam.forward,
1415 _pad3: 0.0,
1416 };
1417 }
1418 let uniform = SceneDdaUniform {
1419 fov_y_rad,
1420 grid_count: scene.grid_count,
1421 max_outer_steps,
1422 _pad0: 0,
1423 screen_size: [surface_w, surface_h],
1424 _pad1: [0; 2],
1425 cameras: cam_array,
1426 fog_color: [
1427 self.fog_color[0],
1428 self.fog_color[1],
1429 self.fog_color[2],
1430 self.fog_near,
1431 ],
1432 fog_far: self.fog_far,
1433 write_depth: u32::from(self.sprite_registry.is_some()),
1434 occ_page_words: scene.occupancy_page_words,
1435 occ_num_pages: scene.occupancy_num_pages,
1436 mip_scan_dist: self.scene_mip_scan_dist,
1437 _pad2: 0,
1438 _pad3: 0,
1439 _pad4: 0,
1440 };
1441 self.queue
1442 .write_buffer(&dda.uniform_buf, 0, bytemuck::bytes_of(&uniform));
1443
1444 let dda_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1445 label: Some("roxlap-gpu scene_dda.bg"),
1446 layout: &dda.bgl_dda,
1447 entries: &[
1448 wgpu::BindGroupEntry {
1449 binding: 0,
1450 resource: dda.uniform_buf.as_entire_binding(),
1451 },
1452 wgpu::BindGroupEntry {
1455 binding: 1,
1456 resource: scene.occupancy_pages[0].as_entire_binding(),
1457 },
1458 wgpu::BindGroupEntry {
1459 binding: 2,
1460 resource: scene.all_color_offsets.as_entire_binding(),
1461 },
1462 wgpu::BindGroupEntry {
1463 binding: 3,
1464 resource: scene.all_colors.as_entire_binding(),
1465 },
1466 wgpu::BindGroupEntry {
1467 binding: 4,
1468 resource: scene.all_chunk_colors_base.as_entire_binding(),
1469 },
1470 wgpu::BindGroupEntry {
1471 binding: 5,
1472 resource: scene.all_chunk_occupancy.as_entire_binding(),
1473 },
1474 wgpu::BindGroupEntry {
1475 binding: 6,
1476 resource: scene.grid_static_meta.as_entire_binding(),
1477 },
1478 wgpu::BindGroupEntry {
1479 binding: 7,
1480 resource: scene.all_slot_chunk_idx.as_entire_binding(),
1481 },
1482 wgpu::BindGroupEntry {
1483 binding: 8,
1484 resource: wgpu::BindingResource::TextureView(&dda.storage_view),
1485 },
1486 wgpu::BindGroupEntry {
1487 binding: 9,
1488 resource: wgpu::BindingResource::TextureView(&self.sky_view),
1489 },
1490 wgpu::BindGroupEntry {
1491 binding: 10,
1492 resource: wgpu::BindingResource::Sampler(&self.sky_sampler),
1493 },
1494 wgpu::BindGroupEntry {
1495 binding: 11,
1496 resource: dda.depth_buffer.as_entire_binding(),
1497 },
1498 wgpu::BindGroupEntry {
1499 binding: 12,
1500 resource: scene.occupancy_pages[1].as_entire_binding(),
1501 },
1502 wgpu::BindGroupEntry {
1503 binding: 13,
1504 resource: scene.occupancy_pages[2].as_entire_binding(),
1505 },
1506 wgpu::BindGroupEntry {
1507 binding: 14,
1508 resource: scene.occupancy_pages[3].as_entire_binding(),
1509 },
1510 ],
1511 });
1512
1513 let sprite_model_bg = match (&self.sprite_model_dda, &self.sprite_registry, sprite_pass) {
1520 (Some(smd), Some(reg), Some((visible, tiles_x))) => {
1521 let cam = &cameras[0];
1522 let uni = SpriteModelUniform {
1523 cam_pos: cam.position,
1524 _p0: 0.0,
1525 cam_right: cam.right,
1526 _p1: 0.0,
1527 cam_down: cam.down,
1528 _p2: 0.0,
1529 cam_forward: cam.forward,
1530 _p3: 0.0,
1531 fog_color: [
1532 self.fog_color[0],
1533 self.fog_color[1],
1534 self.fog_color[2],
1535 self.fog_near,
1536 ],
1537 screen_size: [surface_w, surface_h],
1538 instance_count: visible,
1539 fog_far: self.fog_far,
1540 fov_y_rad,
1541 tiles_x,
1542 tile_size: SPRITE_TILE_SIZE,
1543 _p6: 0.0,
1544 };
1545 self.queue
1546 .write_buffer(&smd.uniform_buf, 0, bytemuck::bytes_of(&uni));
1547 Some(self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1548 label: Some("roxlap-gpu sprite_model_dda.bg"),
1549 layout: &smd.bgl,
1550 entries: &[
1551 wgpu::BindGroupEntry {
1552 binding: 0,
1553 resource: smd.uniform_buf.as_entire_binding(),
1554 },
1555 wgpu::BindGroupEntry {
1556 binding: 1,
1557 resource: reg.occupancy.as_entire_binding(),
1558 },
1559 wgpu::BindGroupEntry {
1560 binding: 2,
1561 resource: reg.colors.as_entire_binding(),
1562 },
1563 wgpu::BindGroupEntry {
1564 binding: 3,
1565 resource: reg.color_offsets.as_entire_binding(),
1566 },
1567 wgpu::BindGroupEntry {
1568 binding: 4,
1569 resource: reg.model_meta.as_entire_binding(),
1570 },
1571 wgpu::BindGroupEntry {
1572 binding: 5,
1573 resource: reg.instances.as_entire_binding(),
1574 },
1575 wgpu::BindGroupEntry {
1576 binding: 6,
1577 resource: dda.depth_buffer.as_entire_binding(),
1578 },
1579 wgpu::BindGroupEntry {
1580 binding: 7,
1581 resource: wgpu::BindingResource::TextureView(&dda.storage_view),
1582 },
1583 wgpu::BindGroupEntry {
1584 binding: 8,
1585 resource: reg.tile_ranges.as_entire_binding(),
1586 },
1587 wgpu::BindGroupEntry {
1588 binding: 9,
1589 resource: reg.tile_instances.as_entire_binding(),
1590 },
1591 ],
1592 }))
1593 }
1594 _ => None,
1595 };
1596
1597 let mut encoder = self
1598 .device
1599 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1600 label: Some("roxlap-gpu scene encoder"),
1601 });
1602 {
1603 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1604 label: Some("roxlap-gpu scene_dda compute"),
1605 timestamp_writes: None,
1606 });
1607 cpass.set_pipeline(&dda.pipeline_dda);
1608 cpass.set_bind_group(0, &dda_bg, &[]);
1609 cpass.dispatch_workgroups(surface_w.div_ceil(8), surface_h.div_ceil(8), 1);
1610 }
1611 if let (Some(smd), Some(bg)) = (&self.sprite_model_dda, &sprite_model_bg) {
1615 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1616 label: Some("roxlap-gpu sprite_model_dda"),
1617 timestamp_writes: None,
1618 });
1619 cpass.set_pipeline(&smd.pipeline);
1620 cpass.set_bind_group(0, bg, &[]);
1621 cpass.dispatch_workgroups(surface_w.div_ceil(8), surface_h.div_ceil(8), 1);
1622 }
1623 {
1624 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1625 label: Some("roxlap-gpu scene_dda blit"),
1626 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1627 view: &surf_view,
1628 resolve_target: None,
1629 ops: wgpu::Operations {
1630 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1631 store: wgpu::StoreOp::Store,
1632 },
1633 })],
1634 depth_stencil_attachment: None,
1635 timestamp_writes: None,
1636 occlusion_query_set: None,
1637 });
1638 rpass.set_pipeline(&dda.pipeline_blit);
1639 rpass.set_bind_group(0, &dda.blit_bg, &[]);
1640 rpass.draw(0..3, 0..1);
1641 }
1642 self.queue.submit(std::iter::once(encoder.finish()));
1643 surf_tex.present();
1644 self.frame_count = self.frame_count.wrapping_add(1);
1645 }
1646
1647 fn build_scene_dda(
1648 &self,
1649 width: u32,
1650 height: u32,
1651 surface_format: wgpu::TextureFormat,
1652 ) -> SceneDdaResources {
1653 let storage_tex = self.device.create_texture(&wgpu::TextureDescriptor {
1654 label: Some("roxlap-gpu scene_dda.storage"),
1655 size: wgpu::Extent3d {
1656 width,
1657 height,
1658 depth_or_array_layers: 1,
1659 },
1660 mip_level_count: 1,
1661 sample_count: 1,
1662 dimension: wgpu::TextureDimension::D2,
1663 format: wgpu::TextureFormat::Rgba8Unorm,
1664 usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
1665 view_formats: &[],
1666 });
1667 let storage_view = storage_tex.create_view(&wgpu::TextureViewDescriptor::default());
1668
1669 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
1670 label: Some("roxlap-gpu scene_dda.uniform"),
1671 size: std::mem::size_of::<SceneDdaUniform>() as u64,
1672 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1673 mapped_at_creation: false,
1674 });
1675
1676 let depth_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
1680 label: Some("roxlap-gpu scene_dda.depth"),
1681 size: u64::from(width) * u64::from(height) * 4,
1682 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
1683 mapped_at_creation: false,
1684 });
1685 let dda_shader = self
1686 .device
1687 .create_shader_module(wgpu::ShaderModuleDescriptor {
1688 label: Some("scene_dda.wgsl"),
1689 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/scene_dda.wgsl").into()),
1690 });
1691 let bgl_dda = self
1692 .device
1693 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1694 label: Some("roxlap-gpu scene_dda.bgl"),
1695 entries: &[
1696 bgl_uniform_entry(0),
1697 bgl_storage_entry(1, true),
1698 bgl_storage_entry(2, true),
1699 bgl_storage_entry(3, true),
1700 bgl_storage_entry(4, true),
1701 bgl_storage_entry(5, true),
1702 bgl_storage_entry(6, true),
1703 bgl_storage_entry(7, true),
1704 wgpu::BindGroupLayoutEntry {
1705 binding: 8,
1706 visibility: wgpu::ShaderStages::COMPUTE,
1707 ty: wgpu::BindingType::StorageTexture {
1708 access: wgpu::StorageTextureAccess::WriteOnly,
1709 format: wgpu::TextureFormat::Rgba8Unorm,
1710 view_dimension: wgpu::TextureViewDimension::D2,
1711 },
1712 count: None,
1713 },
1714 wgpu::BindGroupLayoutEntry {
1716 binding: 9,
1717 visibility: wgpu::ShaderStages::COMPUTE,
1718 ty: wgpu::BindingType::Texture {
1719 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1720 view_dimension: wgpu::TextureViewDimension::D2,
1721 multisampled: false,
1722 },
1723 count: None,
1724 },
1725 wgpu::BindGroupLayoutEntry {
1726 binding: 10,
1727 visibility: wgpu::ShaderStages::COMPUTE,
1728 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1729 count: None,
1730 },
1731 bgl_storage_entry(11, false),
1733 bgl_storage_entry(12, true),
1736 bgl_storage_entry(13, true),
1737 bgl_storage_entry(14, true),
1738 ],
1739 });
1740 let dda_pl = self
1741 .device
1742 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1743 label: Some("roxlap-gpu scene_dda.layout"),
1744 bind_group_layouts: &[&bgl_dda],
1745 push_constant_ranges: &[],
1746 });
1747 let pipeline_dda = self
1748 .device
1749 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1750 label: Some("roxlap-gpu scene_dda.pipeline"),
1751 layout: Some(&dda_pl),
1752 module: &dda_shader,
1753 entry_point: "render_scene",
1754 compilation_options: wgpu::PipelineCompilationOptions::default(),
1755 cache: None,
1756 });
1757
1758 let blit_shader = self
1759 .device
1760 .create_shader_module(wgpu::ShaderModuleDescriptor {
1761 label: Some("blit.wgsl"),
1762 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/blit.wgsl").into()),
1763 });
1764 let bgl_blit = self
1765 .device
1766 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1767 label: Some("roxlap-gpu scene_dda.blit_bgl"),
1768 entries: &[
1769 wgpu::BindGroupLayoutEntry {
1770 binding: 0,
1771 visibility: wgpu::ShaderStages::FRAGMENT,
1772 ty: wgpu::BindingType::Texture {
1773 sample_type: wgpu::TextureSampleType::Float { filterable: false },
1774 view_dimension: wgpu::TextureViewDimension::D2,
1775 multisampled: false,
1776 },
1777 count: None,
1778 },
1779 wgpu::BindGroupLayoutEntry {
1780 binding: 1,
1781 visibility: wgpu::ShaderStages::FRAGMENT,
1782 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
1783 count: None,
1784 },
1785 ],
1786 });
1787 let blit_pl = self
1788 .device
1789 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1790 label: Some("roxlap-gpu scene_dda.blit_layout"),
1791 bind_group_layouts: &[&bgl_blit],
1792 push_constant_ranges: &[],
1793 });
1794 let pipeline_blit = self
1795 .device
1796 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1797 label: Some("roxlap-gpu scene_dda.blit_pipeline"),
1798 layout: Some(&blit_pl),
1799 vertex: wgpu::VertexState {
1800 module: &blit_shader,
1801 entry_point: "vs_main",
1802 compilation_options: wgpu::PipelineCompilationOptions::default(),
1803 buffers: &[],
1804 },
1805 fragment: Some(wgpu::FragmentState {
1806 module: &blit_shader,
1807 entry_point: "fs_main",
1808 compilation_options: wgpu::PipelineCompilationOptions::default(),
1809 targets: &[Some(wgpu::ColorTargetState {
1810 format: surface_format,
1811 blend: None,
1812 write_mask: wgpu::ColorWrites::ALL,
1813 })],
1814 }),
1815 primitive: wgpu::PrimitiveState::default(),
1816 depth_stencil: None,
1817 multisample: wgpu::MultisampleState::default(),
1818 multiview: None,
1819 cache: None,
1820 });
1821 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
1822 label: Some("roxlap-gpu scene_dda.blit_sampler"),
1823 address_mode_u: wgpu::AddressMode::ClampToEdge,
1824 address_mode_v: wgpu::AddressMode::ClampToEdge,
1825 address_mode_w: wgpu::AddressMode::ClampToEdge,
1826 mag_filter: wgpu::FilterMode::Nearest,
1827 min_filter: wgpu::FilterMode::Nearest,
1828 mipmap_filter: wgpu::FilterMode::Nearest,
1829 ..Default::default()
1830 });
1831 let blit_bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1832 label: Some("roxlap-gpu scene_dda.blit_bg"),
1833 layout: &bgl_blit,
1834 entries: &[
1835 wgpu::BindGroupEntry {
1836 binding: 0,
1837 resource: wgpu::BindingResource::TextureView(&storage_view),
1838 },
1839 wgpu::BindGroupEntry {
1840 binding: 1,
1841 resource: wgpu::BindingResource::Sampler(&sampler),
1842 },
1843 ],
1844 });
1845
1846 SceneDdaResources {
1847 storage_size: (width, height),
1848 storage_view,
1849 uniform_buf,
1850 bgl_dda,
1851 pipeline_dda,
1852 blit_bg,
1853 pipeline_blit,
1854 _sampler: sampler,
1855 depth_buffer,
1856 }
1857 }
1858
1859 pub fn set_sprite_instances(
1862 &mut self,
1863 registry: &sprite_model::SpriteModelRegistry,
1864 instances: &[sprite_model::SpriteInstance],
1865 ) {
1866 if instances.is_empty() {
1867 self.sprite_registry = None;
1868 return;
1869 }
1870 self.sprite_registry = Some(sprite_model::SpriteRegistryResident::upload(
1871 &self.device,
1872 registry,
1873 instances,
1874 ));
1875 }
1876
1877 pub fn set_sprite_lod_px(&mut self, px: f32) {
1883 self.sprite_lod_px = px.max(0.25);
1884 }
1885
1886 pub fn set_scene_mip_scan_dist(&mut self, dist: f32) {
1894 self.scene_mip_scan_dist = dist.max(0.0);
1895 }
1896
1897 fn build_sprite_model_dda(&self) -> SpriteModelDdaResources {
1900 let shader = self
1901 .device
1902 .create_shader_module(wgpu::ShaderModuleDescriptor {
1903 label: Some("sprite_model_dda.wgsl"),
1904 source: wgpu::ShaderSource::Wgsl(
1905 include_str!("../shaders/sprite_model_dda.wgsl").into(),
1906 ),
1907 });
1908 let bgl = self
1909 .device
1910 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1911 label: Some("roxlap-gpu sprite_model_dda.bgl"),
1912 entries: &[
1913 bgl_uniform_entry(0),
1914 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), wgpu::BindGroupLayoutEntry {
1921 binding: 7,
1922 visibility: wgpu::ShaderStages::COMPUTE,
1923 ty: wgpu::BindingType::StorageTexture {
1924 access: wgpu::StorageTextureAccess::WriteOnly,
1925 format: wgpu::TextureFormat::Rgba8Unorm,
1926 view_dimension: wgpu::TextureViewDimension::D2,
1927 },
1928 count: None,
1929 },
1930 bgl_storage_entry(8, true), bgl_storage_entry(9, true), ],
1933 });
1934 let pl = self
1935 .device
1936 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1937 label: Some("roxlap-gpu sprite_model_dda.layout"),
1938 bind_group_layouts: &[&bgl],
1939 push_constant_ranges: &[],
1940 });
1941 let pipeline = self
1942 .device
1943 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1944 label: Some("roxlap-gpu sprite_model_dda.pipeline"),
1945 layout: Some(&pl),
1946 module: &shader,
1947 entry_point: "march",
1948 compilation_options: wgpu::PipelineCompilationOptions::default(),
1949 cache: None,
1950 });
1951 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
1952 label: Some("roxlap-gpu sprite_model_dda.uniform"),
1953 size: std::mem::size_of::<SpriteModelUniform>() as u64,
1954 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1955 mapped_at_creation: false,
1956 });
1957 SpriteModelDdaResources {
1958 bgl,
1959 pipeline,
1960 uniform_buf,
1961 }
1962 }
1963}
1964
1965pub struct HeadlessSceneRenderer {
1972 width: u32,
1973 height: u32,
1974 output_tex: wgpu::Texture,
1975 output_view: wgpu::TextureView,
1976 depth_buffer: wgpu::Buffer,
1977 uniform_buf: wgpu::Buffer,
1978 _sky_texture: wgpu::Texture,
1979 sky_view: wgpu::TextureView,
1980 sky_sampler: wgpu::Sampler,
1981 bgl: wgpu::BindGroupLayout,
1982 pipeline: wgpu::ComputePipeline,
1983 readback: wgpu::Buffer,
1984 padded_bytes_per_row: u32,
1985}
1986
1987impl HeadlessSceneRenderer {
1988 #[must_use]
1993 pub fn new(device: &wgpu::Device, width: u32, height: u32) -> Self {
1994 let output_tex = device.create_texture(&wgpu::TextureDescriptor {
1995 label: Some("roxlap-gpu headless.output"),
1996 size: wgpu::Extent3d {
1997 width,
1998 height,
1999 depth_or_array_layers: 1,
2000 },
2001 mip_level_count: 1,
2002 sample_count: 1,
2003 dimension: wgpu::TextureDimension::D2,
2004 format: wgpu::TextureFormat::Rgba8Unorm,
2005 usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::COPY_SRC,
2006 view_formats: &[],
2007 });
2008 let output_view = output_tex.create_view(&wgpu::TextureViewDescriptor::default());
2009
2010 let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
2011 label: Some("roxlap-gpu headless.uniform"),
2012 size: std::mem::size_of::<SceneDdaUniform>() as u64,
2013 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2014 mapped_at_creation: false,
2015 });
2016 let depth_buffer = device.create_buffer(&wgpu::BufferDescriptor {
2017 label: Some("roxlap-gpu headless.depth"),
2018 size: u64::from(width) * u64::from(height) * 4,
2019 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
2020 mapped_at_creation: false,
2021 });
2022
2023 let default_sky_pixel = [120u8, 150, 220, 255];
2024 let (sky_texture, sky_view) = create_sky_texture(device, 1, 1, &default_sky_pixel);
2025 let sky_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
2026 label: Some("roxlap-gpu headless.sky_sampler"),
2027 address_mode_u: wgpu::AddressMode::Repeat,
2028 address_mode_v: wgpu::AddressMode::Repeat,
2029 mag_filter: wgpu::FilterMode::Linear,
2030 min_filter: wgpu::FilterMode::Linear,
2031 ..Default::default()
2032 });
2033
2034 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
2035 label: Some("scene_dda.wgsl (headless)"),
2036 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/scene_dda.wgsl").into()),
2037 });
2038 let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2039 label: Some("roxlap-gpu headless.bgl"),
2040 entries: &[
2041 bgl_uniform_entry(0),
2042 bgl_storage_entry(1, true),
2043 bgl_storage_entry(2, true),
2044 bgl_storage_entry(3, true),
2045 bgl_storage_entry(4, true),
2046 bgl_storage_entry(5, true),
2047 bgl_storage_entry(6, true),
2048 bgl_storage_entry(7, true),
2049 wgpu::BindGroupLayoutEntry {
2050 binding: 8,
2051 visibility: wgpu::ShaderStages::COMPUTE,
2052 ty: wgpu::BindingType::StorageTexture {
2053 access: wgpu::StorageTextureAccess::WriteOnly,
2054 format: wgpu::TextureFormat::Rgba8Unorm,
2055 view_dimension: wgpu::TextureViewDimension::D2,
2056 },
2057 count: None,
2058 },
2059 wgpu::BindGroupLayoutEntry {
2060 binding: 9,
2061 visibility: wgpu::ShaderStages::COMPUTE,
2062 ty: wgpu::BindingType::Texture {
2063 sample_type: wgpu::TextureSampleType::Float { filterable: true },
2064 view_dimension: wgpu::TextureViewDimension::D2,
2065 multisampled: false,
2066 },
2067 count: None,
2068 },
2069 wgpu::BindGroupLayoutEntry {
2070 binding: 10,
2071 visibility: wgpu::ShaderStages::COMPUTE,
2072 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
2073 count: None,
2074 },
2075 bgl_storage_entry(11, false),
2076 bgl_storage_entry(12, true),
2077 bgl_storage_entry(13, true),
2078 bgl_storage_entry(14, true),
2079 ],
2080 });
2081 let pl = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2082 label: Some("roxlap-gpu headless.layout"),
2083 bind_group_layouts: &[&bgl],
2084 push_constant_ranges: &[],
2085 });
2086 let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
2087 label: Some("roxlap-gpu headless.pipeline"),
2088 layout: Some(&pl),
2089 module: &shader,
2090 entry_point: "render_scene",
2091 compilation_options: wgpu::PipelineCompilationOptions::default(),
2092 cache: None,
2093 });
2094
2095 let padded_bytes_per_row = (width * 4).div_ceil(256) * 256;
2098 let readback = device.create_buffer(&wgpu::BufferDescriptor {
2099 label: Some("roxlap-gpu headless.readback"),
2100 size: u64::from(padded_bytes_per_row) * u64::from(height),
2101 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
2102 mapped_at_creation: false,
2103 });
2104
2105 Self {
2106 width,
2107 height,
2108 output_tex,
2109 output_view,
2110 depth_buffer,
2111 uniform_buf,
2112 _sky_texture: sky_texture,
2113 sky_view,
2114 sky_sampler,
2115 bgl,
2116 pipeline,
2117 readback,
2118 padded_bytes_per_row,
2119 }
2120 }
2121
2122 #[must_use]
2131 #[allow(clippy::too_many_arguments)]
2132 pub fn render(
2133 &self,
2134 device: &wgpu::Device,
2135 queue: &wgpu::Queue,
2136 scene: &GpuSceneResident,
2137 cameras: &[Camera],
2138 fov_y_rad: f32,
2139 max_outer_steps: u32,
2140 mip_scan_dist: f32,
2141 ) -> Vec<u32> {
2142 assert_eq!(
2143 cameras.len(),
2144 scene.grid_count as usize,
2145 "headless render: {} cameras for {} grids",
2146 cameras.len(),
2147 scene.grid_count,
2148 );
2149
2150 let mut cam_array = [SceneDdaPerGridCamera::zeroed(); SCENE_MAX_GRIDS];
2151 for (i, cam) in cameras.iter().enumerate() {
2152 cam_array[i] = SceneDdaPerGridCamera {
2153 pos: cam.position,
2154 _pad0: 0.0,
2155 right: cam.right,
2156 _pad1: 0.0,
2157 down: cam.down,
2158 _pad2: 0.0,
2159 forward: cam.forward,
2160 _pad3: 0.0,
2161 };
2162 }
2163 let uniform = SceneDdaUniform {
2164 fov_y_rad,
2165 grid_count: scene.grid_count,
2166 max_outer_steps,
2167 _pad0: 0,
2168 screen_size: [self.width, self.height],
2169 _pad1: [0; 2],
2170 cameras: cam_array,
2171 fog_color: [0.0, 0.0, 0.0, 1.0e29],
2173 fog_far: 1.0e30,
2174 write_depth: 0,
2175 occ_page_words: scene.occupancy_page_words,
2176 occ_num_pages: scene.occupancy_num_pages,
2177 mip_scan_dist,
2178 _pad2: 0,
2179 _pad3: 0,
2180 _pad4: 0,
2181 };
2182 queue.write_buffer(&self.uniform_buf, 0, bytemuck::bytes_of(&uniform));
2183
2184 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
2185 label: Some("roxlap-gpu headless.bg"),
2186 layout: &self.bgl,
2187 entries: &[
2188 wgpu::BindGroupEntry {
2189 binding: 0,
2190 resource: self.uniform_buf.as_entire_binding(),
2191 },
2192 wgpu::BindGroupEntry {
2193 binding: 1,
2194 resource: scene.occupancy_pages[0].as_entire_binding(),
2195 },
2196 wgpu::BindGroupEntry {
2197 binding: 2,
2198 resource: scene.all_color_offsets.as_entire_binding(),
2199 },
2200 wgpu::BindGroupEntry {
2201 binding: 3,
2202 resource: scene.all_colors.as_entire_binding(),
2203 },
2204 wgpu::BindGroupEntry {
2205 binding: 4,
2206 resource: scene.all_chunk_colors_base.as_entire_binding(),
2207 },
2208 wgpu::BindGroupEntry {
2209 binding: 5,
2210 resource: scene.all_chunk_occupancy.as_entire_binding(),
2211 },
2212 wgpu::BindGroupEntry {
2213 binding: 6,
2214 resource: scene.grid_static_meta.as_entire_binding(),
2215 },
2216 wgpu::BindGroupEntry {
2217 binding: 7,
2218 resource: scene.all_slot_chunk_idx.as_entire_binding(),
2219 },
2220 wgpu::BindGroupEntry {
2221 binding: 8,
2222 resource: wgpu::BindingResource::TextureView(&self.output_view),
2223 },
2224 wgpu::BindGroupEntry {
2225 binding: 9,
2226 resource: wgpu::BindingResource::TextureView(&self.sky_view),
2227 },
2228 wgpu::BindGroupEntry {
2229 binding: 10,
2230 resource: wgpu::BindingResource::Sampler(&self.sky_sampler),
2231 },
2232 wgpu::BindGroupEntry {
2233 binding: 11,
2234 resource: self.depth_buffer.as_entire_binding(),
2235 },
2236 wgpu::BindGroupEntry {
2237 binding: 12,
2238 resource: scene.occupancy_pages[1].as_entire_binding(),
2239 },
2240 wgpu::BindGroupEntry {
2241 binding: 13,
2242 resource: scene.occupancy_pages[2].as_entire_binding(),
2243 },
2244 wgpu::BindGroupEntry {
2245 binding: 14,
2246 resource: scene.occupancy_pages[3].as_entire_binding(),
2247 },
2248 ],
2249 });
2250
2251 let mut enc =
2252 device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
2253 {
2254 let mut pass = enc.begin_compute_pass(&wgpu::ComputePassDescriptor {
2255 label: Some("roxlap-gpu headless.pass"),
2256 timestamp_writes: None,
2257 });
2258 pass.set_pipeline(&self.pipeline);
2259 pass.set_bind_group(0, &bg, &[]);
2260 pass.dispatch_workgroups(self.width.div_ceil(8), self.height.div_ceil(8), 1);
2261 }
2262 enc.copy_texture_to_buffer(
2263 wgpu::ImageCopyTexture {
2264 texture: &self.output_tex,
2265 mip_level: 0,
2266 origin: wgpu::Origin3d::ZERO,
2267 aspect: wgpu::TextureAspect::All,
2268 },
2269 wgpu::ImageCopyBuffer {
2270 buffer: &self.readback,
2271 layout: wgpu::ImageDataLayout {
2272 offset: 0,
2273 bytes_per_row: Some(self.padded_bytes_per_row),
2274 rows_per_image: Some(self.height),
2275 },
2276 },
2277 wgpu::Extent3d {
2278 width: self.width,
2279 height: self.height,
2280 depth_or_array_layers: 1,
2281 },
2282 );
2283 queue.submit(Some(enc.finish()));
2284
2285 let slice = self.readback.slice(..);
2286 let (tx, rx) = std::sync::mpsc::channel();
2287 slice.map_async(wgpu::MapMode::Read, move |r| {
2288 let _ = tx.send(r);
2289 });
2290 device.poll(wgpu::Maintain::Wait);
2291 rx.recv().expect("map_async channel").expect("map_async");
2292
2293 let data = slice.get_mapped_range();
2294 let mut out = Vec::with_capacity((self.width * self.height) as usize);
2295 let pitch = self.padded_bytes_per_row as usize;
2296 for y in 0..self.height as usize {
2297 let row = &data[y * pitch..y * pitch + self.width as usize * 4];
2298 for px in row.chunks_exact(4) {
2299 out.push(
2300 u32::from(px[0])
2301 | (u32::from(px[1]) << 8)
2302 | (u32::from(px[2]) << 16)
2303 | (u32::from(px[3]) << 24),
2304 );
2305 }
2306 }
2307 drop(data);
2308 self.readback.unmap();
2309 out
2310 }
2311}
2312
2313fn bgl_uniform_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
2314 wgpu::BindGroupLayoutEntry {
2315 binding,
2316 visibility: wgpu::ShaderStages::COMPUTE,
2317 ty: wgpu::BindingType::Buffer {
2318 ty: wgpu::BufferBindingType::Uniform,
2319 has_dynamic_offset: false,
2320 min_binding_size: None,
2321 },
2322 count: None,
2323 }
2324}
2325
2326fn bgl_storage_entry(binding: u32, read_only: bool) -> wgpu::BindGroupLayoutEntry {
2327 wgpu::BindGroupLayoutEntry {
2328 binding,
2329 visibility: wgpu::ShaderStages::COMPUTE,
2330 ty: wgpu::BindingType::Buffer {
2331 ty: wgpu::BufferBindingType::Storage { read_only },
2332 has_dynamic_offset: false,
2333 min_binding_size: None,
2334 },
2335 count: None,
2336 }
2337}
2338
2339fn create_sky_texture(
2344 device: &wgpu::Device,
2345 width: u32,
2346 height: u32,
2347 _initial_pixels: &[u8],
2348) -> (wgpu::Texture, wgpu::TextureView) {
2349 let tex = device.create_texture(&wgpu::TextureDescriptor {
2350 label: Some("roxlap-gpu sky_texture"),
2351 size: wgpu::Extent3d {
2352 width,
2353 height,
2354 depth_or_array_layers: 1,
2355 },
2356 mip_level_count: 1,
2357 sample_count: 1,
2358 dimension: wgpu::TextureDimension::D2,
2359 format: wgpu::TextureFormat::Rgba8Unorm,
2360 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2361 view_formats: &[],
2362 });
2363 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
2364 (tex, view)
2365}
2366
2367pub(crate) fn pick_required_limits(adapter_limits: &wgpu::Limits) -> wgpu::Limits {
2375 wgpu::Limits {
2376 max_storage_buffer_binding_size: adapter_limits.max_storage_buffer_binding_size,
2377 max_buffer_size: adapter_limits.max_buffer_size,
2378 max_storage_buffers_per_shader_stage: adapter_limits
2383 .max_storage_buffers_per_shader_stage
2384 .min(16),
2385 ..wgpu::Limits::default()
2386 }
2387}
2388
2389fn pick_present_mode(modes: &[wgpu::PresentMode]) -> wgpu::PresentMode {
2390 for &m in &[wgpu::PresentMode::Mailbox, wgpu::PresentMode::Immediate] {
2393 if modes.contains(&m) {
2394 return m;
2395 }
2396 }
2397 wgpu::PresentMode::Fifo
2398}