1use bytemuck::{Pod, Zeroable};
8
9use crate::GpuRenderer;
10
11#[derive(Clone, Copy, Debug)]
24pub struct GpuLine {
25 pub a: [f32; 3],
27 pub b: [f32; 3],
29 pub color: [f32; 4],
32 pub width_px: f32,
35 pub depth_test: bool,
39}
40
41#[derive(Clone, Copy, Debug)]
45pub struct GpuLineCamera {
46 pub pos: [f32; 3],
48 pub right: [f32; 3],
51 pub down: [f32; 3],
53 pub forward: [f32; 3],
56}
57
58pub(crate) const LINE_NEAR_Z: f32 = 0.0625;
61const LINE_DEPTH_BIAS: f32 = 0.5;
64
65#[repr(C)]
69#[derive(Clone, Copy, Pod, Zeroable)]
70struct LineVertex {
71 pos: [f32; 2],
72 depth: f32,
73 depth_test: f32,
74 color: [f32; 4],
75}
76
77#[repr(C)]
80#[derive(Clone, Copy, Pod, Zeroable)]
81struct LineParams {
82 screen_w: u32,
84 screen_h: u32,
85 depth_bias: f32,
86 no_depth: u32,
87 flip_x: u32,
91 depth_w: u32,
95 depth_h: u32,
96 _pad: u32,
97}
98
99pub(crate) struct LineResources {
103 pipeline: wgpu::RenderPipeline,
104 bgl: wgpu::BindGroupLayout,
105 uniform_buf: wgpu::Buffer,
106 dummy_depth: wgpu::Buffer,
109}
110
111fn build_line_vertices(
119 cam: &GpuLineCamera,
120 lines: &[GpuLine],
121 w: u32,
122 h: u32,
123 fov_y: f32,
124 flip_x: bool,
125) -> Vec<LineVertex> {
126 let aspect = w as f32 / h as f32;
127 let half_h = (fov_y * 0.5).tan();
128 let half_w = half_h * aspect;
129 let (wf, hf) = (w as f32, h as f32);
130
131 let cam_coords = |p: [f32; 3]| -> [f32; 3] {
132 let d = [p[0] - cam.pos[0], p[1] - cam.pos[1], p[2] - cam.pos[2]];
133 [
134 cam.right[0] * d[0] + cam.right[1] * d[1] + cam.right[2] * d[2],
135 cam.down[0] * d[0] + cam.down[1] * d[1] + cam.down[2] * d[2],
136 cam.forward[0] * d[0] + cam.forward[1] * d[1] + cam.forward[2] * d[2],
137 ]
138 };
139 let project = |q: [f32; 3]| -> ([f32; 2], f32) {
142 let inv = 1.0 / q[2];
143 let nx = q[0] * inv / half_w;
144 let ny = -q[1] * inv / half_h;
145 let depth = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2]).sqrt();
146 ([nx, ny], depth)
147 };
148
149 let mut out = Vec::with_capacity(lines.len() * 6);
150 for line in lines {
151 let ca = cam_coords(line.a);
152 let cb = cam_coords(line.b);
153 let (cfa, cfb) = (ca[2], cb[2]);
154 if cfa < LINE_NEAR_Z && cfb < LINE_NEAR_Z {
155 continue;
156 }
157 let (mut t0, mut t1) = (0.0f32, 1.0f32);
159 let dz = cfb - cfa;
160 if dz.abs() > f32::EPSILON {
161 let tn = (LINE_NEAR_Z - cfa) / dz;
162 if dz > 0.0 {
163 t0 = t0.max(tn);
164 } else {
165 t1 = t1.min(tn);
166 }
167 }
168 if t0 > t1 {
169 continue;
170 }
171 let lerp3 = |t: f32| {
172 [
173 ca[0] + (cb[0] - ca[0]) * t,
174 ca[1] + (cb[1] - ca[1]) * t,
175 ca[2] + (cb[2] - ca[2]) * t,
176 ]
177 };
178 let (n0, d0) = project(lerp3(t0));
179 let (n1, d1) = project(lerp3(t1));
180
181 let to_px = |n: [f32; 2]| [(n[0] * 0.5 + 0.5) * wf, (0.5 - n[1] * 0.5) * hf];
183 let to_ndc = |p: [f32; 2]| [p[0] / wf * 2.0 - 1.0, 1.0 - p[1] / hf * 2.0];
184 let p0 = to_px(n0);
185 let p1 = to_px(n1);
186 let (dx, dy) = (p1[0] - p0[0], p1[1] - p0[1]);
187 let len = (dx * dx + dy * dy).sqrt().max(1e-6);
188 let half = line.width_px.max(1.0) * 0.5;
189 let (ex, ey) = (-dy / len * half, dx / len * half);
190
191 let c0a = to_ndc([p0[0] + ex, p0[1] + ey]);
192 let c0b = to_ndc([p0[0] - ex, p0[1] - ey]);
193 let c1a = to_ndc([p1[0] + ex, p1[1] + ey]);
194 let c1b = to_ndc([p1[0] - ex, p1[1] - ey]);
195 let dt = if line.depth_test { 1.0 } else { 0.0 };
196 let vert = |pos: [f32; 2], depth: f32| LineVertex {
198 pos: [if flip_x { -pos[0] } else { pos[0] }, pos[1]],
199 depth,
200 depth_test: dt,
201 color: line.color,
202 };
203 out.push(vert(c0a, d0));
205 out.push(vert(c0b, d0));
206 out.push(vert(c1a, d1));
207 out.push(vert(c1a, d1));
208 out.push(vert(c0b, d0));
209 out.push(vert(c1b, d1));
210 }
211 out
212}
213
214#[derive(Clone, Copy, Debug)]
222pub struct GpuImageQuad {
223 pub corners: [[f32; 3]; 4],
228 pub image: usize,
231 pub tint: [f32; 4],
235 pub depth_test: bool,
238 pub alpha_cutoff: f32,
241}
242
243#[repr(C)]
249#[derive(Clone, Copy, Pod, Zeroable)]
250struct ImageVertex {
251 ndc: [f32; 2],
252 w: f32,
253 depth: f32,
254 depth_test: f32,
255 cutoff: f32,
256 uv: [f32; 2],
257 tint: [f32; 4],
258}
259
260pub(crate) struct ImageResources {
264 pipeline: wgpu::RenderPipeline,
265 bgl: wgpu::BindGroupLayout,
266 uniform_buf: wgpu::Buffer,
267 dummy_depth: wgpu::Buffer,
268 sampler: wgpu::Sampler,
269}
270
271pub(crate) struct ImageResident {
274 view: wgpu::TextureView,
275 _texture: wgpu::Texture,
277}
278
279#[derive(Clone, Copy)]
282struct ImgClipV {
283 cam: [f32; 3],
284 uv: [f32; 2],
285}
286
287fn clip_near_image(poly: &[ImgClipV]) -> Vec<ImgClipV> {
290 let n = poly.len();
291 let mut out: Vec<ImgClipV> = Vec::with_capacity(n + 1);
292 for i in 0..n {
293 let cur = poly[i];
294 let prev = poly[(i + n - 1) % n];
295 let cur_in = cur.cam[2] >= LINE_NEAR_Z;
296 let prev_in = prev.cam[2] >= LINE_NEAR_Z;
297 if cur_in != prev_in {
298 let t = (LINE_NEAR_Z - prev.cam[2]) / (cur.cam[2] - prev.cam[2]);
299 out.push(ImgClipV {
300 cam: [
301 prev.cam[0] + (cur.cam[0] - prev.cam[0]) * t,
302 prev.cam[1] + (cur.cam[1] - prev.cam[1]) * t,
303 LINE_NEAR_Z,
304 ],
305 uv: [
306 prev.uv[0] + (cur.uv[0] - prev.uv[0]) * t,
307 prev.uv[1] + (cur.uv[1] - prev.uv[1]) * t,
308 ],
309 });
310 }
311 if cur_in {
312 out.push(cur);
313 }
314 }
315 out
316}
317
318fn build_image_vertices(
324 cam: &GpuLineCamera,
325 quad: &GpuImageQuad,
326 w: u32,
327 h: u32,
328 fov_y: f32,
329 flip_x: bool,
330) -> Vec<ImageVertex> {
331 let aspect = w as f32 / h as f32;
332 let half_h = (fov_y * 0.5).tan();
333 let half_w = half_h * aspect;
334 let dt = if quad.depth_test { 1.0 } else { 0.0 };
335
336 let cam_coords = |p: [f32; 3]| -> [f32; 3] {
337 let d = [p[0] - cam.pos[0], p[1] - cam.pos[1], p[2] - cam.pos[2]];
338 [
339 cam.right[0] * d[0] + cam.right[1] * d[1] + cam.right[2] * d[2],
340 cam.down[0] * d[0] + cam.down[1] * d[1] + cam.down[2] * d[2],
341 cam.forward[0] * d[0] + cam.forward[1] * d[1] + cam.forward[2] * d[2],
342 ]
343 };
344 let project = |v: ImgClipV| -> ImageVertex {
345 let (cx, cy, cz) = (v.cam[0], v.cam[1], v.cam[2]);
346 let nx = cx / (cz * half_w);
347 ImageVertex {
348 ndc: [if flip_x { -nx } else { nx }, -cy / (cz * half_h)],
350 w: cz,
351 depth: (cx * cx + cy * cy + cz * cz).sqrt(),
352 depth_test: dt,
353 cutoff: quad.alpha_cutoff,
354 uv: v.uv,
355 tint: quad.tint,
356 }
357 };
358
359 let uvs = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
361 let verts: Vec<ImgClipV> = quad
362 .corners
363 .iter()
364 .zip(uvs)
365 .map(|(c, uv)| ImgClipV {
366 cam: cam_coords(*c),
367 uv,
368 })
369 .collect();
370
371 let mut out = Vec::with_capacity(12);
372 for tri in [[0usize, 1, 2], [1, 3, 2]] {
373 let poly = [verts[tri[0]], verts[tri[1]], verts[tri[2]]];
374 let clipped = clip_near_image(&poly);
375 if clipped.len() < 3 {
376 continue;
377 }
378 for i in 1..clipped.len() - 1 {
379 out.push(project(clipped[0]));
380 out.push(project(clipped[i]));
381 out.push(project(clipped[i + 1]));
382 }
383 }
384 out
385}
386
387impl GpuRenderer {
388 pub fn draw_lines_deferred(&mut self, cam: &GpuLineCamera, lines: &[GpuLine]) {
398 if self.pending_frame.is_none() || lines.is_empty() {
399 return;
400 }
401 let (w, h) = (self.surface_config.width, self.surface_config.height);
402 let (rw, rh) = self.render_dims();
405 let fov = self.last_fov_y_rad;
406 if w == 0 || h == 0 || fov <= 0.0 {
407 return; }
409 let verts = build_line_vertices(cam, lines, rw, rh, fov, self.flip_x);
410 if verts.is_empty() {
411 return;
412 }
413 self.ensure_line_resources();
414 let res = self.line_resources.as_ref().expect("just built");
415
416 let no_depth = u32::from(self.scene_dda.is_none() || !self.dirty.scene_depth_valid);
423 let params = LineParams {
424 screen_w: w,
425 screen_h: h,
426 depth_bias: LINE_DEPTH_BIAS,
427 no_depth,
428 flip_x: u32::from(self.flip_x),
429 depth_w: rw,
430 depth_h: rh,
431 _pad: 0,
432 };
433 self.queue
434 .write_buffer(&res.uniform_buf, 0, bytemuck::bytes_of(¶ms));
435
436 let depth_key: Option<wgpu::Buffer> =
441 self.scene_dda.as_ref().map(|dda| dda.depth_buffer.clone());
442 if !matches!(&self.line_bg_cache, Some((_, key)) if *key == depth_key) {
443 let depth_resource = match &self.scene_dda {
444 Some(dda) => dda.depth_buffer.as_entire_binding(),
445 None => res.dummy_depth.as_entire_binding(),
446 };
447 let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
448 label: Some("roxlap-gpu line.bg"),
449 layout: &res.bgl,
450 entries: &[
451 wgpu::BindGroupEntry {
452 binding: 0,
453 resource: res.uniform_buf.as_entire_binding(),
454 },
455 wgpu::BindGroupEntry {
456 binding: 1,
457 resource: depth_resource,
458 },
459 ],
460 });
461 self.line_bg_cache = Some((bg, depth_key));
462 }
463 let bg = &self.line_bg_cache.as_ref().expect("just ensured").0;
464
465 let needed = std::mem::size_of_val(verts.as_slice()) as u64;
469 if self.line_vbuf_cap < needed {
470 let cap = needed.next_power_of_two().max(4096);
471 self.line_vbuf = Some(self.device.create_buffer(&wgpu::BufferDescriptor {
472 label: Some("roxlap-gpu line.vbuf"),
473 size: cap,
474 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
475 mapped_at_creation: false,
476 }));
477 self.line_vbuf_cap = cap;
478 }
479 let vbuf = self.line_vbuf.as_ref().expect("ensured above");
480 self.queue
481 .write_buffer(vbuf, 0, bytemuck::cast_slice(&verts));
482
483 let view = &self.pending_frame.as_ref().expect("checked above").1;
484 let mut encoder = self
485 .device
486 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
487 label: Some("roxlap-gpu lines"),
488 });
489 {
490 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
493 label: Some("roxlap-gpu line paint"),
494 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
495 view,
496 depth_slice: None,
497 resolve_target: None,
498 ops: wgpu::Operations {
499 load: wgpu::LoadOp::Load,
500 store: wgpu::StoreOp::Store,
501 },
502 })],
503 depth_stencil_attachment: None,
504 timestamp_writes: None,
505 occlusion_query_set: None,
506 multiview_mask: None,
507 });
508 pass.set_pipeline(&res.pipeline);
509 pass.set_bind_group(0, bg, &[]);
510 pass.set_vertex_buffer(0, vbuf.slice(..));
511 pass.draw(0..verts.len() as u32, 0..1);
512 }
513 self.queue.submit(std::iter::once(encoder.finish()));
514 }
516
517 fn ensure_line_resources(&mut self) {
522 if self.line_resources.is_some() {
523 return;
524 }
525 let shader = self
526 .device
527 .create_shader_module(wgpu::ShaderModuleDescriptor {
528 label: Some("line.wgsl"),
529 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/line.wgsl").into()),
530 });
531 let bgl = self
532 .device
533 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
534 label: Some("roxlap-gpu line.bgl"),
535 entries: &[
536 wgpu::BindGroupLayoutEntry {
537 binding: 0,
538 visibility: wgpu::ShaderStages::FRAGMENT,
539 ty: wgpu::BindingType::Buffer {
540 ty: wgpu::BufferBindingType::Uniform,
541 has_dynamic_offset: false,
542 min_binding_size: None,
543 },
544 count: None,
545 },
546 wgpu::BindGroupLayoutEntry {
547 binding: 1,
548 visibility: wgpu::ShaderStages::FRAGMENT,
549 ty: wgpu::BindingType::Buffer {
550 ty: wgpu::BufferBindingType::Storage { read_only: true },
551 has_dynamic_offset: false,
552 min_binding_size: None,
553 },
554 count: None,
555 },
556 ],
557 });
558 let layout = self
559 .device
560 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
561 label: Some("roxlap-gpu line.layout"),
562 bind_group_layouts: &[Some(&bgl)],
563 immediate_size: 0,
564 });
565 let pipeline = self
566 .device
567 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
568 label: Some("roxlap-gpu line.pipeline"),
569 layout: Some(&layout),
570 vertex: wgpu::VertexState {
571 module: &shader,
572 entry_point: Some("vs_main"),
573 compilation_options: wgpu::PipelineCompilationOptions::default(),
574 buffers: &[wgpu::VertexBufferLayout {
575 array_stride: std::mem::size_of::<LineVertex>() as u64,
576 step_mode: wgpu::VertexStepMode::Vertex,
577 attributes: &wgpu::vertex_attr_array![
578 0 => Float32x2, 1 => Float32, 2 => Float32, 3 => Float32x4, ],
583 }],
584 },
585 fragment: Some(wgpu::FragmentState {
586 module: &shader,
587 entry_point: Some("fs_main"),
588 compilation_options: wgpu::PipelineCompilationOptions::default(),
589 targets: &[Some(wgpu::ColorTargetState {
590 format: self.surface_config.format,
591 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
592 write_mask: wgpu::ColorWrites::ALL,
593 })],
594 }),
595 primitive: wgpu::PrimitiveState {
596 cull_mode: None,
597 ..Default::default()
598 },
599 depth_stencil: None,
600 multisample: wgpu::MultisampleState::default(),
601 multiview_mask: None,
602 cache: None,
603 });
604 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
605 label: Some("roxlap-gpu line.uniform"),
606 size: std::mem::size_of::<LineParams>() as u64,
607 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
608 mapped_at_creation: false,
609 });
610 let dummy_depth = self.device.create_buffer(&wgpu::BufferDescriptor {
611 label: Some("roxlap-gpu line.dummy_depth"),
612 size: 4,
613 usage: wgpu::BufferUsages::STORAGE,
614 mapped_at_creation: false,
615 });
616 self.line_resources = Some(LineResources {
617 pipeline,
618 bgl,
619 uniform_buf,
620 dummy_depth,
621 });
622 }
623
624 pub fn upload_image(&mut self, rgba: &[u8], width: u32, height: u32) -> usize {
630 if width == 0 || height == 0 || rgba.len() != (width as usize) * (height as usize) * 4 {
631 return 0;
632 }
633 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
634 label: Some("roxlap-gpu image_sprite"),
635 size: wgpu::Extent3d {
636 width,
637 height,
638 depth_or_array_layers: 1,
639 },
640 mip_level_count: 1,
641 sample_count: 1,
642 dimension: wgpu::TextureDimension::D2,
643 format: wgpu::TextureFormat::Rgba8Unorm,
644 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
645 view_formats: &[],
646 });
647 self.queue.write_texture(
648 wgpu::TexelCopyTextureInfo {
649 texture: &texture,
650 mip_level: 0,
651 origin: wgpu::Origin3d::ZERO,
652 aspect: wgpu::TextureAspect::All,
653 },
654 rgba,
655 wgpu::TexelCopyBufferLayout {
656 offset: 0,
657 bytes_per_row: Some(width * 4),
658 rows_per_image: Some(height),
659 },
660 wgpu::Extent3d {
661 width,
662 height,
663 depth_or_array_layers: 1,
664 },
665 );
666 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
667 let resident = ImageResident {
668 view,
669 _texture: texture,
670 };
671 if let Some(slot) = self.images.iter().position(Option::is_none) {
672 self.images[slot] = Some(resident);
673 self.image_bg_cache.remove(&slot);
676 slot
677 } else {
678 self.images.push(Some(resident));
679 self.images.len() - 1
680 }
681 }
682
683 pub fn drop_image(&mut self, id: usize) {
686 if let Some(slot) = self.images.get_mut(id) {
687 *slot = None;
688 self.image_bg_cache.remove(&id);
689 }
690 }
691
692 pub fn draw_images_deferred(&mut self, cam: &GpuLineCamera, quads: &[GpuImageQuad]) {
702 if self.pending_frame.is_none() || quads.is_empty() {
703 return;
704 }
705 let (w, h) = (self.surface_config.width, self.surface_config.height);
706 let (rw, rh) = self.render_dims();
709 let fov = self.last_fov_y_rad;
710 if w == 0 || h == 0 || fov <= 0.0 {
711 return;
712 }
713
714 let mut verts: Vec<ImageVertex> = Vec::new();
717 let mut draws: Vec<(u32, u32, usize)> = Vec::new();
718 for quad in quads {
719 if !matches!(self.images.get(quad.image), Some(Some(_))) {
720 continue; }
722 let v = build_image_vertices(cam, quad, rw, rh, fov, self.flip_x);
723 if v.is_empty() {
724 continue;
725 }
726 let start = verts.len() as u32;
727 verts.extend_from_slice(&v);
728 draws.push((start, verts.len() as u32, quad.image));
729 }
730 if draws.is_empty() {
731 return;
732 }
733
734 self.ensure_image_resources();
735 let no_depth = u32::from(self.scene_dda.is_none() || !self.dirty.scene_depth_valid);
738 let params = LineParams {
739 screen_w: w,
740 screen_h: h,
741 depth_bias: LINE_DEPTH_BIAS,
742 no_depth,
743 flip_x: u32::from(self.flip_x),
744 depth_w: rw,
745 depth_h: rh,
746 _pad: 0,
747 };
748 {
749 let res = self.image_resources.as_ref().expect("just built");
750 self.queue
751 .write_buffer(&res.uniform_buf, 0, bytemuck::bytes_of(¶ms));
752 }
753
754 let needed = std::mem::size_of_val(verts.as_slice()) as u64;
756 if self.image_vbuf_cap < needed {
757 let cap = needed.next_power_of_two().max(4096);
758 self.image_vbuf = Some(self.device.create_buffer(&wgpu::BufferDescriptor {
759 label: Some("roxlap-gpu image.vbuf"),
760 size: cap,
761 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
762 mapped_at_creation: false,
763 }));
764 self.image_vbuf_cap = cap;
765 }
766 let vbuf = self.image_vbuf.as_ref().expect("ensured above");
767 self.queue
768 .write_buffer(vbuf, 0, bytemuck::cast_slice(&verts));
769
770 let res = self.image_resources.as_ref().expect("just built");
776 let depth_key: Option<wgpu::Buffer> =
777 self.scene_dda.as_ref().map(|dda| dda.depth_buffer.clone());
778 if self.image_bg_depth != depth_key {
779 self.image_bg_cache.clear();
780 self.image_bg_depth = depth_key;
781 }
782 let depth_resource = match &self.scene_dda {
783 Some(dda) => dda.depth_buffer.as_entire_binding(),
784 None => res.dummy_depth.as_entire_binding(),
785 };
786 for &(_, _, image_id) in &draws {
787 if self.image_bg_cache.contains_key(&image_id) {
788 continue;
789 }
790 let resident = self.images[image_id].as_ref().expect("checked present");
791 let bg = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
792 label: Some("roxlap-gpu image.bg"),
793 layout: &res.bgl,
794 entries: &[
795 wgpu::BindGroupEntry {
796 binding: 0,
797 resource: res.uniform_buf.as_entire_binding(),
798 },
799 wgpu::BindGroupEntry {
800 binding: 1,
801 resource: depth_resource.clone(),
802 },
803 wgpu::BindGroupEntry {
804 binding: 2,
805 resource: wgpu::BindingResource::TextureView(&resident.view),
806 },
807 wgpu::BindGroupEntry {
808 binding: 3,
809 resource: wgpu::BindingResource::Sampler(&res.sampler),
810 },
811 ],
812 });
813 self.image_bg_cache.insert(image_id, bg);
814 }
815
816 let view = &self.pending_frame.as_ref().expect("checked above").1;
817 let mut encoder = self
818 .device
819 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
820 label: Some("roxlap-gpu images"),
821 });
822 {
823 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
824 label: Some("roxlap-gpu image paint"),
825 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
826 view,
827 depth_slice: None,
828 resolve_target: None,
829 ops: wgpu::Operations {
830 load: wgpu::LoadOp::Load,
831 store: wgpu::StoreOp::Store,
832 },
833 })],
834 depth_stencil_attachment: None,
835 timestamp_writes: None,
836 occlusion_query_set: None,
837 multiview_mask: None,
838 });
839 pass.set_pipeline(&res.pipeline);
840 pass.set_vertex_buffer(0, vbuf.slice(..));
841 for &(start, end, image_id) in &draws {
842 let bg = self.image_bg_cache.get(&image_id).expect("just ensured");
843 pass.set_bind_group(0, bg, &[]);
844 pass.draw(start..end, 0..1);
845 }
846 }
847 self.queue.submit(std::iter::once(encoder.finish()));
848 }
850
851 fn ensure_image_resources(&mut self) {
855 if self.image_resources.is_some() {
856 return;
857 }
858 let shader = self
859 .device
860 .create_shader_module(wgpu::ShaderModuleDescriptor {
861 label: Some("image.wgsl"),
862 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/image.wgsl").into()),
863 });
864 let bgl = self
865 .device
866 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
867 label: Some("roxlap-gpu image.bgl"),
868 entries: &[
869 wgpu::BindGroupLayoutEntry {
870 binding: 0,
871 visibility: wgpu::ShaderStages::FRAGMENT,
872 ty: wgpu::BindingType::Buffer {
873 ty: wgpu::BufferBindingType::Uniform,
874 has_dynamic_offset: false,
875 min_binding_size: None,
876 },
877 count: None,
878 },
879 wgpu::BindGroupLayoutEntry {
880 binding: 1,
881 visibility: wgpu::ShaderStages::FRAGMENT,
882 ty: wgpu::BindingType::Buffer {
883 ty: wgpu::BufferBindingType::Storage { read_only: true },
884 has_dynamic_offset: false,
885 min_binding_size: None,
886 },
887 count: None,
888 },
889 wgpu::BindGroupLayoutEntry {
890 binding: 2,
891 visibility: wgpu::ShaderStages::FRAGMENT,
892 ty: wgpu::BindingType::Texture {
893 sample_type: wgpu::TextureSampleType::Float { filterable: true },
894 view_dimension: wgpu::TextureViewDimension::D2,
895 multisampled: false,
896 },
897 count: None,
898 },
899 wgpu::BindGroupLayoutEntry {
900 binding: 3,
901 visibility: wgpu::ShaderStages::FRAGMENT,
902 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
903 count: None,
904 },
905 ],
906 });
907 let layout = self
908 .device
909 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
910 label: Some("roxlap-gpu image.layout"),
911 bind_group_layouts: &[Some(&bgl)],
912 immediate_size: 0,
913 });
914 let pipeline = self
915 .device
916 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
917 label: Some("roxlap-gpu image.pipeline"),
918 layout: Some(&layout),
919 vertex: wgpu::VertexState {
920 module: &shader,
921 entry_point: Some("vs_main"),
922 compilation_options: wgpu::PipelineCompilationOptions::default(),
923 buffers: &[wgpu::VertexBufferLayout {
924 array_stride: std::mem::size_of::<ImageVertex>() as u64,
925 step_mode: wgpu::VertexStepMode::Vertex,
926 attributes: &wgpu::vertex_attr_array![
927 0 => Float32x2, 1 => Float32, 2 => Float32, 3 => Float32, 4 => Float32, 5 => Float32x2, 6 => Float32x4, ],
935 }],
936 },
937 fragment: Some(wgpu::FragmentState {
938 module: &shader,
939 entry_point: Some("fs_main"),
940 compilation_options: wgpu::PipelineCompilationOptions::default(),
941 targets: &[Some(wgpu::ColorTargetState {
942 format: self.surface_config.format,
943 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
944 write_mask: wgpu::ColorWrites::ALL,
945 })],
946 }),
947 primitive: wgpu::PrimitiveState {
948 cull_mode: None,
949 ..Default::default()
950 },
951 depth_stencil: None,
952 multisample: wgpu::MultisampleState::default(),
953 multiview_mask: None,
954 cache: None,
955 });
956 let uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
957 label: Some("roxlap-gpu image.uniform"),
958 size: std::mem::size_of::<LineParams>() as u64,
959 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
960 mapped_at_creation: false,
961 });
962 let dummy_depth = self.device.create_buffer(&wgpu::BufferDescriptor {
963 label: Some("roxlap-gpu image.dummy_depth"),
964 size: 4,
965 usage: wgpu::BufferUsages::STORAGE,
966 mapped_at_creation: false,
967 });
968 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
969 label: Some("roxlap-gpu image.sampler"),
970 address_mode_u: wgpu::AddressMode::ClampToEdge,
973 address_mode_v: wgpu::AddressMode::ClampToEdge,
974 address_mode_w: wgpu::AddressMode::ClampToEdge,
975 mag_filter: wgpu::FilterMode::Nearest,
976 min_filter: wgpu::FilterMode::Nearest,
977 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
978 ..Default::default()
979 });
980 self.image_resources = Some(ImageResources {
981 pipeline,
982 bgl,
983 uniform_buf,
984 dummy_depth,
985 sampler,
986 });
987 }
988
989 #[cfg(feature = "hud")]
998 pub fn paint_egui(
999 &mut self,
1000 jobs: &[egui::ClippedPrimitive],
1001 textures: &egui::TexturesDelta,
1002 pixels_per_point: f32,
1003 ) {
1004 let Some((surf_tex, surf_view)) = self.pending_frame.take() else {
1005 return;
1006 };
1007 let format = self.surface_config.format;
1008 let egui_rend = self.egui_renderer.get_or_insert_with(|| {
1009 egui_wgpu::Renderer::new(
1010 &self.device,
1011 format,
1012 egui_wgpu::RendererOptions {
1013 msaa_samples: 1,
1014 depth_stencil_format: None,
1015 dithering: false,
1016 ..Default::default()
1017 },
1018 )
1019 });
1020
1021 let screen = egui_wgpu::ScreenDescriptor {
1022 size_in_pixels: [self.surface_config.width, self.surface_config.height],
1023 pixels_per_point,
1024 };
1025 for (id, delta) in &textures.set {
1026 egui_rend.update_texture(&self.device, &self.queue, *id, delta);
1027 }
1028 let mut encoder = self
1029 .device
1030 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1031 label: Some("roxlap-gpu egui"),
1032 });
1033 let user_bufs =
1034 egui_rend.update_buffers(&self.device, &self.queue, &mut encoder, jobs, &screen);
1035 {
1036 let mut pass = encoder
1038 .begin_render_pass(&wgpu::RenderPassDescriptor {
1039 label: Some("roxlap-gpu egui paint"),
1040 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1041 view: &surf_view,
1042 depth_slice: None,
1043 resolve_target: None,
1044 ops: wgpu::Operations {
1045 load: wgpu::LoadOp::Load,
1046 store: wgpu::StoreOp::Store,
1047 },
1048 })],
1049 depth_stencil_attachment: None,
1050 timestamp_writes: None,
1051 occlusion_query_set: None,
1052 multiview_mask: None,
1053 })
1054 .forget_lifetime();
1056 egui_rend.render(&mut pass, jobs, &screen);
1057 }
1058 for id in &textures.free {
1059 egui_rend.free_texture(id);
1060 }
1061 self.queue.submit(
1062 user_bufs
1063 .into_iter()
1064 .chain(std::iter::once(encoder.finish())),
1065 );
1066 surf_tex.present();
1067 }
1068}
1069
1070#[cfg(test)]
1071mod tests {
1072 #[test]
1077 fn image_vertices_carry_forward_w_and_euclidean_depth() {
1078 let cam = crate::GpuLineCamera {
1079 pos: [0.0, 0.0, 0.0],
1080 right: [1.0, 0.0, 0.0],
1081 down: [0.0, 1.0, 0.0],
1082 forward: [0.0, 0.0, 1.0],
1083 };
1084 let quad = crate::GpuImageQuad {
1086 corners: [
1087 [-1.0, -1.0, 10.0], [1.0, -1.0, 10.0], [-1.0, 1.0, 10.0], [1.0, 1.0, 10.0], ],
1092 image: 0,
1093 tint: [1.0, 1.0, 1.0, 1.0],
1094 depth_test: true,
1095 alpha_cutoff: 0.0,
1096 };
1097 let verts = super::build_image_vertices(&cam, &quad, 800, 600, 60_f32.to_radians(), false);
1098 assert_eq!(verts.len(), 6, "two triangles, no near-clip");
1099 for v in &verts {
1100 assert!((v.w - 10.0).abs() < 1e-4, "w == forward distance");
1101 assert!(v.depth >= 10.0, "euclidean depth >= forward distance");
1102 assert_eq!(v.depth_test, 1.0);
1103 }
1104 }
1105}