Skip to main content

runmat_plot/gpu/
bar.rs

1use crate::core::renderer::Vertex;
2use crate::core::scene::GpuVertexBuffer;
3use crate::gpu::shaders;
4use crate::gpu::{tuning, ScalarType};
5use glam::Vec4;
6use std::sync::Arc;
7use wgpu::util::DeviceExt;
8const VERTICES_PER_BAR: u32 = 6;
9
10/// Inputs required to pack bar vertices directly on the GPU.
11pub struct BarGpuInputs {
12    pub values_buffer: Arc<wgpu::Buffer>,
13    pub row_count: u32,
14    pub scalar: ScalarType,
15}
16
17/// Parameters describing how the GPU vertices should be generated.
18pub struct BarGpuParams {
19    pub color: Vec4,
20    pub bar_width: f32,
21    pub series_index: u32,
22    pub series_count: u32,
23    pub group_index: u32,
24    pub group_count: u32,
25    pub orientation: BarOrientation,
26    pub layout: BarLayoutMode,
27}
28
29#[derive(Clone, Copy, Debug)]
30pub enum BarLayoutMode {
31    Grouped,
32    Stacked,
33}
34
35impl BarLayoutMode {
36    fn as_u32(self) -> u32 {
37        match self {
38            BarLayoutMode::Grouped => 0,
39            BarLayoutMode::Stacked => 1,
40        }
41    }
42}
43
44#[derive(Clone, Copy, Debug)]
45pub enum BarOrientation {
46    Vertical,
47    Horizontal,
48}
49
50impl BarOrientation {
51    fn as_u32(self) -> u32 {
52        match self {
53            BarOrientation::Vertical => 0,
54            BarOrientation::Horizontal => 1,
55        }
56    }
57}
58
59#[repr(C)]
60#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
61struct BarUniforms {
62    color: [f32; 4],
63    bar_width: f32,
64    row_count: u32,
65    series_index: u32,
66    series_count: u32,
67    group_index: u32,
68    group_count: u32,
69    orientation: u32,
70    layout: u32,
71    _pad: [u32; 2],
72}
73
74/// Builds a GPU-resident vertex buffer for bar charts directly from provider-owned value arrays.
75pub fn pack_vertices_from_values(
76    device: &Arc<wgpu::Device>,
77    queue: &Arc<wgpu::Queue>,
78    inputs: &BarGpuInputs,
79    params: &BarGpuParams,
80) -> Result<GpuVertexBuffer, String> {
81    if inputs.row_count == 0 {
82        return Err("bar: input cannot be empty".to_string());
83    }
84
85    let workgroup_size = tuning::effective_workgroup_size();
86    let shader = compile_shader(device, workgroup_size, inputs.scalar);
87
88    let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
89        label: Some("bar-pack-bind-layout"),
90        entries: &[
91            wgpu::BindGroupLayoutEntry {
92                binding: 0,
93                visibility: wgpu::ShaderStages::COMPUTE,
94                ty: wgpu::BindingType::Buffer {
95                    ty: wgpu::BufferBindingType::Storage { read_only: true },
96                    has_dynamic_offset: false,
97                    min_binding_size: None,
98                },
99                count: None,
100            },
101            wgpu::BindGroupLayoutEntry {
102                binding: 1,
103                visibility: wgpu::ShaderStages::COMPUTE,
104                ty: wgpu::BindingType::Buffer {
105                    ty: wgpu::BufferBindingType::Storage { read_only: false },
106                    has_dynamic_offset: false,
107                    min_binding_size: None,
108                },
109                count: None,
110            },
111            wgpu::BindGroupLayoutEntry {
112                binding: 2,
113                visibility: wgpu::ShaderStages::COMPUTE,
114                ty: wgpu::BindingType::Buffer {
115                    ty: wgpu::BufferBindingType::Uniform,
116                    has_dynamic_offset: false,
117                    min_binding_size: None,
118                },
119                count: None,
120            },
121        ],
122    });
123
124    let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
125        label: Some("bar-pack-pipeline-layout"),
126        bind_group_layouts: &[&bind_group_layout],
127        push_constant_ranges: &[],
128    });
129
130    let pipeline =
131        device.create_compute_pipeline(&crate::wgpu_compat::wgpu_compute_pipeline_descriptor! {
132            label: Some("bar-pack-pipeline"),
133            layout: Some(&pipeline_layout),
134            module: &shader,
135            entry_point: "main",
136        });
137
138    let vertex_count = inputs.row_count as u64 * VERTICES_PER_BAR as u64;
139    let output_size = vertex_count * std::mem::size_of::<Vertex>() as u64;
140    let output_buffer = Arc::new(device.create_buffer(&wgpu::BufferDescriptor {
141        label: Some("bar-gpu-vertices"),
142        size: output_size,
143        usage: wgpu::BufferUsages::STORAGE
144            | wgpu::BufferUsages::VERTEX
145            | wgpu::BufferUsages::COPY_DST,
146        mapped_at_creation: false,
147    }));
148
149    let uniforms = BarUniforms {
150        color: params.color.to_array(),
151        bar_width: params.bar_width,
152        row_count: inputs.row_count,
153        series_index: params.series_index,
154        series_count: params.series_count.max(1),
155        group_index: params.group_index,
156        group_count: params.group_count.max(1),
157        orientation: params.orientation.as_u32(),
158        layout: params.layout.as_u32(),
159        _pad: [0, 0],
160    };
161    let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
162        label: Some("bar-pack-uniforms"),
163        contents: bytemuck::bytes_of(&uniforms),
164        usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
165    });
166
167    let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
168        label: Some("bar-pack-bind-group"),
169        layout: &bind_group_layout,
170        entries: &[
171            wgpu::BindGroupEntry {
172                binding: 0,
173                resource: inputs.values_buffer.as_entire_binding(),
174            },
175            wgpu::BindGroupEntry {
176                binding: 1,
177                resource: output_buffer.as_entire_binding(),
178            },
179            wgpu::BindGroupEntry {
180                binding: 2,
181                resource: uniform_buffer.as_entire_binding(),
182            },
183        ],
184    });
185
186    let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
187        label: Some("bar-pack-encoder"),
188    });
189    {
190        let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
191            label: Some("bar-pack-pass"),
192            timestamp_writes: None,
193        });
194        pass.set_pipeline(&pipeline);
195        pass.set_bind_group(0, &bind_group, &[]);
196        let workgroups = inputs.row_count.div_ceil(workgroup_size);
197        pass.dispatch_workgroups(workgroups, 1, 1);
198    }
199    queue.submit(Some(encoder.finish()));
200
201    Ok(GpuVertexBuffer::new(output_buffer, vertex_count as usize))
202}
203
204fn compile_shader(
205    device: &Arc<wgpu::Device>,
206    workgroup_size: u32,
207    scalar: ScalarType,
208) -> wgpu::ShaderModule {
209    let template = match scalar {
210        ScalarType::F32 => shaders::bar::F32,
211        ScalarType::F64 => shaders::bar::F64,
212    };
213    let source = template.replace("{{WORKGROUP_SIZE}}", &workgroup_size.to_string());
214    device.create_shader_module(wgpu::ShaderModuleDescriptor {
215        label: Some("bar-pack-shader"),
216        source: wgpu::ShaderSource::Wgsl(source.into()),
217    })
218}