Skip to main content

petaplot_render/
pipeline.rs

1use wgpu::util::DeviceExt;
2use wgpu::{
3    BindGroup, BindGroupLayout, Buffer, BufferUsages, ColorTargetState, Device,
4    PipelineLayoutDescriptor, Queue, RenderPipelineDescriptor, TextureFormat, VertexAttribute,
5    VertexBufferLayout, VertexFormat, VertexStepMode,
6};
7
8use petaplot_core::compute::simd::MinMaxPair;
9use crate::camera::ViewportCamera;
10
11/// Pipeline de renderizado GPU para el dibujo acelerado de series temporales por instancias.
12pub struct LineRenderPipeline {
13    pub pipeline: wgpu::RenderPipeline,
14    pub camera_buffer: Buffer,
15    pub camera_bind_group: BindGroup,
16    pub camera_bind_group_layout: BindGroupLayout,
17    pub instance_buffer: Option<Buffer>,
18    pub num_instances: u32,
19}
20
21impl LineRenderPipeline {
22    /// Crea el pipeline de renderizado `wgpu` utilizando el shader WGSL instanciado.
23    pub fn new(device: &Device, surface_format: TextureFormat, camera: &ViewportCamera) -> Self {
24        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
25            label: Some("Instanced Line Shader"),
26            source: wgpu::ShaderSource::Wgsl(include_str!("shaders/line.wgsl").into()),
27        });
28
29        let camera_uniform = camera.build_uniform();
30        let camera_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
31            label: Some("Camera Uniform Buffer"),
32            contents: bytemuck::cast_slice(&[camera_uniform]),
33            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
34        });
35
36        let camera_bind_group_layout =
37            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
38                label: Some("Camera Bind Group Layout"),
39                entries: &[wgpu::BindGroupLayoutEntry {
40                    binding: 0,
41                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
42                    ty: wgpu::BindingType::Buffer {
43                        ty: wgpu::BufferBindingType::Uniform,
44                        has_dynamic_offset: false,
45                        min_binding_size: None,
46                    },
47                    count: None,
48                }],
49            });
50
51        let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
52            label: Some("Camera Bind Group"),
53            layout: &camera_bind_group_layout,
54            entries: &[wgpu::BindGroupEntry {
55                binding: 0,
56                resource: camera_buffer.as_entire_binding(),
57            }],
58        });
59
60        let instance_layout = VertexBufferLayout {
61            array_stride: (std::mem::size_of::<f32>() * 3) as u64,
62            step_mode: VertexStepMode::Instance,
63            attributes: &[
64                VertexAttribute {
65                    offset: 0,
66                    shader_location: 0,
67                    format: VertexFormat::Float32,
68                },
69                VertexAttribute {
70                    offset: 4,
71                    shader_location: 1,
72                    format: VertexFormat::Float32,
73                },
74                VertexAttribute {
75                    offset: 8,
76                    shader_location: 2,
77                    format: VertexFormat::Float32,
78                },
79            ],
80        };
81
82        let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
83            label: Some("Line Pipeline Layout"),
84            bind_group_layouts: &[&camera_bind_group_layout],
85            push_constant_ranges: &[],
86        });
87
88        let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
89            label: Some("Instanced Line Render Pipeline"),
90            layout: Some(&pipeline_layout),
91            vertex: wgpu::VertexState {
92                module: &shader,
93                entry_point: Some("vs_main"),
94                buffers: &[instance_layout],
95                compilation_options: Default::default(),
96            },
97            fragment: Some(wgpu::FragmentState {
98                module: &shader,
99                entry_point: Some("fs_main"),
100                targets: &[Some(ColorTargetState {
101                    format: surface_format,
102                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
103                    write_mask: wgpu::ColorWrites::ALL,
104                })],
105                compilation_options: Default::default(),
106            }),
107            primitive: wgpu::PrimitiveState {
108                topology: wgpu::PrimitiveTopology::LineList,
109                strip_index_format: None,
110                front_face: wgpu::FrontFace::Ccw,
111                cull_mode: None,
112                polygon_mode: wgpu::PolygonMode::Fill,
113                unclipped_depth: false,
114                conservative: false,
115            },
116            depth_stencil: None,
117            multisample: wgpu::MultisampleState::default(),
118            multiview: None,
119            cache: None,
120        });
121
122        Self {
123            pipeline,
124            camera_buffer,
125            camera_bind_group,
126            camera_bind_group_layout,
127            instance_buffer: None,
128            num_instances: 0,
129        }
130    }
131
132    /// Actualiza la matriz de cámara en la GPU sin costo de re-asignación de geometría ($0\text{ ms}$ overhead).
133    pub fn update_camera(&self, queue: &Queue, camera: &ViewportCamera) {
134        let uniform = camera.build_uniform();
135        queue.write_buffer(&self.camera_buffer, 0, bytemuck::cast_slice(&[uniform]));
136    }
137
138    /// Carga los datos de las instancias de pares MinMax en el VBO de la GPU.
139    pub fn upload_instances(&mut self, device: &Device, pairs: &[MinMaxPair], x_step: f32) {
140        if pairs.is_empty() {
141            self.num_instances = 0;
142            return;
143        }
144
145        let mut raw_instance_data: Vec<f32> = Vec::with_capacity(pairs.len() * 3);
146        for (i, pair) in pairs.iter().enumerate() {
147            let x_pos = i as f32 * x_step;
148            raw_instance_data.push(x_pos);
149            raw_instance_data.push(pair.min);
150            raw_instance_data.push(pair.max);
151        }
152
153        let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
154            label: Some("Line Instance Buffer"),
155            contents: bytemuck::cast_slice(&raw_instance_data),
156            usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
157        });
158
159        self.instance_buffer = Some(buffer);
160        self.num_instances = pairs.len() as u32;
161    }
162
163    /// Ejecuta el pase de renderizado en GPU.
164    pub fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
165        if let Some(ref instance_buffer) = self.instance_buffer {
166            if self.num_instances > 0 {
167                render_pass.set_pipeline(&self.pipeline);
168                render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
169                render_pass.set_vertex_buffer(0, instance_buffer.slice(..));
170                render_pass.draw(0..2, 0..self.num_instances);
171            }
172        }
173    }
174}