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