Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-flow/src/gpu/variant.rs

//! Optional attributes never rebuild geometry. Group 0 and the plain shader stay
//! unchanged; group 1 is allocated only after a scene actually sets a table.
use super::*;
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub(super) struct Uniforms {
    ok: [f32; 4],
    slow: [f32; 4],
    error: [f32; 4],
    options: [f32; 4],
}
impl Uniforms {
    #[allow(unused_mut)] // optional palette and LOD uniforms
    pub(super) fn from_palette(
        _p: &crate::FlowPalette,
        ppp: f32,
        _opts: &crate::PaintOpts,
    ) -> Self {
        let mut out = Self {
            ok: [0.0; 4],
            slow: [0.0; 4],
            error: [0.0; 4],
            options: [0.0, 1.0, ppp, 0.0],
        };
        #[cfg(feature = "status")]
        {
            out.ok = gpu::color32_to_f32(_p.status.ok);
            out.slow = gpu::color32_to_f32(_p.status.slow);
            out.error = gpu::color32_to_f32(_p.status.error);
        }
        #[cfg(feature = "lod")]
        {
            out.options[3] = _opts.min_packet_px.max(0.0);
        }
        out
    }
}
pub(super) struct Resident {
    generation: u64,
    pub bind: wgpu::BindGroup,
    uniform: wgpu::Buffer,
    _nodes: wgpu::Buffer,
    _edges: wgpu::Buffer,
    pub arrows: bool,
    pub packets: u32,
}
pub(super) struct Pipelines {
    layout: wgpu::BindGroupLayout,
    pub node: wgpu::RenderPipeline,
    pub edge: wgpu::RenderPipeline,
    pub pulse: wgpu::RenderPipeline,
    pub arrow: wgpu::RenderPipeline,
}
impl Pipelines {
    pub fn new(device: &wgpu::Device, base: &wgpu::BindGroupLayout) -> Self {
        let entries: Vec<_> = (0..3)
            .map(|binding| wgpu::BindGroupLayoutEntry {
                binding,
                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
                ty: wgpu::BindingType::Buffer {
                    ty: if binding == 0 {
                        wgpu::BufferBindingType::Uniform
                    } else {
                        wgpu::BufferBindingType::Storage { read_only: true }
                    },
                    has_dynamic_offset: false,
                    min_binding_size: None,
                },
                count: None,
            })
            .collect();
        let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: Some("flow attributes"),
            entries: &entries,
        });
        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("flow extension"),
            source: wgpu::ShaderSource::Wgsl(shader_source().into()),
        });
        let pll = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("flow extension layout"),
            bind_group_layouts: &[Some(base), Some(&layout)],
            immediate_size: 0,
        });
        let pipeline = |vs: &str, fs: &str| {
            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
                label: Some(vs),
                layout: Some(&pll),
                vertex: wgpu::VertexState {
                    module: &shader,
                    entry_point: Some(vs),
                    buffers: &[],
                    compilation_options: Default::default(),
                },
                fragment: Some(wgpu::FragmentState {
                    module: &shader,
                    entry_point: Some(fs),
                    targets: &[Some(wgpu::ColorTargetState {
                        format: OFFSCREEN_FORMAT,
                        blend: Some(wgpu::BlendState::ALPHA_BLENDING),
                        write_mask: wgpu::ColorWrites::ALL,
                    })],
                    compilation_options: Default::default(),
                }),
                primitive: wgpu::PrimitiveState::default(),
                depth_stencil: None,
                multisample: gpu::msaa_state(),
                multiview_mask: None,
                cache: None,
            })
        };
        Self {
            node: pipeline("node_vs_x", "node_fs_x"),
            edge: pipeline("edge_vs_x", "edge_fs_x"),
            pulse: pipeline("pulse_vs_x", "pulse_fs"),
            arrow: pipeline("arrow_vs_x", "arrow_fs_x"),
            layout,
        }
    }
    pub fn upload(
        &self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        pane: &mut super::Resident,
        attributes: Option<&crate::host::Attributes>,
        extra: &Uniforms,
    ) -> Result<(), String> {
        let Some(a) = attributes else {
            pane.attributes = None;
            return Ok(());
        };
        if pane
            .attributes
            .as_ref()
            .is_none_or(|old| old.generation != a.generation)
        {
            let start = Instant::now();
            let empty = [[0.0_f32; 4]];
            let nodes = if a.nodes.is_empty() {
                &empty[..]
            } else {
                &a.nodes
            };
            // One f32 speed + packed count/status/explicit-rate flags: 8 bytes/edge.
            let packed: Vec<[u32; 2]> = a
                .edges
                .iter()
                .map(|e| {
                    [
                        e[0].to_bits(),
                        (e[1] as u32) | ((e[2] as u32) << 8) | ((e[3] as u32) << 10),
                    ]
                })
                .collect();
            let empty_edge = [[0_u32; 2]];
            let edges = if packed.is_empty() {
                &empty_edge[..]
            } else {
                &packed
            };
            let nodes_buffer = checked_buffer(
                device,
                queue,
                "flow node attributes",
                bytemuck::cast_slice(nodes),
                wgpu::BufferUsages::STORAGE,
            )?;
            let edges_buffer = checked_buffer(
                device,
                queue,
                "flow edge attributes",
                bytemuck::cast_slice(edges),
                wgpu::BufferUsages::STORAGE,
            )?;
            let uniform = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("flow attribute palette"),
                size: 64,
                usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
                mapped_at_creation: false,
            });
            let bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("flow attribute bind"),
                layout: &self.layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: uniform.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: nodes_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: edges_buffer.as_entire_binding(),
                    },
                ],
            });
            let packets = a
                .edges
                .iter()
                .map(|v| v[1] as u32)
                .max()
                .unwrap_or(1)
                .max(1);
            pane.attributes = Some(Resident {
                generation: a.generation,
                bind,
                uniform,
                _nodes: nodes_buffer,
                _edges: edges_buffer,
                arrows: a.arrows,
                packets,
            });
            lane::note_upload(
                lane_name(),
                start.elapsed().as_micros() as u64,
                (nodes.len() * 16 + edges.len() * 8) as u64,
            );
        }
        let resident = pane.attributes.as_ref().unwrap();
        let mut u = *extra;
        u.options[0] = f32::from(resident.arrows);
        u.options[1] = resident.packets as f32;
        queue.write_buffer(&resident.uniform, 0, bytemuck::bytes_of(&u));
        Ok(())
    }
}

