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;
#[derive(Debug)]
pub struct RenderPipeline {
inner: wgpu::RenderPipeline,
}
impl RenderPipeline {
#[must_use]
pub fn inner(&self) -> &wgpu::RenderPipeline {
&self.inner
}
}
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> {
#[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,
}
}
#[must_use]
pub fn shader(mut self, shader: &'a Shader) -> Self {
self.shader = Some(shader);
self
}
#[must_use]
pub fn vertex_entry(mut self, entry: &'a str) -> Self {
self.vertex_entry = entry;
self
}
#[must_use]
pub fn fragment_entry(mut self, entry: &'a str) -> Self {
self.fragment_entry = entry;
self
}
#[must_use]
pub fn vertex<V: Vertex>(mut self) -> Self {
self.vertex_layouts.push(V::layout());
self
}
#[must_use]
pub fn target_format(mut self, format: TextureFormat) -> Self {
self.target_format = Some(format);
self
}
#[must_use]
pub fn blend(mut self, blend: BlendState) -> Self {
self.blend_state = Some(blend);
self
}
#[must_use]
pub fn alpha_blend(mut self) -> Self {
self.blend_state = Some(BlendState::ALPHA_BLENDING);
self
}
#[must_use]
pub fn cull_mode(mut self, mode: Option<Face>) -> Self {
self.cull_mode = mode;
self
}
#[must_use]
pub fn front_face(mut self, front_face: FrontFace) -> Self {
self.front_face = front_face;
self
}
#[must_use]
pub fn polygon_mode(mut self, mode: PolygonMode) -> Self {
self.polygon_mode = mode;
self
}
#[must_use]
pub fn topology(mut self, topology: PrimitiveTopology) -> Self {
self.topology = topology;
self
}
#[must_use]
pub fn depth_format(mut self, format: TextureFormat) -> Self {
self.depth_format = Some(format);
self
}
#[must_use]
pub fn label(mut self, label: &'a str) -> Self {
self.label = Some(label);
self
}
#[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 }
}
}