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 = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
131        label: Some("bar-pack-pipeline"),
132        layout: Some(&pipeline_layout),
133        module: &shader,
134        entry_point: "main",
135    });
136
137    let vertex_count = inputs.row_count as u64 * VERTICES_PER_BAR as u64;
138    let output_size = vertex_count * std::mem::size_of::<Vertex>() as u64;
139    let output_buffer = Arc::new(device.create_buffer(&wgpu::BufferDescriptor {
140        label: Some("bar-gpu-vertices"),
141        size: output_size,
142        usage: wgpu::BufferUsages::STORAGE
143            | wgpu::BufferUsages::VERTEX
144            | wgpu::BufferUsages::COPY_DST,
145        mapped_at_creation: false,
146    }));
147
148    let uniforms = BarUniforms {
149        color: params.color.to_array(),
150        bar_width: params.bar_width,
151        row_count: inputs.row_count,
152        series_index: params.series_index,
153        series_count: params.series_count.max(1),
154        group_index: params.group_index,
155        group_count: params.group_count.max(1),
156        orientation: params.orientation.as_u32(),
157        layout: params.layout.as_u32(),
158        _pad: [0, 0],
159    };
160    let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
161        label: Some("bar-pack-uniforms"),
162        contents: bytemuck::bytes_of(&uniforms),
163        usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
164    });
165
166    let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
167        label: Some("bar-pack-bind-group"),
168        layout: &bind_group_layout,
169        entries: &[
170            wgpu::BindGroupEntry {
171                binding: 0,
172                resource: inputs.values_buffer.as_entire_binding(),
173            },
174            wgpu::BindGroupEntry {
175                binding: 1,
176                resource: output_buffer.as_entire_binding(),
177            },
178            wgpu::BindGroupEntry {
179                binding: 2,
180                resource: uniform_buffer.as_entire_binding(),
181            },
182        ],
183    });
184
185    let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
186        label: Some("bar-pack-encoder"),
187    });
188    {
189        let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
190            label: Some("bar-pack-pass"),
191            timestamp_writes: None,
192        });
193        pass.set_pipeline(&pipeline);
194        pass.set_bind_group(0, &bind_group, &[]);
195        let workgroups = inputs.row_count.div_ceil(workgroup_size);
196        pass.dispatch_workgroups(workgroups, 1, 1);
197    }
198    queue.submit(Some(encoder.finish()));
199
200    Ok(GpuVertexBuffer::new(output_buffer, vertex_count as usize))
201}
202
203fn compile_shader(
204    device: &Arc<wgpu::Device>,
205    workgroup_size: u32,
206    scalar: ScalarType,
207) -> wgpu::ShaderModule {
208    let template = match scalar {
209        ScalarType::F32 => shaders::bar::F32,
210        ScalarType::F64 => shaders::bar::F64,
211    };
212    let source = template.replace("{{WORKGROUP_SIZE}}", &workgroup_size.to_string());
213    device.create_shader_module(wgpu::ShaderModuleDescriptor {
214        label: Some("bar-pack-shader"),
215        source: wgpu::ShaderSource::Wgsl(source.into()),
216    })
217}