fn shader_source() -> String {
    let mut source = String::from(concat!(
        include_str!("../flow.wgsl"),
        "\n",
        include_str!("../flow_ext.wgsl")
    ));
    #[cfg(feature = "boxes")]
    source.push_str(include_str!("../hooks/boxes_on.wgsl"));
    #[cfg(not(feature = "boxes"))]
    source.push_str(include_str!("../hooks/boxes_off.wgsl"));
    #[cfg(feature = "arrows")]
    source.push_str(include_str!("../hooks/arrows_on.wgsl"));
    #[cfg(not(feature = "arrows"))]
    source.push_str(include_str!("../hooks/arrows_off.wgsl"));
    #[cfg(feature = "status")]
    source.push_str(include_str!("../hooks/status_on.wgsl"));
    #[cfg(not(feature = "status"))]
    source.push_str(include_str!("../hooks/status_off.wgsl"));
    #[cfg(feature = "rate")]
    source.push_str(include_str!("../hooks/rate_on.wgsl"));
    #[cfg(not(feature = "rate"))]
    source.push_str(include_str!("../hooks/rate_off.wgsl"));
    #[cfg(feature = "lod")]
    source.push_str(include_str!("../hooks/lod_on.wgsl"));
    #[cfg(not(feature = "lod"))]
    source.push_str(include_str!("../hooks/lod_off.wgsl"));
    source
}