graph-explorer-render 0.1.0

WebGL2/wgpu renderer for graph-explorer — nodes, edges, halos and labels.
Documentation
use bytemuck::{Pod, Zeroable};
use crate::EdgeLineData;

// Tightly packed (40 bytes: a 8 + b 8 + color 16 + width 4 + opacity 4) so
// `vertex_attr_array!`'s computed offsets (0, 8, 16, 32, 36) line up with the
// true field offsets — see the note in nodes.rs for why no padding is allowed.
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct EdgeInstance {
    a: [f32; 2],
    b: [f32; 2],
    color: [f32; 4],
    width: f32,
    opacity: f32,
}

pub struct EdgePipeline {
    pipeline: wgpu::RenderPipeline,
    instances: wgpu::Buffer,
    count: u32,
    capacity: u64,
}

impl EdgePipeline {
    pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat, camera_bgl: &wgpu::BindGroupLayout) -> Self {
        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("edge-shader"),
            source: wgpu::ShaderSource::Wgsl(include_str!("shaders/edge.wgsl").into()),
        });
        let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("edge-layout"),
            bind_group_layouts: &[camera_bgl],
            push_constant_ranges: &[],
        });
        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("edge-pipeline"),
            layout: Some(&layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: "vs_main",
                buffers: &[wgpu::VertexBufferLayout {
                    array_stride: std::mem::size_of::<EdgeInstance>() as u64,
                    step_mode: wgpu::VertexStepMode::Instance,
                    attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Float32x4, 3 => Float32, 4 => Float32],
                }],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: "fs_main",
                targets: &[Some(wgpu::ColorTargetState {
                    format, blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, ..Default::default() },
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });
        let capacity = 128;
        let instances = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("edge-instances"),
            size: capacity * std::mem::size_of::<EdgeInstance>() as u64,
            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });
        Self { pipeline, instances, count: 0, capacity }
    }

    pub fn upload(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, edges: &[EdgeLineData]) {
        let data: Vec<EdgeInstance> = edges.iter().map(|e| EdgeInstance {
            a: e.a, b: e.b, color: e.color, width: e.width, opacity: e.opacity,
        }).collect();
        let needed = data.len() as u64;
        if needed > self.capacity {
            self.capacity = needed.next_power_of_two();
            self.instances = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("edge-instances"),
                size: self.capacity * std::mem::size_of::<EdgeInstance>() as u64,
                usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
                mapped_at_creation: false,
            });
        }
        queue.write_buffer(&self.instances, 0, bytemuck::cast_slice(&data));
        self.count = data.len() as u32;
    }

    pub fn draw<'a>(&'a self, rpass: &mut wgpu::RenderPass<'a>, camera_bg: &'a wgpu::BindGroup) {
        if self.count == 0 { return; }
        rpass.set_pipeline(&self.pipeline);
        rpass.set_bind_group(0, camera_bg, &[]);
        rpass.set_vertex_buffer(0, self.instances.slice(..));
        rpass.draw(0..6, 0..self.count);
    }
}