sable-gpu 0.1.0

GPU abstraction layer for Sable Engine - wgpu-based rendering primitives
Documentation
//! Render pipeline builder and management.

use wgpu::{
    BlendState, ColorTargetState, ColorWrites, Device, Face, FragmentState, FrontFace,
    MultisampleState, PipelineCompilationOptions, PipelineLayoutDescriptor, PolygonMode,
    PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor, TextureFormat, VertexState,
};

use crate::shader::Shader;
use crate::vertex::Vertex;

/// A render pipeline wrapping wgpu's render pipeline.
#[derive(Debug)]
pub struct RenderPipeline {
    inner: wgpu::RenderPipeline,
}

impl RenderPipeline {
    /// Get a reference to the underlying wgpu render pipeline.
    #[must_use]
    pub fn inner(&self) -> &wgpu::RenderPipeline {
        &self.inner
    }
}

/// Builder for creating render pipelines.
pub struct PipelineBuilder<'a> {
    device: &'a Device,
    shader: Option<&'a Shader>,
    vertex_entry: &'a str,
    fragment_entry: &'a str,
    vertex_layouts: Vec<wgpu::VertexBufferLayout<'static>>,
    target_format: Option<TextureFormat>,
    blend_state: Option<BlendState>,
    cull_mode: Option<Face>,
    front_face: FrontFace,
    polygon_mode: PolygonMode,
    topology: PrimitiveTopology,
    depth_format: Option<TextureFormat>,
    label: Option<&'a str>,
}

impl<'a> PipelineBuilder<'a> {
    /// Create a new pipeline builder.
    #[must_use]
    pub fn new(device: &'a Device) -> Self {
        Self {
            device,
            shader: None,
            vertex_entry: "vs_main",
            fragment_entry: "fs_main",
            vertex_layouts: Vec::new(),
            target_format: None,
            blend_state: None,
            cull_mode: None,
            front_face: FrontFace::Ccw,
            polygon_mode: PolygonMode::Fill,
            topology: PrimitiveTopology::TriangleList,
            depth_format: None,
            label: None,
        }
    }

    /// Set the shader module.
    #[must_use]
    pub fn shader(mut self, shader: &'a Shader) -> Self {
        self.shader = Some(shader);
        self
    }

    /// Set the vertex shader entry point.
    #[must_use]
    pub fn vertex_entry(mut self, entry: &'a str) -> Self {
        self.vertex_entry = entry;
        self
    }

    /// Set the fragment shader entry point.
    #[must_use]
    pub fn fragment_entry(mut self, entry: &'a str) -> Self {
        self.fragment_entry = entry;
        self
    }

    /// Add a vertex buffer layout using a vertex type.
    #[must_use]
    pub fn vertex<V: Vertex>(mut self) -> Self {
        self.vertex_layouts.push(V::layout());
        self
    }

    /// Set the render target format.
    #[must_use]
    pub fn target_format(mut self, format: TextureFormat) -> Self {
        self.target_format = Some(format);
        self
    }

    /// Set the blend state.
    #[must_use]
    pub fn blend(mut self, blend: BlendState) -> Self {
        self.blend_state = Some(blend);
        self
    }

    /// Enable alpha blending.
    #[must_use]
    pub fn alpha_blend(mut self) -> Self {
        self.blend_state = Some(BlendState::ALPHA_BLENDING);
        self
    }

    /// Set the cull mode.
    #[must_use]
    pub fn cull_mode(mut self, mode: Option<Face>) -> Self {
        self.cull_mode = mode;
        self
    }

    /// Set the front face winding order.
    #[must_use]
    pub fn front_face(mut self, front_face: FrontFace) -> Self {
        self.front_face = front_face;
        self
    }

    /// Set the polygon mode (fill, line, point).
    #[must_use]
    pub fn polygon_mode(mut self, mode: PolygonMode) -> Self {
        self.polygon_mode = mode;
        self
    }

    /// Set the primitive topology.
    #[must_use]
    pub fn topology(mut self, topology: PrimitiveTopology) -> Self {
        self.topology = topology;
        self
    }

    /// Set the depth texture format.
    #[must_use]
    pub fn depth_format(mut self, format: TextureFormat) -> Self {
        self.depth_format = Some(format);
        self
    }

    /// Set the pipeline label.
    #[must_use]
    pub fn label(mut self, label: &'a str) -> Self {
        self.label = Some(label);
        self
    }

    /// Build the render pipeline.
    ///
    /// # Panics
    ///
    /// Panics if no shader or target format is set.
    #[must_use]
    pub fn build(self) -> RenderPipeline {
        let shader = self.shader.expect("shader is required");
        let target_format = self.target_format.expect("target format is required");

        let pipeline_layout = self
            .device
            .create_pipeline_layout(&PipelineLayoutDescriptor {
                label: self.label.map(|l| format!("{l} Layout")).as_deref(),
                bind_group_layouts: &[],
                push_constant_ranges: &[],
            });

        let depth_stencil = self.depth_format.map(|format| wgpu::DepthStencilState {
            format,
            depth_write_enabled: true,
            depth_compare: wgpu::CompareFunction::Less,
            stencil: wgpu::StencilState::default(),
            bias: wgpu::DepthBiasState::default(),
        });

        let inner = self
            .device
            .create_render_pipeline(&RenderPipelineDescriptor {
                label: self.label,
                layout: Some(&pipeline_layout),
                vertex: VertexState {
                    module: shader.module(),
                    entry_point: Some(self.vertex_entry),
                    compilation_options: PipelineCompilationOptions::default(),
                    buffers: &self.vertex_layouts,
                },
                fragment: Some(FragmentState {
                    module: shader.module(),
                    entry_point: Some(self.fragment_entry),
                    compilation_options: PipelineCompilationOptions::default(),
                    targets: &[Some(ColorTargetState {
                        format: target_format,
                        blend: self.blend_state,
                        write_mask: ColorWrites::ALL,
                    })],
                }),
                primitive: PrimitiveState {
                    topology: self.topology,
                    strip_index_format: None,
                    front_face: self.front_face,
                    cull_mode: self.cull_mode,
                    polygon_mode: self.polygon_mode,
                    unclipped_depth: false,
                    conservative: false,
                },
                depth_stencil,
                multisample: MultisampleState {
                    count: 1,
                    mask: !0,
                    alpha_to_coverage_enabled: false,
                },
                multiview: None,
                cache: None,
            });

        RenderPipeline { inner }
    }
}