1use crate::core::renderer::Vertex;
2use crate::core::scene::GpuVertexBuffer;
3use crate::gpu::shaders;
4use crate::gpu::{tuning, ScalarType};
5use crate::plots::line::LineStyle;
6use glam::Vec4;
7use log::trace;
8use std::sync::Arc;
9use wgpu::util::DeviceExt;
10
11#[derive(Debug, Clone)]
13pub struct LineGpuInputs {
14 pub x_buffer: Arc<wgpu::Buffer>,
15 pub y_buffer: Arc<wgpu::Buffer>,
16 pub len: u32,
17 pub scalar: ScalarType,
18}
19
20pub struct LineGpuParams {
22 pub color: Vec4,
23 pub half_width_px: f32,
25 pub viewport_width_px: f32,
26 pub viewport_height_px: f32,
27 pub x_min: f32,
28 pub x_span: f32,
29 pub y_min: f32,
30 pub y_span: f32,
31 pub line_style: LineStyle,
32 pub marker_size: f32,
33}
34
35#[repr(C)]
36#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
37struct LineSegmentUniforms {
38 color: [f32; 4],
39 count: u32,
40 line_style: u32,
41 half_width_px: f32,
42 _pad0: f32,
43 viewport_width_px: f32,
44 viewport_height_px: f32,
45 x_min: f32,
46 x_span: f32,
47 y_min: f32,
48 y_span: f32,
49 _pad1: [f32; 2],
50}
51
52#[repr(C)]
53#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
54struct MarkerUniforms {
55 color: [f32; 4],
56 count: u32,
57 size: f32,
58 _pad: [u32; 2],
59}
60
61pub fn pack_vertices_from_xy(
62 device: &Arc<wgpu::Device>,
63 queue: &Arc<wgpu::Queue>,
64 inputs: &LineGpuInputs,
65 params: &LineGpuParams,
66) -> Result<GpuVertexBuffer, String> {
67 if inputs.len < 2 {
68 return Err("plot: line inputs must contain at least two points".to_string());
69 }
70
71 let segments = inputs.len - 1;
72 if segments == 0 {
73 return Err("plot: unable to construct segments from degenerate input".to_string());
74 }
75 let vertices_per_segment = 6u64;
76 let max_vertices = segments as u64 * vertices_per_segment;
77 trace!(
78 target: "runmat_plot",
79 "line-pack-kernel: dispatch segments={} max_vertices={} half_width_px={} viewport=({},{})",
80 segments,
81 max_vertices,
82 params.half_width_px,
83 params.viewport_width_px,
84 params.viewport_height_px
85 );
86
87 let workgroup_size = tuning::effective_workgroup_size();
88 let shader = compile_shader(device, workgroup_size, inputs.scalar);
89
90 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
91 label: Some("line-pack-bind-layout"),
92 entries: &[
93 wgpu::BindGroupLayoutEntry {
94 binding: 0,
95 visibility: wgpu::ShaderStages::COMPUTE,
96 ty: wgpu::BindingType::Buffer {
97 ty: wgpu::BufferBindingType::Storage { read_only: true },
98 has_dynamic_offset: false,
99 min_binding_size: None,
100 },
101 count: None,
102 },
103 wgpu::BindGroupLayoutEntry {
104 binding: 1,
105 visibility: wgpu::ShaderStages::COMPUTE,
106 ty: wgpu::BindingType::Buffer {
107 ty: wgpu::BufferBindingType::Storage { read_only: true },
108 has_dynamic_offset: false,
109 min_binding_size: None,
110 },
111 count: None,
112 },
113 wgpu::BindGroupLayoutEntry {
114 binding: 2,
115 visibility: wgpu::ShaderStages::COMPUTE,
116 ty: wgpu::BindingType::Buffer {
117 ty: wgpu::BufferBindingType::Storage { read_only: false },
118 has_dynamic_offset: false,
119 min_binding_size: None,
120 },
121 count: None,
122 },
123 wgpu::BindGroupLayoutEntry {
124 binding: 3,
125 visibility: wgpu::ShaderStages::COMPUTE,
126 ty: wgpu::BindingType::Buffer {
127 ty: wgpu::BufferBindingType::Uniform,
128 has_dynamic_offset: false,
129 min_binding_size: None,
130 },
131 count: None,
132 },
133 ],
134 });
135
136 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
137 label: Some("line-pack-pipeline-layout"),
138 bind_group_layouts: &[&bind_group_layout],
139 push_constant_ranges: &[],
140 });
141
142 let pipeline =
143 device.create_compute_pipeline(&crate::wgpu_compat::wgpu_compute_pipeline_descriptor! {
144 label: Some("line-pack-pipeline"),
145 layout: Some(&pipeline_layout),
146 module: &shader,
147 entry_point: "main",
148 });
149
150 let output_size = max_vertices * std::mem::size_of::<Vertex>() as u64;
151 let output_buffer = Arc::new(device.create_buffer(&wgpu::BufferDescriptor {
152 label: Some("line-gpu-vertices"),
153 size: output_size,
154 usage: wgpu::BufferUsages::STORAGE
155 | wgpu::BufferUsages::VERTEX
156 | wgpu::BufferUsages::COPY_DST
157 | wgpu::BufferUsages::COPY_SRC,
158 mapped_at_creation: false,
159 }));
160
161 let uniforms = LineSegmentUniforms {
162 color: params.color.to_array(),
163 count: inputs.len,
164 line_style: line_style_code(params.line_style),
165 half_width_px: params.half_width_px.max(0.0),
166 _pad0: 0.0,
167 viewport_width_px: params.viewport_width_px.max(1.0),
168 viewport_height_px: params.viewport_height_px.max(1.0),
169 x_min: params.x_min,
170 x_span: params.x_span.max(1e-12),
171 y_min: params.y_min,
172 y_span: params.y_span.max(1e-12),
173 _pad1: [0.0; 2],
174 };
175 let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
176 label: Some("line-pack-uniforms"),
177 contents: bytemuck::bytes_of(&uniforms),
178 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
179 });
180
181 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
182 label: Some("line-pack-bind-group"),
183 layout: &bind_group_layout,
184 entries: &[
185 wgpu::BindGroupEntry {
186 binding: 0,
187 resource: inputs.x_buffer.as_entire_binding(),
188 },
189 wgpu::BindGroupEntry {
190 binding: 1,
191 resource: inputs.y_buffer.as_entire_binding(),
192 },
193 wgpu::BindGroupEntry {
194 binding: 2,
195 resource: output_buffer.as_entire_binding(),
196 },
197 wgpu::BindGroupEntry {
198 binding: 3,
199 resource: uniform_buffer.as_entire_binding(),
200 },
201 ],
202 });
203
204 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
205 label: Some("line-pack-encoder"),
206 });
207 {
208 let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
209 label: Some("line-pack-pass"),
210 timestamp_writes: None,
211 });
212 pass.set_pipeline(&pipeline);
213 pass.set_bind_group(0, &bind_group, &[]);
214 let workgroups = segments.div_ceil(workgroup_size);
215 pass.dispatch_workgroups(workgroups, 1, 1);
216 }
217 queue.submit(Some(encoder.finish()));
218 Ok(GpuVertexBuffer::new(output_buffer, max_vertices as usize))
219}
220
221fn compile_shader(
222 device: &Arc<wgpu::Device>,
223 workgroup_size: u32,
224 scalar: ScalarType,
225) -> wgpu::ShaderModule {
226 let template = match scalar {
227 ScalarType::F32 => shaders::line::F32,
228 ScalarType::F64 => shaders::line::F64,
229 };
230 let source = template.replace("{{WORKGROUP_SIZE}}", &workgroup_size.to_string());
231 device.create_shader_module(wgpu::ShaderModuleDescriptor {
232 label: Some("line-pack-shader"),
233 source: wgpu::ShaderSource::Wgsl(source.into()),
234 })
235}
236
237pub fn pack_marker_vertices_from_xy(
238 device: &Arc<wgpu::Device>,
239 queue: &Arc<wgpu::Queue>,
240 inputs: &LineGpuInputs,
241 params: &LineGpuParams,
242) -> Result<GpuVertexBuffer, String> {
243 if inputs.len < 1 {
244 return Err("plot: marker inputs must contain at least one point".to_string());
245 }
246
247 let workgroup_size = tuning::effective_workgroup_size();
248 let shader = compile_marker_shader(device, workgroup_size, inputs.scalar);
249
250 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
251 label: Some("line-marker-pack-bind-layout"),
252 entries: &[
253 wgpu::BindGroupLayoutEntry {
254 binding: 0,
255 visibility: wgpu::ShaderStages::COMPUTE,
256 ty: wgpu::BindingType::Buffer {
257 ty: wgpu::BufferBindingType::Storage { read_only: true },
258 has_dynamic_offset: false,
259 min_binding_size: None,
260 },
261 count: None,
262 },
263 wgpu::BindGroupLayoutEntry {
264 binding: 1,
265 visibility: wgpu::ShaderStages::COMPUTE,
266 ty: wgpu::BindingType::Buffer {
267 ty: wgpu::BufferBindingType::Storage { read_only: true },
268 has_dynamic_offset: false,
269 min_binding_size: None,
270 },
271 count: None,
272 },
273 wgpu::BindGroupLayoutEntry {
274 binding: 2,
275 visibility: wgpu::ShaderStages::COMPUTE,
276 ty: wgpu::BindingType::Buffer {
277 ty: wgpu::BufferBindingType::Storage { read_only: false },
278 has_dynamic_offset: false,
279 min_binding_size: None,
280 },
281 count: None,
282 },
283 wgpu::BindGroupLayoutEntry {
284 binding: 3,
285 visibility: wgpu::ShaderStages::COMPUTE,
286 ty: wgpu::BindingType::Buffer {
287 ty: wgpu::BufferBindingType::Uniform,
288 has_dynamic_offset: false,
289 min_binding_size: None,
290 },
291 count: None,
292 },
293 ],
294 });
295
296 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
297 label: Some("line-marker-pack-pipeline-layout"),
298 bind_group_layouts: &[&bind_group_layout],
299 push_constant_ranges: &[],
300 });
301
302 let pipeline =
303 device.create_compute_pipeline(&crate::wgpu_compat::wgpu_compute_pipeline_descriptor! {
304 label: Some("line-marker-pack-pipeline"),
305 layout: Some(&pipeline_layout),
306 module: &shader,
307 entry_point: "main",
308 });
309
310 let expanded_vertices = inputs.len as u64 * 6;
312 let output_size = expanded_vertices * std::mem::size_of::<Vertex>() as u64;
313 let output_buffer = Arc::new(device.create_buffer(&wgpu::BufferDescriptor {
314 label: Some("line-marker-gpu-vertices"),
315 size: output_size,
316 usage: wgpu::BufferUsages::STORAGE
317 | wgpu::BufferUsages::VERTEX
318 | wgpu::BufferUsages::COPY_DST
319 | wgpu::BufferUsages::COPY_SRC,
320 mapped_at_creation: false,
321 }));
322
323 let uniforms = MarkerUniforms {
324 color: params.color.to_array(),
325 count: inputs.len,
326 size: params.marker_size,
327 _pad: [0; 2],
328 };
329 let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
330 label: Some("line-marker-pack-uniforms"),
331 contents: bytemuck::bytes_of(&uniforms),
332 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
333 });
334
335 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
336 label: Some("line-marker-pack-bind-group"),
337 layout: &bind_group_layout,
338 entries: &[
339 wgpu::BindGroupEntry {
340 binding: 0,
341 resource: inputs.x_buffer.as_entire_binding(),
342 },
343 wgpu::BindGroupEntry {
344 binding: 1,
345 resource: inputs.y_buffer.as_entire_binding(),
346 },
347 wgpu::BindGroupEntry {
348 binding: 2,
349 resource: output_buffer.as_entire_binding(),
350 },
351 wgpu::BindGroupEntry {
352 binding: 3,
353 resource: uniform_buffer.as_entire_binding(),
354 },
355 ],
356 });
357
358 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
359 label: Some("line-marker-pack-encoder"),
360 });
361 {
362 let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
363 label: Some("line-marker-pack-pass"),
364 timestamp_writes: None,
365 });
366 pass.set_pipeline(&pipeline);
367 pass.set_bind_group(0, &bind_group, &[]);
368 let workgroups = inputs.len.div_ceil(workgroup_size);
369 pass.dispatch_workgroups(workgroups, 1, 1);
370 }
371 queue.submit(Some(encoder.finish()));
372
373 Ok(GpuVertexBuffer::new(
374 output_buffer,
375 (inputs.len as usize) * 6,
376 ))
377}
378
379fn compile_marker_shader(
380 device: &Arc<wgpu::Device>,
381 workgroup_size: u32,
382 scalar: ScalarType,
383) -> wgpu::ShaderModule {
384 let template = match scalar {
385 ScalarType::F32 => shaders::line::MARKER_F32,
386 ScalarType::F64 => shaders::line::MARKER_F64,
387 };
388 let source = template.replace("{{WORKGROUP_SIZE}}", &workgroup_size.to_string());
389 device.create_shader_module(wgpu::ShaderModuleDescriptor {
390 label: Some("line-marker-pack-shader"),
391 source: wgpu::ShaderSource::Wgsl(source.into()),
392 })
393}
394
395fn line_style_code(style: LineStyle) -> u32 {
396 match style {
397 LineStyle::Solid => 0,
398 LineStyle::Dashed => 1,
399 LineStyle::Dotted => 2,
400 LineStyle::DashDot => 3,
401 }
402}