1use wgpu::{
6 RenderPipeline, RenderPipelineDescriptor, VertexState, FragmentState,
7 PrimitiveState, MultisampleState, PipelineLayoutDescriptor,
8 ShaderModule, ShaderModuleDescriptor, ShaderSource,
9 ColorTargetState, BlendState, ColorWrites,
10 PrimitiveTopology, FrontFace, PolygonMode,
11 TextureFormat, Device,
12};
13use log::{info, debug};
14
15use crate::renderer::RenderDevice;
16use anvilkit_core::error::{AnvilKitError, Result};
17
18pub struct RenderPipelineBuilder {
45 vertex_shader: Option<String>,
47 fragment_shader: Option<String>,
49 format: Option<TextureFormat>,
51 topology: PrimitiveTopology,
53 multisample_count: u32,
55 label: Option<String>,
57 vertex_layouts: Vec<wgpu::VertexBufferLayout<'static>>,
59 depth_format: Option<TextureFormat>,
61 bind_group_layouts: Vec<wgpu::BindGroupLayout>,
63}
64
65impl Default for RenderPipelineBuilder {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71impl RenderPipelineBuilder {
72 pub fn new() -> Self {
82 Self {
83 vertex_shader: None,
84 fragment_shader: None,
85 format: None,
86 topology: PrimitiveTopology::TriangleList,
87 multisample_count: 1,
88 label: None,
89 vertex_layouts: Vec::new(),
90 depth_format: None,
91 bind_group_layouts: Vec::new(),
92 }
93 }
94
95 pub fn with_vertex_shader<S: Into<String>>(mut self, source: S) -> Self {
110 self.vertex_shader = Some(source.into());
111 self
112 }
113
114 pub fn with_fragment_shader<S: Into<String>>(mut self, source: S) -> Self {
129 self.fragment_shader = Some(source.into());
130 self
131 }
132
133 pub fn with_format(mut self, format: TextureFormat) -> Self {
149 self.format = Some(format);
150 self
151 }
152
153 pub fn with_topology(mut self, topology: PrimitiveTopology) -> Self {
169 self.topology = topology;
170 self
171 }
172
173 pub fn with_multisample_count(mut self, count: u32) -> Self {
188 self.multisample_count = count;
189 self
190 }
191
192 pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
207 self.label = Some(label.into());
208 self
209 }
210
211 pub fn with_vertex_layouts(mut self, layouts: Vec<wgpu::VertexBufferLayout<'static>>) -> Self {
226 self.vertex_layouts = layouts;
227 self
228 }
229
230 pub fn with_depth_format(mut self, format: TextureFormat) -> Self {
236 self.depth_format = Some(format);
237 self
238 }
239
240 pub fn with_bind_group_layouts(mut self, layouts: Vec<wgpu::BindGroupLayout>) -> Self {
246 self.bind_group_layouts = layouts;
247 self
248 }
249
250 pub fn build_depth_only(self, device: &RenderDevice) -> Result<BasicRenderPipeline> {
277 let vertex_shader = self.vertex_shader
278 .ok_or_else(|| AnvilKitError::render("缺少顶点着色器".to_string()))?;
279
280 let depth_format = self.depth_format
281 .ok_or_else(|| AnvilKitError::render("深度-only 管线需要深度格式".to_string()))?;
282
283 let bind_group_layout_refs: Vec<&wgpu::BindGroupLayout> =
284 self.bind_group_layouts.iter().collect();
285
286 let wgpu_device = device.device();
287
288 let vs_module = BasicRenderPipeline::create_shader_module(
289 wgpu_device, &vertex_shader, Some("Shadow VS"),
290 )?;
291
292 let layout = wgpu_device.create_pipeline_layout(&PipelineLayoutDescriptor {
293 label: Some("Shadow Pipeline Layout"),
294 bind_group_layouts: &bind_group_layout_refs,
295 push_constant_ranges: &[],
296 });
297
298 let pipeline = wgpu_device.create_render_pipeline(&RenderPipelineDescriptor {
299 label: self.label.as_deref(),
300 layout: Some(&layout),
301 vertex: VertexState {
302 module: &vs_module,
303 entry_point: "vs_main",
304 buffers: &self.vertex_layouts,
305 },
306 primitive: PrimitiveState {
307 topology: self.topology,
308 strip_index_format: None,
309 front_face: FrontFace::Ccw,
310 cull_mode: None, unclipped_depth: false,
312 polygon_mode: PolygonMode::Fill,
313 conservative: false,
314 },
315 depth_stencil: Some(wgpu::DepthStencilState {
316 format: depth_format,
317 depth_write_enabled: true,
318 depth_compare: wgpu::CompareFunction::Less,
319 stencil: wgpu::StencilState::default(),
320 bias: wgpu::DepthBiasState::default(),
321 }),
322 multisample: MultisampleState {
323 count: 1,
324 mask: !0,
325 alpha_to_coverage_enabled: false,
326 },
327 fragment: None, multiview: None,
329 });
330
331 let dummy_fs = BasicRenderPipeline::create_shader_module(
333 wgpu_device,
334 "// dummy\n@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(0.0); }",
335 Some("Dummy FS"),
336 )?;
337
338 Ok(BasicRenderPipeline {
339 pipeline,
340 vertex_shader: vs_module,
341 fragment_shader: dummy_fs,
342 })
343 }
344
345 pub fn build(self, device: &RenderDevice) -> Result<BasicRenderPipeline> {
347 let vertex_shader = self.vertex_shader
348 .ok_or_else(|| AnvilKitError::render("缺少顶点着色器".to_string()))?;
349
350 let fragment_shader = self.fragment_shader
351 .ok_or_else(|| AnvilKitError::render("缺少片段着色器".to_string()))?;
352
353 let format = self.format
354 .ok_or_else(|| AnvilKitError::render("缺少渲染目标格式".to_string()))?;
355
356 let bind_group_layout_refs: Vec<&wgpu::BindGroupLayout> =
357 self.bind_group_layouts.iter().collect();
358
359 BasicRenderPipeline::new(
360 device,
361 &vertex_shader,
362 &fragment_shader,
363 format,
364 self.topology,
365 self.multisample_count,
366 self.label.as_deref(),
367 &self.vertex_layouts,
368 self.depth_format,
369 &bind_group_layout_refs,
370 )
371 }
372}
373
374pub struct BasicRenderPipeline {
401 pipeline: RenderPipeline,
403 vertex_shader: ShaderModule,
405 fragment_shader: ShaderModule,
407}
408
409impl BasicRenderPipeline {
410 pub fn new(
426 device: &RenderDevice,
427 vertex_source: &str,
428 fragment_source: &str,
429 format: TextureFormat,
430 topology: PrimitiveTopology,
431 multisample_count: u32,
432 label: Option<&str>,
433 vertex_layouts: &[wgpu::VertexBufferLayout<'_>],
434 depth_format: Option<TextureFormat>,
435 bind_group_layouts: &[&wgpu::BindGroupLayout],
436 ) -> Result<Self> {
437 info!("创建基础渲染管线: {:?}", label);
438
439 let wgpu_device = device.device();
440
441 let vertex_shader = Self::create_shader_module(
443 wgpu_device,
444 vertex_source,
445 Some("Vertex Shader"),
446 )?;
447
448 let fragment_shader = Self::create_shader_module(
449 wgpu_device,
450 fragment_source,
451 Some("Fragment Shader"),
452 )?;
453
454 let layout = wgpu_device.create_pipeline_layout(&PipelineLayoutDescriptor {
456 label: Some("Basic Pipeline Layout"),
457 bind_group_layouts,
458 push_constant_ranges: &[],
459 });
460
461 let pipeline = wgpu_device.create_render_pipeline(&RenderPipelineDescriptor {
463 label,
464 layout: Some(&layout),
465 vertex: VertexState {
466 module: &vertex_shader,
467 entry_point: "vs_main",
468 buffers: vertex_layouts,
469 },
470 primitive: PrimitiveState {
471 topology,
472 strip_index_format: None,
473 front_face: FrontFace::Ccw,
474 cull_mode: None, unclipped_depth: false,
476 polygon_mode: PolygonMode::Fill,
477 conservative: false,
478 },
479 depth_stencil: depth_format.map(|format| wgpu::DepthStencilState {
480 format,
481 depth_write_enabled: true,
482 depth_compare: wgpu::CompareFunction::Less,
483 stencil: wgpu::StencilState::default(),
484 bias: wgpu::DepthBiasState::default(),
485 }),
486 multisample: MultisampleState {
487 count: multisample_count,
488 mask: !0,
489 alpha_to_coverage_enabled: false,
490 },
491 fragment: Some(FragmentState {
492 module: &fragment_shader,
493 entry_point: "fs_main",
494 targets: &[Some(ColorTargetState {
495 format,
496 blend: Some(BlendState::REPLACE),
497 write_mask: ColorWrites::ALL,
498 })],
499 }),
500 multiview: None,
501 });
502
503 info!("基础渲染管线创建成功");
504
505 Ok(Self {
506 pipeline,
507 vertex_shader,
508 fragment_shader,
509 })
510 }
511
512 fn create_shader_module(
524 device: &Device,
525 source: &str,
526 label: Option<&str>,
527 ) -> Result<ShaderModule> {
528 debug!("创建着色器模块: {:?}", label);
529
530 let shader = device.create_shader_module(ShaderModuleDescriptor {
531 label,
532 source: ShaderSource::Wgsl(source.into()),
533 });
534
535 Ok(shader)
536 }
537
538 pub fn pipeline(&self) -> &RenderPipeline {
554 &self.pipeline
555 }
556
557 pub fn into_pipeline(self) -> RenderPipeline {
559 self.pipeline
560 }
561
562 pub fn vertex_shader(&self) -> &ShaderModule {
577 &self.vertex_shader
578 }
579
580 pub fn fragment_shader(&self) -> &ShaderModule {
595 &self.fragment_shader
596 }
597}
598
599#[cfg(test)]
600mod tests {
601 use super::*;
602 use wgpu::{TextureFormat, PrimitiveTopology};
603
604 #[test]
605 fn test_pipeline_builder_creation() {
606 let builder = RenderPipelineBuilder::new()
607 .with_vertex_shader("vertex.wgsl")
608 .with_fragment_shader("fragment.wgsl")
609 .with_format(TextureFormat::Bgra8UnormSrgb)
610 .with_topology(PrimitiveTopology::LineList)
611 .with_multisample_count(4)
612 .with_label("Test Pipeline");
613
614 assert_eq!(builder.vertex_shader.as_ref().unwrap(), "vertex.wgsl");
615 assert_eq!(builder.fragment_shader.as_ref().unwrap(), "fragment.wgsl");
616 assert_eq!(builder.format.unwrap(), TextureFormat::Bgra8UnormSrgb);
617 assert_eq!(builder.topology, PrimitiveTopology::LineList);
618 assert_eq!(builder.multisample_count, 4);
619 assert_eq!(builder.label.as_ref().unwrap(), "Test Pipeline");
620 }
621
622 #[test]
623 fn test_pipeline_builder_defaults() {
624 let builder = RenderPipelineBuilder::new();
625
626 assert!(builder.vertex_shader.is_none());
627 assert!(builder.fragment_shader.is_none());
628 assert!(builder.format.is_none());
629 assert_eq!(builder.topology, PrimitiveTopology::TriangleList);
630 assert_eq!(builder.multisample_count, 1);
631 assert!(builder.label.is_none());
632 }
633
634 #[test]
635 fn test_pipeline_builder_with_label() {
636 let builder = RenderPipelineBuilder::new()
637 .with_label("Test Pipeline");
638 assert_eq!(builder.label.as_deref(), Some("Test Pipeline"));
639 }
640
641 #[test]
642 fn test_pipeline_builder_with_format() {
643 let builder = RenderPipelineBuilder::new()
644 .with_format(TextureFormat::Bgra8UnormSrgb);
645 assert_eq!(builder.format, Some(TextureFormat::Bgra8UnormSrgb));
646 }
647
648 #[test]
649 fn test_pipeline_builder_with_topology() {
650 let builder = RenderPipelineBuilder::new()
651 .with_topology(PrimitiveTopology::LineList);
652 assert_eq!(builder.topology, PrimitiveTopology::LineList);
653 }
654
655 #[test]
656 fn test_pipeline_builder_with_multisample() {
657 let builder = RenderPipelineBuilder::new()
658 .with_multisample_count(4);
659 assert_eq!(builder.multisample_count, 4);
660 }
661
662 #[test]
663 fn test_pipeline_builder_chaining() {
664 let builder = RenderPipelineBuilder::new()
665 .with_label("Chained")
666 .with_format(TextureFormat::Rgba8Unorm)
667 .with_topology(PrimitiveTopology::TriangleStrip)
668 .with_multisample_count(2);
669
670 assert_eq!(builder.label.as_deref(), Some("Chained"));
671 assert_eq!(builder.format, Some(TextureFormat::Rgba8Unorm));
672 assert_eq!(builder.topology, PrimitiveTopology::TriangleStrip);
673 assert_eq!(builder.multisample_count, 2);
674 }
675}