Another project
1use uom::si::length::millimeter;
2
3use crate::camera::{Camera2, GridSpacing};
4use crate::gpu::Gpu;
5use crate::snapshot::Style;
6
7#[repr(C, align(16))]
8#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
9struct GridUniform {
10 world_from_clip: [f32; 16],
11 minor: [f32; 4],
12 major: [f32; 4],
13 axis_x: [f32; 4],
14 axis_y: [f32; 4],
15 origin: [f32; 4],
16 viewport: [f32; 2],
17 minor_spacing: f32,
18 major_every: f32,
19 line_width_px: f32,
20 axis_width_px: f32,
21 origin_radius_px: f32,
22 pixels_per_mm: f32,
23}
24
25const UNIFORM_SIZE: u64 = core::mem::size_of::<GridUniform>() as u64;
26
27pub struct GridPipeline {
28 pipeline: wgpu::RenderPipeline,
29 uniform_buffer: wgpu::Buffer,
30 bind_group: wgpu::BindGroup,
31 queue: wgpu::Queue,
32}
33
34impl GridPipeline {
35 #[must_use]
36 pub fn new(gpu: &Gpu, color_format: wgpu::TextureFormat) -> Self {
37 let device = gpu.device();
38 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
39 label: Some("bone-render:grid-shader"),
40 source: wgpu::ShaderSource::Wgsl(include_str!("grid.wgsl").into()),
41 });
42 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
43 label: Some("bone-render:grid-bgl"),
44 entries: &[wgpu::BindGroupLayoutEntry {
45 binding: 0,
46 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
47 ty: wgpu::BindingType::Buffer {
48 ty: wgpu::BufferBindingType::Uniform,
49 has_dynamic_offset: false,
50 min_binding_size: wgpu::BufferSize::new(UNIFORM_SIZE),
51 },
52 count: None,
53 }],
54 });
55 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
56 label: Some("bone-render:grid-layout"),
57 bind_group_layouts: &[Some(&bind_group_layout)],
58 immediate_size: 0,
59 });
60 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
61 label: Some("bone-render:grid-pipeline"),
62 layout: Some(&pipeline_layout),
63 vertex: wgpu::VertexState {
64 module: &shader,
65 entry_point: Some("vs"),
66 compilation_options: wgpu::PipelineCompilationOptions::default(),
67 buffers: &[],
68 },
69 fragment: Some(wgpu::FragmentState {
70 module: &shader,
71 entry_point: Some("fs"),
72 compilation_options: wgpu::PipelineCompilationOptions::default(),
73 targets: &[Some(wgpu::ColorTargetState {
74 format: color_format,
75 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
76 write_mask: wgpu::ColorWrites::ALL,
77 })],
78 }),
79 primitive: wgpu::PrimitiveState {
80 topology: wgpu::PrimitiveTopology::TriangleList,
81 strip_index_format: None,
82 front_face: wgpu::FrontFace::Ccw,
83 cull_mode: None,
84 polygon_mode: wgpu::PolygonMode::Fill,
85 conservative: false,
86 unclipped_depth: false,
87 },
88 depth_stencil: None,
89 multisample: wgpu::MultisampleState::default(),
90 multiview_mask: None,
91 cache: None,
92 });
93 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
94 label: Some("bone-render:grid-uniform"),
95 size: UNIFORM_SIZE,
96 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
97 mapped_at_creation: false,
98 });
99 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
100 label: Some("bone-render:grid-bg"),
101 layout: &bind_group_layout,
102 entries: &[wgpu::BindGroupEntry {
103 binding: 0,
104 resource: uniform_buffer.as_entire_binding(),
105 }],
106 });
107 Self {
108 pipeline,
109 uniform_buffer,
110 bind_group,
111 queue: gpu.queue().clone(),
112 }
113 }
114
115 pub fn draw(
116 &self,
117 encoder: &mut wgpu::CommandEncoder,
118 view: &wgpu::TextureView,
119 camera: Camera2,
120 style: &Style,
121 ) {
122 let spacing = GridSpacing::from_zoom(camera.zoom(), style.grid().minor_spacing_target_px());
123 let uniform = build_uniform(camera, spacing, style);
124 self.queue
125 .write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform));
126 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
127 label: Some("bone-render:grid-pass"),
128 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
129 view,
130 resolve_target: None,
131 depth_slice: None,
132 ops: wgpu::Operations {
133 load: wgpu::LoadOp::Clear(style.background().into()),
134 store: wgpu::StoreOp::Store,
135 },
136 })],
137 depth_stencil_attachment: None,
138 timestamp_writes: None,
139 occlusion_query_set: None,
140 multiview_mask: None,
141 });
142 pass.set_pipeline(&self.pipeline);
143 pass.set_bind_group(0, &self.bind_group, &[]);
144 pass.draw(0..3, 0..1);
145 }
146}
147
148impl core::fmt::Debug for GridPipeline {
149 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
150 f.debug_struct("GridPipeline").finish_non_exhaustive()
151 }
152}
153
154#[allow(
155 clippy::cast_possible_truncation,
156 clippy::cast_precision_loss,
157 reason = "viewport extents and grid spacing fit f32 mantissa"
158)]
159fn build_uniform(camera: Camera2, spacing: GridSpacing, style: &Style) -> GridUniform {
160 let grid = style.grid();
161 let extent = camera.extent();
162 GridUniform {
163 world_from_clip: camera.world_mm_from_clip(),
164 minor: grid.minor().to_rgba_array(),
165 major: grid.major().to_rgba_array(),
166 axis_x: grid.axis_x().to_rgba_array(),
167 axis_y: grid.axis_y().to_rgba_array(),
168 origin: grid.origin().to_rgba_array(),
169 viewport: [
170 extent.width().value() as f32,
171 extent.height().value() as f32,
172 ],
173 minor_spacing: spacing.minor().get::<millimeter>() as f32,
174 major_every: spacing.major_every() as f32,
175 line_width_px: grid.line_width_px(),
176 axis_width_px: grid.axis_width_px(),
177 origin_radius_px: grid.origin_radius_px(),
178 pixels_per_mm: camera.zoom().value() as f32,
179 }
180}