1use std::collections::{BTreeMap, HashMap};
2use std::mem::size_of;
3use std::sync::Arc;
4
5use crate::engine::graphics::vulkano_swapchain::VulkanoSwapchainState;
6use vulkano::buffer::BufferContents;
7use vulkano::command_buffer::{
8 AutoCommandBufferBuilder, PrimaryAutoCommandBuffer, RenderingAttachmentInfo, RenderingInfo,
9};
10use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
11use vulkano::descriptor_set::layout::{
12 DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType,
13};
14use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet};
15use vulkano::device::Device;
16use vulkano::format::{ClearValue, Format};
17use vulkano::image::sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo};
18use vulkano::image::view::{ImageView, ImageViewCreateInfo};
19use vulkano::image::{
20 Image, ImageAspects, ImageCreateInfo, ImageSubresourceRange, ImageType, ImageUsage, SampleCount,
21};
22use vulkano::memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator};
23use vulkano::pipeline::graphics::color_blend::{ColorBlendAttachmentState, ColorBlendState};
24use vulkano::pipeline::graphics::input_assembly::InputAssemblyState;
25use vulkano::pipeline::graphics::multisample::MultisampleState;
26use vulkano::pipeline::graphics::rasterization::RasterizationState;
27use vulkano::pipeline::graphics::subpass::{PipelineRenderingCreateInfo, PipelineSubpassType};
28use vulkano::pipeline::graphics::vertex_input::VertexInputState;
29use vulkano::pipeline::graphics::viewport::{Scissor, Viewport, ViewportState};
30use vulkano::pipeline::layout::{PipelineLayout, PipelineLayoutCreateInfo, PushConstantRange};
31use vulkano::pipeline::{
32 DynamicState, GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineShaderStageCreateInfo,
33};
34use vulkano::render_pass::{AttachmentLoadOp, AttachmentStoreOp};
35use vulkano::shader::ShaderStages;
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
38pub enum BloomSource {
39 #[default]
40 Emissive,
41}
42
43#[derive(Debug, Clone)]
44pub struct EmissivePassConfig {
45 pub output_texture: Option<String>,
46}
47
48impl Default for EmissivePassConfig {
49 fn default() -> Self {
50 Self {
51 output_texture: None,
52 }
53 }
54}
55
56#[derive(Debug, Clone)]
57pub struct BlurPassConfig {
58 pub enabled: bool,
59 pub radius_ndc: f32,
60 pub half_res: bool,
61}
62
63impl Default for BlurPassConfig {
64 fn default() -> Self {
65 Self {
66 enabled: true,
67 radius_ndc: 0.05,
68 half_res: true,
69 }
70 }
71}
72
73#[derive(Debug, Clone)]
74pub struct BloomConfig {
75 pub intensity: f32,
76 pub radius_ndc: f32,
77 pub emissive_scale: f32,
78 pub half_res: bool,
79 pub source: BloomSource,
80 pub output_texture: Option<String>,
81}
82
83impl Default for BloomConfig {
84 fn default() -> Self {
85 Self {
86 intensity: 1.0,
87 radius_ndc: 0.05,
88 emissive_scale: 1.0,
89 half_res: true,
90 source: BloomSource::Emissive,
91 output_texture: None,
92 }
93 }
94}
95
96#[derive(Debug, Clone)]
97pub struct BokehConfig {
98 pub focus_distance: f32,
99 pub aperture: f32,
100 pub max_blur_radius: f32,
101}
102
103impl Default for BokehConfig {
104 fn default() -> Self {
105 Self {
106 focus_distance: 2.0,
107 aperture: 0.0,
108 max_blur_radius: 8.0,
109 }
110 }
111}
112
113#[derive(Debug, Clone, Default)]
114pub struct PostProcessingConfig {
115 pub enabled: bool,
116 pub emissive_pass: Option<EmissivePassConfig>,
117 pub blur_pass: Option<BlurPassConfig>,
118 pub bloom: Option<BloomConfig>,
119 pub bokeh: Option<BokehConfig>,
120}
121
122impl PostProcessingConfig {
123 pub fn is_active(&self) -> bool {
124 self.enabled && (self.bloom.is_some() || self.bokeh.is_some())
125 }
126
127 pub fn needs_depth(&self) -> bool {
128 self.enabled && (self.bloom.is_some() || self.bokeh.is_some())
129 }
130
131 pub fn bloom_radius_pixels(&self, viewport_width: u32) -> Option<u32> {
132 let bloom = self.bloom.as_ref()?;
133 Some(Self::ndc_radius_to_pixels(bloom.radius_ndc, viewport_width))
134 }
135
136 pub fn effective_blur_radius_pixels(&self, viewport_width: u32) -> Option<u32> {
138 if let Some(blur) = &self.blur_pass {
139 if blur.enabled {
140 return Some(Self::ndc_radius_to_pixels(blur.radius_ndc, viewport_width));
141 }
142 }
143 self.bloom_radius_pixels(viewport_width)
144 }
145
146 pub fn effective_blur_half_res(&self) -> bool {
148 if let Some(blur) = &self.blur_pass {
149 if blur.enabled {
150 return blur.half_res;
151 }
152 }
153 self.bloom.as_ref().map(|b| b.half_res).unwrap_or(false)
154 }
155
156 pub fn ndc_radius_to_pixels(radius_ndc: f32, viewport_width: u32) -> u32 {
157 ((radius_ndc.max(0.0) * viewport_width as f32) / 2.0)
158 .round()
159 .max(1.0) as u32
160 }
161}
162
163#[derive(Debug, Clone)]
164pub struct PostProcessFrameTargets {
165 pub main_color: Arc<ImageView>,
166 pub main_msaa_color: Option<Arc<ImageView>>,
167 pub depth: Arc<ImageView>,
168 pub bloom_source_msaa: Option<Arc<ImageView>>,
169 pub bloom_source: Option<Arc<ImageView>>,
170 pub bloom_a: Option<Arc<ImageView>>,
171 pub bloom_b: Option<Arc<ImageView>>,
172 pub bloom_extent: [u32; 2],
173}
174
175#[derive(Debug, Clone)]
176struct PostProcessTargetSet {
177 extent: [u32; 2],
178 color_format: Format,
179 msaa_samples: SampleCount,
180 frames: Vec<PostProcessFrameTargets>,
181}
182
183#[derive(BufferContents, Clone, Copy)]
184#[repr(C)]
185struct PostProcessPushConstants {
186 direction: [f32; 2],
187 bloom_intensity: f32,
188 radius_pixels: f32,
189}
190
191mod fullscreen_vs {
192 vulkano_shaders::shader! {
193 ty: "vertex",
194 path: "assets/shaders/post-process-fullscreen.vert",
195 }
196}
197
198mod blit_fs {
199 vulkano_shaders::shader! {
200 ty: "fragment",
201 path: "assets/shaders/post-process-copy.frag",
202 }
203}
204
205mod blur_fs {
206 vulkano_shaders::shader! {
207 ty: "fragment",
208 path: "assets/shaders/post-process-bloom-blur.frag",
209 }
210}
211
212mod composite_fs {
213 vulkano_shaders::shader! {
214 ty: "fragment",
215 path: "assets/shaders/post-process-bloom-composite.frag",
216 }
217}
218
219pub struct PostProcessingRenderer {
220 device: Arc<Device>,
221 memory_allocator: Arc<StandardMemoryAllocator>,
222 descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
223 sampler_linear: Arc<Sampler>,
224 sampled_layout: Arc<DescriptorSetLayout>,
225 blit_pipelines: HashMap<Format, Arc<GraphicsPipeline>>,
226 blur_pipelines: HashMap<Format, Arc<GraphicsPipeline>>,
227 composite_pipelines: HashMap<Format, Arc<GraphicsPipeline>>,
228 window_targets: Option<PostProcessTargetSet>,
229 xr_targets: Option<PostProcessTargetSet>,
230}
231
232impl PostProcessingRenderer {
233 pub fn new(
234 device: Arc<Device>,
235 memory_allocator: Arc<StandardMemoryAllocator>,
236 descriptor_set_allocator: Arc<StandardDescriptorSetAllocator>,
237 ) -> Result<Self, Box<dyn std::error::Error>> {
238 let mut bindings = BTreeMap::new();
239
240 let mut src0 =
241 DescriptorSetLayoutBinding::descriptor_type(DescriptorType::CombinedImageSampler);
242 src0.descriptor_count = 1;
243 src0.stages = ShaderStages::FRAGMENT;
244 bindings.insert(0, src0);
245
246 let mut src1 =
247 DescriptorSetLayoutBinding::descriptor_type(DescriptorType::CombinedImageSampler);
248 src1.descriptor_count = 1;
249 src1.stages = ShaderStages::FRAGMENT;
250 bindings.insert(1, src1);
251
252 let sampled_layout = DescriptorSetLayout::new(
253 device.clone(),
254 DescriptorSetLayoutCreateInfo {
255 bindings,
256 ..Default::default()
257 },
258 )?;
259
260 let sampler_linear = Sampler::new(
261 device.clone(),
262 SamplerCreateInfo {
263 mag_filter: Filter::Linear,
264 min_filter: Filter::Linear,
265 address_mode: [SamplerAddressMode::ClampToEdge; 3],
266 ..Default::default()
267 },
268 )?;
269
270 Ok(Self {
271 device,
272 memory_allocator,
273 descriptor_set_allocator,
274 sampler_linear,
275 sampled_layout,
276 blit_pipelines: HashMap::new(),
277 blur_pipelines: HashMap::new(),
278 composite_pipelines: HashMap::new(),
279 window_targets: None,
280 xr_targets: None,
281 })
282 }
283
284 pub fn ensure_window_targets(
285 &mut self,
286 frame_count: usize,
287 extent: [u32; 2],
288 color_format: Format,
289 msaa_samples: SampleCount,
290 config: &PostProcessingConfig,
291 ) -> Result<(), Box<dyn std::error::Error>> {
292 if !config.is_active() {
293 self.window_targets = None;
294 return Ok(());
295 }
296
297 let needs_recreate = self.window_targets.as_ref().is_none_or(|targets| {
298 targets.extent != extent
299 || targets.color_format != color_format
300 || targets.msaa_samples != msaa_samples
301 || targets.frames.len() != frame_count
302 });
303
304 if needs_recreate {
305 self.window_targets = Some(self.build_target_set(
306 frame_count,
307 extent,
308 color_format,
309 msaa_samples,
310 config,
311 )?);
312 }
313
314 Ok(())
315 }
316
317 pub fn ensure_xr_targets(
318 &mut self,
319 view_count: usize,
320 extent: [u32; 2],
321 color_format: Format,
322 msaa_samples: SampleCount,
323 config: &PostProcessingConfig,
324 ) -> Result<(), Box<dyn std::error::Error>> {
325 if !config.is_active() {
326 self.xr_targets = None;
327 return Ok(());
328 }
329
330 let needs_recreate = self.xr_targets.as_ref().is_none_or(|targets| {
331 targets.extent != extent
332 || targets.color_format != color_format
333 || targets.msaa_samples != msaa_samples
334 || targets.frames.len() != view_count
335 });
336
337 if needs_recreate {
338 self.xr_targets = Some(self.build_target_set(
339 view_count,
340 extent,
341 color_format,
342 msaa_samples,
343 config,
344 )?);
345 }
346
347 Ok(())
348 }
349
350 pub fn window_frame_targets(&self, frame: usize) -> Option<&PostProcessFrameTargets> {
351 self.window_targets.as_ref()?.frames.get(frame)
352 }
353
354 pub fn xr_frame_targets(&self, eye: usize) -> Option<&PostProcessFrameTargets> {
355 self.xr_targets.as_ref()?.frames.get(eye)
356 }
357
358 pub fn record_blur_pass(
359 &mut self,
360 cbb: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
361 color_format: Format,
362 source: Arc<ImageView>,
363 destination: Arc<ImageView>,
364 extent: [u32; 2],
365 direction: [f32; 2],
366 radius_pixels: u32,
367 ) -> Result<(), Box<dyn std::error::Error>> {
368 let pipeline = self.blur_pipeline(color_format)?;
369 let set = self.sampled_set(source.clone(), source)?;
370 self.record_fullscreen_pass(
371 cbb,
372 destination,
373 extent,
374 pipeline,
375 set,
376 PostProcessPushConstants {
377 direction,
378 bloom_intensity: 0.0,
379 radius_pixels: radius_pixels as f32,
380 },
381 )
382 }
383
384 pub fn record_final_pass(
385 &mut self,
386 cbb: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
387 color_format: Format,
388 output: Arc<ImageView>,
389 extent: [u32; 2],
390 main_color: Arc<ImageView>,
391 bloom_color: Option<Arc<ImageView>>,
392 config: &PostProcessingConfig,
393 ) -> Result<(), Box<dyn std::error::Error>> {
394 let (pipeline, set) = if let Some(bloom_view) = bloom_color {
395 (
396 self.composite_pipeline(color_format)?,
397 self.sampled_set(main_color, bloom_view)?,
398 )
399 } else {
400 (
401 self.blit_pipeline(color_format)?,
402 self.sampled_set(main_color.clone(), main_color)?,
403 )
404 };
405
406 self.record_fullscreen_pass(
407 cbb,
408 output,
409 extent,
410 pipeline,
411 set,
412 PostProcessPushConstants {
413 direction: [0.0, 0.0],
414 bloom_intensity: config
415 .bloom
416 .as_ref()
417 .map(|b| b.intensity.max(0.0))
418 .unwrap_or(0.0),
419 radius_pixels: 0.0,
420 },
421 )
422 }
423
424 fn record_fullscreen_pass(
425 &self,
426 cbb: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
427 output: Arc<ImageView>,
428 extent: [u32; 2],
429 pipeline: Arc<GraphicsPipeline>,
430 set: Arc<DescriptorSet>,
431 push_constants: PostProcessPushConstants,
432 ) -> Result<(), Box<dyn std::error::Error>> {
433 let viewport = Viewport {
434 offset: [0.0, 0.0],
435 extent: [extent[0] as f32, extent[1] as f32],
436 depth_range: 0.0..=1.0,
437 ..Default::default()
438 };
439
440 cbb.begin_rendering(RenderingInfo {
441 render_area_offset: [0, 0],
442 render_area_extent: extent,
443 layer_count: 1,
444 color_attachments: vec![Some(RenderingAttachmentInfo {
445 load_op: AttachmentLoadOp::Clear,
446 store_op: AttachmentStoreOp::Store,
447 clear_value: Some(ClearValue::from([0.0, 0.0, 0.0, 1.0])),
448 ..RenderingAttachmentInfo::image_view(output)
449 })],
450 ..Default::default()
451 })?;
452
453 cbb.set_viewport(0, vec![viewport].into())?;
454 cbb.set_scissor(
455 0,
456 vec![Scissor {
457 offset: [0, 0],
458 extent,
459 ..Default::default()
460 }]
461 .into(),
462 )?;
463 cbb.bind_pipeline_graphics(pipeline.clone())?;
464 cbb.bind_descriptor_sets(
465 PipelineBindPoint::Graphics,
466 pipeline.layout().clone(),
467 0,
468 set,
469 )?;
470 cbb.push_constants(pipeline.layout().clone(), 0, push_constants)?;
471 unsafe {
472 cbb.draw(3, 1, 0, 0)?;
473 }
474 cbb.end_rendering()?;
475 Ok(())
476 }
477
478 fn build_target_set(
479 &self,
480 count: usize,
481 extent: [u32; 2],
482 color_format: Format,
483 msaa_samples: SampleCount,
484 config: &PostProcessingConfig,
485 ) -> Result<PostProcessTargetSet, Box<dyn std::error::Error>> {
486 let bloom_extent = if config.effective_blur_half_res() {
487 [extent[0].max(2) / 2, extent[1].max(2) / 2]
488 } else {
489 extent
490 };
491
492 let mut frames = Vec::with_capacity(count);
493 for _ in 0..count {
494 let main_color = Self::create_color_view(
495 self.memory_allocator.clone(),
496 color_format,
497 extent,
498 SampleCount::Sample1,
499 ImageUsage::COLOR_ATTACHMENT | ImageUsage::SAMPLED | ImageUsage::TRANSFER_SRC,
500 )?;
501 let main_msaa_color = if msaa_samples != SampleCount::Sample1 {
502 Some(Self::create_color_view(
503 self.memory_allocator.clone(),
504 color_format,
505 extent,
506 msaa_samples,
507 ImageUsage::COLOR_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT,
508 )?)
509 } else {
510 None
511 };
512 let depth =
513 Self::create_depth_view(self.memory_allocator.clone(), extent, msaa_samples)?;
514
515 let (bloom_source_msaa, bloom_source, bloom_a, bloom_b) = if config.bloom.is_some() {
516 (
517 if msaa_samples != SampleCount::Sample1 {
518 Some(Self::create_color_view(
519 self.memory_allocator.clone(),
520 color_format,
521 extent,
522 msaa_samples,
523 ImageUsage::COLOR_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT,
524 )?)
525 } else {
526 None
527 },
528 Some(Self::create_color_view(
529 self.memory_allocator.clone(),
530 color_format,
531 extent,
532 SampleCount::Sample1,
533 ImageUsage::COLOR_ATTACHMENT
534 | ImageUsage::SAMPLED
535 | ImageUsage::TRANSFER_SRC,
536 )?),
537 Some(Self::create_color_view(
538 self.memory_allocator.clone(),
539 color_format,
540 bloom_extent,
541 SampleCount::Sample1,
542 ImageUsage::COLOR_ATTACHMENT
543 | ImageUsage::SAMPLED
544 | ImageUsage::TRANSFER_SRC,
545 )?),
546 Some(Self::create_color_view(
547 self.memory_allocator.clone(),
548 color_format,
549 bloom_extent,
550 SampleCount::Sample1,
551 ImageUsage::COLOR_ATTACHMENT
552 | ImageUsage::SAMPLED
553 | ImageUsage::TRANSFER_SRC,
554 )?),
555 )
556 } else {
557 (None, None, None, None)
558 };
559
560 frames.push(PostProcessFrameTargets {
561 main_color,
562 main_msaa_color,
563 depth,
564 bloom_source_msaa,
565 bloom_source,
566 bloom_a,
567 bloom_b,
568 bloom_extent,
569 });
570 }
571
572 Ok(PostProcessTargetSet {
573 extent,
574 color_format,
575 msaa_samples,
576 frames,
577 })
578 }
579
580 fn create_color_view(
581 memory_allocator: Arc<StandardMemoryAllocator>,
582 format: Format,
583 extent: [u32; 2],
584 samples: SampleCount,
585 usage: ImageUsage,
586 ) -> Result<Arc<ImageView>, Box<dyn std::error::Error>> {
587 let image = Image::new(
588 memory_allocator,
589 ImageCreateInfo {
590 image_type: ImageType::Dim2d,
591 format,
592 extent: [extent[0], extent[1], 1],
593 samples,
594 usage,
595 ..Default::default()
596 },
597 AllocationCreateInfo {
598 memory_type_filter: MemoryTypeFilter::PREFER_DEVICE,
599 ..Default::default()
600 },
601 )?;
602 Ok(ImageView::new_default(image)?)
603 }
604
605 fn create_depth_view(
606 memory_allocator: Arc<StandardMemoryAllocator>,
607 extent: [u32; 2],
608 samples: SampleCount,
609 ) -> Result<Arc<ImageView>, Box<dyn std::error::Error>> {
610 let image = Image::new(
611 memory_allocator,
612 ImageCreateInfo {
613 image_type: ImageType::Dim2d,
614 format: VulkanoSwapchainState::DEPTH_FORMAT,
615 extent: [extent[0], extent[1], 1],
616 samples,
617 usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT,
618 ..Default::default()
619 },
620 AllocationCreateInfo {
621 memory_type_filter: MemoryTypeFilter::PREFER_DEVICE,
622 ..Default::default()
623 },
624 )?;
625 Ok(ImageView::new(
626 image.clone(),
627 ImageViewCreateInfo {
628 subresource_range: ImageSubresourceRange {
629 aspects: ImageAspects::DEPTH | ImageAspects::STENCIL,
630 ..image.subresource_range()
631 },
632 ..ImageViewCreateInfo::from_image(&image)
633 },
634 )?)
635 }
636
637 fn sampled_set(
638 &self,
639 source0: Arc<ImageView>,
640 source1: Arc<ImageView>,
641 ) -> Result<Arc<DescriptorSet>, Box<dyn std::error::Error>> {
642 Ok(DescriptorSet::new(
643 self.descriptor_set_allocator.clone(),
644 self.sampled_layout.clone(),
645 [
646 WriteDescriptorSet::image_view_sampler(0, source0, self.sampler_linear.clone()),
647 WriteDescriptorSet::image_view_sampler(1, source1, self.sampler_linear.clone()),
648 ],
649 [],
650 )?)
651 }
652
653 fn blit_pipeline(
654 &mut self,
655 color_format: Format,
656 ) -> Result<Arc<GraphicsPipeline>, Box<dyn std::error::Error>> {
657 if let Some(existing) = self.blit_pipelines.get(&color_format) {
658 return Ok(existing.clone());
659 }
660
661 let pipeline = self.build_pipeline(
662 color_format,
663 blit_fs::load(self.device.clone())?
664 .entry_point("main")
665 .ok_or("missing post-process-copy.frag entry point")?,
666 )?;
667 self.blit_pipelines.insert(color_format, pipeline.clone());
668 Ok(pipeline)
669 }
670
671 fn blur_pipeline(
672 &mut self,
673 color_format: Format,
674 ) -> Result<Arc<GraphicsPipeline>, Box<dyn std::error::Error>> {
675 if let Some(existing) = self.blur_pipelines.get(&color_format) {
676 return Ok(existing.clone());
677 }
678
679 let pipeline = self.build_pipeline(
680 color_format,
681 blur_fs::load(self.device.clone())?
682 .entry_point("main")
683 .ok_or("missing post-process-bloom-blur.frag entry point")?,
684 )?;
685 self.blur_pipelines.insert(color_format, pipeline.clone());
686 Ok(pipeline)
687 }
688
689 fn composite_pipeline(
690 &mut self,
691 color_format: Format,
692 ) -> Result<Arc<GraphicsPipeline>, Box<dyn std::error::Error>> {
693 if let Some(existing) = self.composite_pipelines.get(&color_format) {
694 return Ok(existing.clone());
695 }
696
697 let pipeline = self.build_pipeline(
698 color_format,
699 composite_fs::load(self.device.clone())?
700 .entry_point("main")
701 .ok_or("missing post-process-bloom-composite.frag entry point")?,
702 )?;
703 self.composite_pipelines
704 .insert(color_format, pipeline.clone());
705 Ok(pipeline)
706 }
707
708 fn build_pipeline(
709 &self,
710 color_format: Format,
711 fragment_entry: vulkano::shader::EntryPoint,
712 ) -> Result<Arc<GraphicsPipeline>, Box<dyn std::error::Error>> {
713 let vs = fullscreen_vs::load(self.device.clone())?;
714 let layout = PipelineLayout::new(
715 self.device.clone(),
716 PipelineLayoutCreateInfo {
717 set_layouts: vec![self.sampled_layout.clone()],
718 push_constant_ranges: vec![PushConstantRange {
719 stages: ShaderStages::FRAGMENT,
720 offset: 0,
721 size: size_of::<PostProcessPushConstants>() as u32,
722 }],
723 ..Default::default()
724 },
725 )?;
726
727 let mut ci = vulkano::pipeline::graphics::GraphicsPipelineCreateInfo::layout(layout);
728 ci.stages = vec![
729 PipelineShaderStageCreateInfo::new(
730 vs.entry_point("main")
731 .ok_or("missing post-process-fullscreen.vert entry point")?,
732 ),
733 PipelineShaderStageCreateInfo::new(fragment_entry),
734 ]
735 .into();
736 ci.vertex_input_state = Some(VertexInputState::default());
737 ci.input_assembly_state = Some(InputAssemblyState::default());
738 ci.viewport_state = Some(ViewportState::default());
739 ci.rasterization_state = Some(RasterizationState::default());
740 ci.multisample_state = Some(MultisampleState::default());
741 ci.color_blend_state = Some(ColorBlendState::with_attachment_states(
742 1,
743 ColorBlendAttachmentState::default(),
744 ));
745 ci.dynamic_state = [DynamicState::Viewport, DynamicState::Scissor]
746 .into_iter()
747 .collect();
748
749 let mut rendering = PipelineRenderingCreateInfo::default();
750 rendering.color_attachment_formats = vec![Some(color_format)];
751 ci.subpass = Some(PipelineSubpassType::BeginRendering(rendering));
752
753 Ok(GraphicsPipeline::new(self.device.clone(), None, ci)?)
754 }
755}