use wgpu::{
RenderPipeline, RenderPipelineDescriptor, VertexState, FragmentState,
PrimitiveState, MultisampleState, PipelineLayoutDescriptor,
ShaderModule, ShaderModuleDescriptor, ShaderSource,
ColorTargetState, BlendState, ColorWrites,
PrimitiveTopology, FrontFace, PolygonMode,
TextureFormat, Device,
};
use log::{info, debug};
use crate::renderer::RenderDevice;
use anvilkit_core::error::{AnvilKitError, Result};
pub struct RenderPipelineBuilder {
vertex_shader: Option<String>,
fragment_shader: Option<String>,
format: Option<TextureFormat>,
topology: PrimitiveTopology,
multisample_count: u32,
label: Option<String>,
vertex_layouts: Vec<wgpu::VertexBufferLayout<'static>>,
depth_format: Option<TextureFormat>,
bind_group_layouts: Vec<wgpu::BindGroupLayout>,
}
impl Default for RenderPipelineBuilder {
fn default() -> Self {
Self::new()
}
}
impl RenderPipelineBuilder {
pub fn new() -> Self {
Self {
vertex_shader: None,
fragment_shader: None,
format: None,
topology: PrimitiveTopology::TriangleList,
multisample_count: 1,
label: None,
vertex_layouts: Vec::new(),
depth_format: None,
bind_group_layouts: Vec::new(),
}
}
pub fn with_vertex_shader<S: Into<String>>(mut self, source: S) -> Self {
self.vertex_shader = Some(source.into());
self
}
pub fn with_fragment_shader<S: Into<String>>(mut self, source: S) -> Self {
self.fragment_shader = Some(source.into());
self
}
pub fn with_format(mut self, format: TextureFormat) -> Self {
self.format = Some(format);
self
}
pub fn with_topology(mut self, topology: PrimitiveTopology) -> Self {
self.topology = topology;
self
}
pub fn with_multisample_count(mut self, count: u32) -> Self {
self.multisample_count = count;
self
}
pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
self.label = Some(label.into());
self
}
pub fn with_vertex_layouts(mut self, layouts: Vec<wgpu::VertexBufferLayout<'static>>) -> Self {
self.vertex_layouts = layouts;
self
}
pub fn with_depth_format(mut self, format: TextureFormat) -> Self {
self.depth_format = Some(format);
self
}
pub fn with_bind_group_layouts(mut self, layouts: Vec<wgpu::BindGroupLayout>) -> Self {
self.bind_group_layouts = layouts;
self
}
pub fn build_depth_only(self, device: &RenderDevice) -> Result<BasicRenderPipeline> {
let vertex_shader = self.vertex_shader
.ok_or_else(|| AnvilKitError::render("缺少顶点着色器".to_string()))?;
let depth_format = self.depth_format
.ok_or_else(|| AnvilKitError::render("深度-only 管线需要深度格式".to_string()))?;
let bind_group_layout_refs: Vec<&wgpu::BindGroupLayout> =
self.bind_group_layouts.iter().collect();
let wgpu_device = device.device();
let vs_module = BasicRenderPipeline::create_shader_module(
wgpu_device, &vertex_shader, Some("Shadow VS"),
)?;
let layout = wgpu_device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some("Shadow Pipeline Layout"),
bind_group_layouts: &bind_group_layout_refs,
push_constant_ranges: &[],
});
let pipeline = wgpu_device.create_render_pipeline(&RenderPipelineDescriptor {
label: self.label.as_deref(),
layout: Some(&layout),
vertex: VertexState {
module: &vs_module,
entry_point: "vs_main",
buffers: &self.vertex_layouts,
},
primitive: PrimitiveState {
topology: self.topology,
strip_index_format: None,
front_face: FrontFace::Ccw,
cull_mode: None, unclipped_depth: false,
polygon_mode: PolygonMode::Fill,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: depth_format,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
fragment: None, multiview: None,
});
let dummy_fs = BasicRenderPipeline::create_shader_module(
wgpu_device,
"// dummy\n@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(0.0); }",
Some("Dummy FS"),
)?;
Ok(BasicRenderPipeline {
pipeline,
vertex_shader: vs_module,
fragment_shader: dummy_fs,
})
}
pub fn build(self, device: &RenderDevice) -> Result<BasicRenderPipeline> {
let vertex_shader = self.vertex_shader
.ok_or_else(|| AnvilKitError::render("缺少顶点着色器".to_string()))?;
let fragment_shader = self.fragment_shader
.ok_or_else(|| AnvilKitError::render("缺少片段着色器".to_string()))?;
let format = self.format
.ok_or_else(|| AnvilKitError::render("缺少渲染目标格式".to_string()))?;
let bind_group_layout_refs: Vec<&wgpu::BindGroupLayout> =
self.bind_group_layouts.iter().collect();
BasicRenderPipeline::new(
device,
&vertex_shader,
&fragment_shader,
format,
self.topology,
self.multisample_count,
self.label.as_deref(),
&self.vertex_layouts,
self.depth_format,
&bind_group_layout_refs,
)
}
}
pub struct BasicRenderPipeline {
pipeline: RenderPipeline,
vertex_shader: ShaderModule,
fragment_shader: ShaderModule,
}
impl BasicRenderPipeline {
pub fn new(
device: &RenderDevice,
vertex_source: &str,
fragment_source: &str,
format: TextureFormat,
topology: PrimitiveTopology,
multisample_count: u32,
label: Option<&str>,
vertex_layouts: &[wgpu::VertexBufferLayout<'_>],
depth_format: Option<TextureFormat>,
bind_group_layouts: &[&wgpu::BindGroupLayout],
) -> Result<Self> {
info!("创建基础渲染管线: {:?}", label);
let wgpu_device = device.device();
let vertex_shader = Self::create_shader_module(
wgpu_device,
vertex_source,
Some("Vertex Shader"),
)?;
let fragment_shader = Self::create_shader_module(
wgpu_device,
fragment_source,
Some("Fragment Shader"),
)?;
let layout = wgpu_device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some("Basic Pipeline Layout"),
bind_group_layouts,
push_constant_ranges: &[],
});
let pipeline = wgpu_device.create_render_pipeline(&RenderPipelineDescriptor {
label,
layout: Some(&layout),
vertex: VertexState {
module: &vertex_shader,
entry_point: "vs_main",
buffers: vertex_layouts,
},
primitive: PrimitiveState {
topology,
strip_index_format: None,
front_face: FrontFace::Ccw,
cull_mode: None, unclipped_depth: false,
polygon_mode: PolygonMode::Fill,
conservative: false,
},
depth_stencil: depth_format.map(|format| wgpu::DepthStencilState {
format,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: MultisampleState {
count: multisample_count,
mask: !0,
alpha_to_coverage_enabled: false,
},
fragment: Some(FragmentState {
module: &fragment_shader,
entry_point: "fs_main",
targets: &[Some(ColorTargetState {
format,
blend: Some(BlendState::REPLACE),
write_mask: ColorWrites::ALL,
})],
}),
multiview: None,
});
info!("基础渲染管线创建成功");
Ok(Self {
pipeline,
vertex_shader,
fragment_shader,
})
}
fn create_shader_module(
device: &Device,
source: &str,
label: Option<&str>,
) -> Result<ShaderModule> {
debug!("创建着色器模块: {:?}", label);
let shader = device.create_shader_module(ShaderModuleDescriptor {
label,
source: ShaderSource::Wgsl(source.into()),
});
Ok(shader)
}
pub fn pipeline(&self) -> &RenderPipeline {
&self.pipeline
}
pub fn into_pipeline(self) -> RenderPipeline {
self.pipeline
}
pub fn vertex_shader(&self) -> &ShaderModule {
&self.vertex_shader
}
pub fn fragment_shader(&self) -> &ShaderModule {
&self.fragment_shader
}
}
#[cfg(test)]
mod tests {
use super::*;
use wgpu::{TextureFormat, PrimitiveTopology};
#[test]
fn test_pipeline_builder_creation() {
let builder = RenderPipelineBuilder::new()
.with_vertex_shader("vertex.wgsl")
.with_fragment_shader("fragment.wgsl")
.with_format(TextureFormat::Bgra8UnormSrgb)
.with_topology(PrimitiveTopology::LineList)
.with_multisample_count(4)
.with_label("Test Pipeline");
assert_eq!(builder.vertex_shader.as_ref().unwrap(), "vertex.wgsl");
assert_eq!(builder.fragment_shader.as_ref().unwrap(), "fragment.wgsl");
assert_eq!(builder.format.unwrap(), TextureFormat::Bgra8UnormSrgb);
assert_eq!(builder.topology, PrimitiveTopology::LineList);
assert_eq!(builder.multisample_count, 4);
assert_eq!(builder.label.as_ref().unwrap(), "Test Pipeline");
}
#[test]
fn test_pipeline_builder_defaults() {
let builder = RenderPipelineBuilder::new();
assert!(builder.vertex_shader.is_none());
assert!(builder.fragment_shader.is_none());
assert!(builder.format.is_none());
assert_eq!(builder.topology, PrimitiveTopology::TriangleList);
assert_eq!(builder.multisample_count, 1);
assert!(builder.label.is_none());
}
#[test]
fn test_pipeline_builder_with_label() {
let builder = RenderPipelineBuilder::new()
.with_label("Test Pipeline");
assert_eq!(builder.label.as_deref(), Some("Test Pipeline"));
}
#[test]
fn test_pipeline_builder_with_format() {
let builder = RenderPipelineBuilder::new()
.with_format(TextureFormat::Bgra8UnormSrgb);
assert_eq!(builder.format, Some(TextureFormat::Bgra8UnormSrgb));
}
#[test]
fn test_pipeline_builder_with_topology() {
let builder = RenderPipelineBuilder::new()
.with_topology(PrimitiveTopology::LineList);
assert_eq!(builder.topology, PrimitiveTopology::LineList);
}
#[test]
fn test_pipeline_builder_with_multisample() {
let builder = RenderPipelineBuilder::new()
.with_multisample_count(4);
assert_eq!(builder.multisample_count, 4);
}
#[test]
fn test_pipeline_builder_chaining() {
let builder = RenderPipelineBuilder::new()
.with_label("Chained")
.with_format(TextureFormat::Rgba8Unorm)
.with_topology(PrimitiveTopology::TriangleStrip)
.with_multisample_count(2);
assert_eq!(builder.label.as_deref(), Some("Chained"));
assert_eq!(builder.format, Some(TextureFormat::Rgba8Unorm));
assert_eq!(builder.topology, PrimitiveTopology::TriangleStrip);
assert_eq!(builder.multisample_count, 2);
}
}