diff --git a/vulkano-taskgraph/src/command_buffer/commands/dynamic_state.rs b/vulkano-taskgraph/src/command_buffer/commands/dynamic_state.rs index e3d9168fc9..79ae0b82a3 100644 --- a/vulkano-taskgraph/src/command_buffer/commands/dynamic_state.rs +++ b/vulkano-taskgraph/src/command_buffer/commands/dynamic_state.rs @@ -7,6 +7,7 @@ use vulkano::{ pipeline::graphics::{ color_blend::LogicOp, depth_stencil::{CompareOp, StencilFaces, StencilOp}, + fragment_shading_rate::FragmentShadingRateCombinerOp, input_assembly::PrimitiveTopology, rasterization::{ConservativeRasterizationMode, CullMode, FrontFace}, vertex_input::{ @@ -772,4 +773,34 @@ impl RecordingCommandBuffer<'_> { self } + + /// Sets the dynamic fragment shading rate for future draw calls. + pub unsafe fn set_fragment_shading_rate( + &mut self, + fragment_size: [u32; 2], + combiner_ops: [FragmentShadingRateCombinerOp; 2], + ) -> Result<&mut Self> { + unsafe { Ok(self.set_fragment_shading_rate_unchecked(fragment_size, combiner_ops)) } + } + + pub unsafe fn set_fragment_shading_rate_unchecked( + &mut self, + fragment_size: [u32; 2], + combiner_ops: [FragmentShadingRateCombinerOp; 2], + ) -> &mut Self { + let fns = self.device().fns(); + let fragment_size = vk::Extent2D { + width: fragment_size[0], + height: fragment_size[1], + }; + let combiner_ops = [combiner_ops[0].into(), combiner_ops[1].into()]; + unsafe { + (fns.khr_fragment_shading_rate + .cmd_set_fragment_shading_rate_khr)( + self.handle(), &fragment_size, &combiner_ops + ) + } + + self + } } diff --git a/vulkano/src/command_buffer/auto/builder.rs b/vulkano/src/command_buffer/auto/builder.rs index 4a47d3e94c..ab8f305319 100644 --- a/vulkano/src/command_buffer/auto/builder.rs +++ b/vulkano/src/command_buffer/auto/builder.rs @@ -19,6 +19,7 @@ use crate::{ graphics::{ color_blend::LogicOp, depth_stencil::{CompareOp, StencilOps}, + fragment_shading_rate::FragmentShadingRateState, input_assembly::PrimitiveTopology, rasterization::{ ConservativeRasterizationMode, CullMode, DepthBiasState, FrontFace, LineStipple, @@ -1212,6 +1213,7 @@ pub(in crate::command_buffer) struct CommandBufferBuilderState { pub(in crate::command_buffer) conservative_rasterization_mode: Option, pub(in crate::command_buffer) extra_primitive_overestimation_size: Option, + pub(in crate::command_buffer) fragment_shading_rate: Option, // Active queries pub(in crate::command_buffer) queries: HashMap, @@ -1243,7 +1245,7 @@ impl CommandBufferBuilderState { DynamicState::DepthWriteEnable => self.depth_write_enable = None, DynamicState::DiscardRectangle => self.discard_rectangle.clear(), // DynamicState::ExclusiveScissor => todo!(), - // DynamicState::FragmentShadingRate => todo!(), + DynamicState::FragmentShadingRate => self.fragment_shading_rate = None, DynamicState::FrontFace => self.front_face = None, DynamicState::LineStipple => self.line_stipple = None, DynamicState::LineWidth => self.line_width = None, diff --git a/vulkano/src/command_buffer/commands/dynamic_state.rs b/vulkano/src/command_buffer/commands/dynamic_state.rs index b01b350ad6..db4ec835d4 100644 --- a/vulkano/src/command_buffer/commands/dynamic_state.rs +++ b/vulkano/src/command_buffer/commands/dynamic_state.rs @@ -5,6 +5,7 @@ use crate::{ graphics::{ color_blend::LogicOp, depth_stencil::{CompareOp, StencilFaces, StencilOp, StencilOps}, + fragment_shading_rate::{FragmentShadingRateCombinerOp, FragmentShadingRateState}, input_assembly::PrimitiveTopology, rasterization::{ ConservativeRasterizationMode, CullMode, DepthBiasState, FrontFace, LineStipple, @@ -1280,6 +1281,54 @@ impl RecordingCommandBuffer { self } + + /// Sets the dynamic fragment shading rate for future draw calls. + #[inline] + pub fn set_fragment_shading_rate( + &mut self, + fragment_size: [u32; 2], + combiner_ops: [FragmentShadingRateCombinerOp; 2], + ) -> Result<&mut Self, Box> { + self.validate_set_fragment_shading_rate(fragment_size, combiner_ops)?; + + unsafe { Ok(self.set_fragment_shading_rate_unchecked(fragment_size, combiner_ops)) } + } + + fn validate_set_fragment_shading_rate( + &self, + fragment_size: [u32; 2], + combiner_ops: [FragmentShadingRateCombinerOp; 2], + ) -> Result<(), Box> { + self.inner + .validate_set_fragment_shading_rate(fragment_size, combiner_ops)?; + + self.validate_graphics_pipeline_fixed_state(DynamicState::FragmentShadingRate)?; + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn set_fragment_shading_rate_unchecked( + &mut self, + fragment_size: [u32; 2], + combiner_ops: [FragmentShadingRateCombinerOp; 2], + ) -> &mut Self { + self.builder_state.fragment_shading_rate = Some(FragmentShadingRateState { + fragment_size, + combiner_ops, + ..FragmentShadingRateState::default() + }); + + self.add_command( + "set_fragment_shading_rate", + Default::default(), + move |out: &mut RawRecordingCommandBuffer| { + out.set_fragment_shading_rate_unchecked(fragment_size, combiner_ops); + }, + ); + + self + } } impl RawRecordingCommandBuffer { @@ -3381,4 +3430,44 @@ impl RawRecordingCommandBuffer { self } + + fn validate_set_fragment_shading_rate( + &self, + fragment_size: [u32; 2], + combiner_ops: [FragmentShadingRateCombinerOp; 2], + ) -> Result<(), Box> { + FragmentShadingRateState { + fragment_size, + combiner_ops, + ..FragmentShadingRateState::default() + } + .validate(self.device())?; + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn set_fragment_shading_rate_unchecked( + &mut self, + fragment_size: [u32; 2], + combiner_ops: [FragmentShadingRateCombinerOp; 2], + ) -> &mut Self { + let fns = self.device().fns(); + + let fragment_size = ash::vk::Extent2D { + width: fragment_size[0], + height: fragment_size[1], + }; + let combiner_ops: [ash::vk::FragmentShadingRateCombinerOpKHR; 2] = + [combiner_ops[0].into(), combiner_ops[1].into()]; + + (fns.khr_fragment_shading_rate + .cmd_set_fragment_shading_rate_khr)( + self.handle(), + &fragment_size, + combiner_ops.as_ptr().cast(), + ); + + self + } } diff --git a/vulkano/src/command_buffer/commands/pipeline.rs b/vulkano/src/command_buffer/commands/pipeline.rs index 5ae954e0a8..a6b44e8968 100644 --- a/vulkano/src/command_buffer/commands/pipeline.rs +++ b/vulkano/src/command_buffer/commands/pipeline.rs @@ -2633,7 +2633,25 @@ impl RecordingCommandBuffer { } } // DynamicState::ExclusiveScissor => todo!(), - // DynamicState::FragmentShadingRate => todo!(), + DynamicState::FragmentShadingRate => { + if self.builder_state.fragment_shading_rate.is_none() { + return Err(Box::new(ValidationError { + problem: format!( + "the currently bound graphics pipeline requires the \ + `DynamicState::{:?}` dynamic state, but \ + this state was either not set, or it was overwritten by a \ + more recent `bind_pipeline_graphics` command", + dynamic_state + ) + .into(), + vuids: vuids!( + vuid_type, + "VUID-vkCmdDrawIndexed-pipelineFragmentShadingRate-09238" + ), + ..Default::default() + })); + } + } DynamicState::FrontFace => { if self.builder_state.front_face.is_none() { return Err(Box::new(ValidationError { diff --git a/vulkano/src/pipeline/graphics/fragment_shading_rate.rs b/vulkano/src/pipeline/graphics/fragment_shading_rate.rs new file mode 100644 index 0000000000..8ac8208917 --- /dev/null +++ b/vulkano/src/pipeline/graphics/fragment_shading_rate.rs @@ -0,0 +1,208 @@ +//! Fragment shading rate introduces the ability to change the rate at which fragments are shaded. +//! +//! This feature is part of the [`khr_fragment_shading_rate`] device extension. +//! +//! The fragment shading rate can be controlled per-draw, per-primitive, and per-region: +//! - Per-draw shading rate requires the [`pipeline_fragment_shading_rate`] feature to be enabled +//! on the device, and the rate can be set in a graphics pipeline or dynamically via +//! [`set_fragment_shading_rate`]. +//! - Per-primitive shading rate requires the [`primitive_fragment_shading_rate`] feature to be +//! enabled on the device, and is set in the last active pre-rasterization shader stage. +//! - Per-region shading rate requires the [`attachment_fragment_shading_rate`] feature to be +//! enabled on the device and an additional specialised fragment shading rate image attachment. +//! +//! `vulkano` does not currently support per-region shading state. +//! +//! [`khr_fragment_shading_rate`]: crate::device::DeviceExtensions::khr_fragment_shading_rate +//! [`pipeline_fragment_shading_rate`]: crate::device::DeviceFeatures::pipeline_fragment_shading_rate +//! [`set_fragment_shading_rate`]: crate::command_buffer::AutoCommandBufferBuilder::set_fragment_shading_rate +//! [`primitive_fragment_shading_rate`]: crate::device::DeviceFeatures::primitive_fragment_shading_rate +//! [`attachment_fragment_shading_rate`]: crate::device::DeviceFeatures::attachment_fragment_shading_rate + +use crate::{device::Device, macros::vulkan_enum, ValidationError}; +use ash::vk; + +/// The state in a graphics pipeline describing the fragment shading rate. +#[derive(Clone, Debug)] +pub struct FragmentShadingRateState { + /// The pipeline fragment shading rate. + /// + /// The default value is `[1, 1]`. + pub fragment_size: [u32; 2], + + /// Determines how the pipeline, primitive, and attachment shading rates are combined for + /// fragments generated. + /// + /// The default value is `[FragmentShadingRateCombinerOp::Keep; 2]`. + pub combiner_ops: [FragmentShadingRateCombinerOp; 2], + + pub _ne: crate::NonExhaustive, +} + +impl Default for FragmentShadingRateState { + #[inline] + fn default() -> Self { + Self { + fragment_size: [1, 1], + combiner_ops: [FragmentShadingRateCombinerOp::Keep; 2], + _ne: crate::NonExhaustive(()), + } + } +} + +impl FragmentShadingRateState { + pub(crate) fn validate(&self, device: &Device) -> Result<(), Box> { + let &Self { + fragment_size, + combiner_ops, + _ne: _, + } = self; + + let properties = device.physical_device().properties(); + let features = device.enabled_features(); + + if !matches!(fragment_size[0], 1 | 2 | 4) { + return Err(Box::new(ValidationError { + context: "fragment_size[0]".into(), + problem: "`fragment_size[0]` must be 1, 2, or 4".into(), + vuids: &[ + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04494", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04496", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04498", + ], + ..Default::default() + })); + } + + if !matches!(fragment_size[1], 1 | 2 | 4) { + return Err(Box::new(ValidationError { + context: "fragment_size[1]".into(), + problem: "`fragment_size[1]` must be 1, 2, or 4".into(), + vuids: &[ + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04495", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04497", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04499", + ], + ..Default::default() + })); + } + + if !features.pipeline_fragment_shading_rate { + return Err(Box::new(ValidationError { + context: "features.pipeline_fragment_shading_rate".into(), + problem: "the `pipeline_fragment_shading_rate` feature must be enabled".into(), + vuids: &["VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04500"], + ..Default::default() + })); + } + + combiner_ops[0].validate_device(device).map_err(|err| { + err.add_context("combiner_ops[0]") + .set_vuids(&["VUID-VkGraphicsPipelineCreateInfo-pDynamicState-06567"]) + })?; + + combiner_ops[1].validate_device(device).map_err(|err| { + err.add_context("combiner_ops[1]") + .set_vuids(&["VUID-VkGraphicsPipelineCreateInfo-pDynamicState-06568"]) + })?; + + if !features.primitive_fragment_shading_rate + && combiner_ops[0] != FragmentShadingRateCombinerOp::Keep + { + return Err(Box::new(ValidationError { + context: "combiner_ops[0]".into(), + problem: "the `primitive_fragment_shading_rate` feature must be enabled if \ + `combiner_ops[0]` is not `FragmentShadingRateCombinerOp::Keep`" + .into(), + vuids: &["VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04501"], + ..Default::default() + })); + } + + if !features.attachment_fragment_shading_rate + && combiner_ops[1] != FragmentShadingRateCombinerOp::Keep + { + return Err(Box::new(ValidationError { + context: "combiner_ops[1]".into(), + problem: "the `attachment_fragment_shading_rate` feature must be enabled if \ + `combiner_ops[1]` is not `FragmentShadingRateCombinerOp::Keep`" + .into(), + vuids: &["VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04502"], + ..Default::default() + })); + } + + if let Some(fragment_shading_rate_non_trivial_combiner_ops) = + properties.fragment_shading_rate_non_trivial_combiner_ops + { + if !fragment_shading_rate_non_trivial_combiner_ops + && (!matches!( + combiner_ops[0], + FragmentShadingRateCombinerOp::Keep | FragmentShadingRateCombinerOp::Replace + ) || !matches!( + combiner_ops[1], + FragmentShadingRateCombinerOp::Keep | FragmentShadingRateCombinerOp::Replace + )) + { + return Err(Box::new(ValidationError { + context: "combiner_ops[0]".into(), + problem: "the `fragment_shading_rate_non_trivial_combiner_ops` feature must be \ + enabled if `combiner_ops[0]` or `combiner_ops[1]` is not \ + `FragmentShadingRateCombinerOp::Keep` or \ + `FragmentShadingRateCombinerOp::Replace`" + .into(), + vuids: &[ + "VUID-VkGraphicsPipelineCreateInfo-fragmentShadingRateNonTrivialCombinerOps-04506", + ], + ..Default::default() + })); + } + } + + Ok(()) + } + + pub(crate) fn to_vk<'a>(&self) -> ash::vk::PipelineFragmentShadingRateStateCreateInfoKHR<'a> { + let fragment_size = vk::Extent2D { + width: self.fragment_size[0], + height: self.fragment_size[1], + }; + let combiner_ops: [ash::vk::FragmentShadingRateCombinerOpKHR; 2] = + [self.combiner_ops[0].into(), self.combiner_ops[1].into()]; + + ash::vk::PipelineFragmentShadingRateStateCreateInfoKHR::default() + .fragment_size(fragment_size) + .combiner_ops(combiner_ops) + } +} + +vulkan_enum! { + #[non_exhaustive] + + /// Control how fragment shading rates are combined. + FragmentShadingRateCombinerOp = FragmentShadingRateCombinerOpKHR(i32); + + /// Specifies a combiner operation of combine(Axy,Bxy) = Axy. + Keep = KEEP, + + /// Specifies a combiner operation of combine(Axy,Bxy) = Bxy. + Replace = REPLACE, + + /// Specifies a combiner operation of combine(Axy,Bxy) = min(Axy,Bxy). + Min = MIN, + + /// Specifies a combiner operation of combine(Axy,Bxy) = max(Axy,Bxy). + Max = MAX, + + /// Specifies a combiner operation of combine(Axy,Bxy) = Axy * Bxy. + /// + /// See the vulkan specification for more information on how this operation is performed if `fragmentShadingRateStrictMultiplyCombiner` is `false`. + Mul = MUL, +} + +impl Default for FragmentShadingRateCombinerOp { + #[inline] + fn default() -> Self { + Self::Keep + } +} diff --git a/vulkano/src/pipeline/graphics/mod.rs b/vulkano/src/pipeline/graphics/mod.rs index 7e6d327704..e3eb1c85e8 100644 --- a/vulkano/src/pipeline/graphics/mod.rs +++ b/vulkano/src/pipeline/graphics/mod.rs @@ -114,6 +114,7 @@ use crate::{ Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, VulkanError, VulkanObject, }; use ahash::{HashMap, HashSet}; +use fragment_shading_rate::FragmentShadingRateState; use smallvec::SmallVec; use std::{ collections::hash_map::Entry, fmt::Debug, mem::MaybeUninit, num::NonZeroU64, ptr, sync::Arc, @@ -122,6 +123,7 @@ use std::{ pub mod color_blend; pub mod depth_stencil; pub mod discard_rectangle; +pub mod fragment_shading_rate; pub mod input_assembly; pub mod multisample; pub mod rasterization; @@ -157,6 +159,7 @@ pub struct GraphicsPipeline { subpass: PipelineSubpassType, discard_rectangle_state: Option, + fragment_shading_rate_state: Option, descriptor_binding_requirements: HashMap<(u32, u32), DescriptorBindingRequirements>, num_used_descriptor_sets: u32, @@ -272,6 +275,8 @@ impl GraphicsPipeline { discard_rectangle_state, + fragment_shading_rate_state, + _ne: _, } = create_info; @@ -409,6 +414,10 @@ impl GraphicsPipeline { fixed_state.extend([DynamicState::DiscardRectangle]); } + if fragment_shading_rate_state.is_some() { + fixed_state.extend([DynamicState::FragmentShadingRate]); + } + fixed_state.retain(|state| !dynamic_state.contains(state)); Arc::new(Self { @@ -432,6 +441,7 @@ impl GraphicsPipeline { subpass: subpass.unwrap(), discard_rectangle_state, + fragment_shading_rate_state, descriptor_binding_requirements, num_used_descriptor_sets, @@ -531,6 +541,12 @@ impl GraphicsPipeline { self.discard_rectangle_state.as_ref() } + /// Returns the fragment shading rate state used to create this pipeline. + #[inline] + pub fn fragment_shading_rate_state(&self) -> Option<&FragmentShadingRateState> { + self.fragment_shading_rate_state.as_ref() + } + /// If the pipeline has a fragment shader, returns the fragment tests stages used. #[inline] pub fn fragment_tests_stages(&self) -> Option { @@ -723,6 +739,11 @@ pub struct GraphicsPipelineCreateInfo { /// The default value is `None`. pub discard_rectangle_state: Option, + /// The fragment shading rate state. + /// + /// The default value is `None`. + pub fragment_shading_rate_state: Option, + pub _ne: crate::NonExhaustive, } @@ -749,6 +770,8 @@ impl GraphicsPipelineCreateInfo { base_pipeline: None, discard_rectangle_state: None, + fragment_shading_rate_state: None, + _ne: crate::NonExhaustive(()), } } @@ -775,6 +798,8 @@ impl GraphicsPipelineCreateInfo { ref base_pipeline, ref discard_rectangle_state, + ref fragment_shading_rate_state, + _ne: _, } = self; @@ -1205,6 +1230,37 @@ impl GraphicsPipelineCreateInfo { _ => (), } + match ( + fragment_shading_rate_state.is_some(), + need_pre_rasterization_shader_state, + ) { + (true, false) => { + return Err(Box::new(ValidationError { + problem: "the pipeline is not being created with \ + pre-rasterization state, but \ + `fragment_shading_rate_state` is `Some`" + .into(), + vuids: &[ + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04494", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04495", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04496", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04497", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04498", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04499", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04500", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-06567", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-06568", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04501", + "VUID-VkGraphicsPipelineCreateInfo-pDynamicState-04502", + "VUID-VkGraphicsPipelineCreateInfo-fragmentShadingRateNonTrivialCombinerOps-04506", + ], + ..Default::default() + })); + } + (false, true) => (), + _ => (), + } + /* Validate shader stages individually */ @@ -1485,6 +1541,23 @@ impl GraphicsPipelineCreateInfo { .map_err(|err| err.add_context("discard_rectangle_state"))?; } + if let Some(fragment_shading_rate_state) = fragment_shading_rate_state { + if !device.enabled_extensions().khr_fragment_shading_rate { + return Err(Box::new(ValidationError { + context: "fragment_shading_rate_state".into(), + problem: "is `Some`".into(), + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceExtension( + "khr_fragment_shading_rate", + )])]), + ..Default::default() + })); + } + + fragment_shading_rate_state + .validate(device) + .map_err(|err| err.add_context("fragment_shading_rate_state"))?; + } + for dynamic_state in dynamic_state.iter().copied() { dynamic_state.validate_device(device).map_err(|err| { err.add_context("dynamic_state") @@ -2287,6 +2360,8 @@ impl GraphicsPipelineCreateInfo { ref base_pipeline, discard_rectangle_state: _, + fragment_shading_rate_state: _, + _ne: _, } = self; let (render_pass_vk, subpass_vk) = match subpass { @@ -2360,12 +2435,17 @@ impl GraphicsPipelineCreateInfo { let GraphicsPipelineCreateInfoExtensionsVk { discard_rectangle_state_vk, rendering_vk, + fragment_shading_rate_vk, } = extensions_vk; if let Some(next) = discard_rectangle_state_vk { val_vk = val_vk.push_next(next); } + if let Some(next) = fragment_shading_rate_vk { + val_vk = val_vk.push_next(next); + } + if let Some(next) = rendering_vk { val_vk = val_vk.push_next(next); } @@ -2392,10 +2472,15 @@ impl GraphicsPipelineCreateInfo { .as_ref() .zip(rendering_fields1_vk.as_ref()) .map(|(subpass, fields1_vk)| subpass.to_vk_rendering(fields1_vk)); + let fragment_shading_rate_vk = self + .fragment_shading_rate_state + .as_ref() + .map(|fragment_shading_rate_state| fragment_shading_rate_state.to_vk()); GraphicsPipelineCreateInfoExtensionsVk { discard_rectangle_state_vk, rendering_vk, + fragment_shading_rate_vk, } } @@ -2608,6 +2693,8 @@ pub(crate) struct GraphicsPipelineCreateInfoExtensionsVk<'a> { pub(crate) discard_rectangle_state_vk: Option>, pub(crate) rendering_vk: Option>, + pub(crate) fragment_shading_rate_vk: + Option>, } pub(crate) struct GraphicsPipelineCreateInfoFields1Vk<'a> { diff --git a/vulkano/src/pipeline/mod.rs b/vulkano/src/pipeline/mod.rs index 1e094b6d24..ec7a3b5a95 100644 --- a/vulkano/src/pipeline/mod.rs +++ b/vulkano/src/pipeline/mod.rs @@ -588,12 +588,15 @@ vulkan_enum! { RequiresAllOf([DeviceExtension(nv_scissor_exclusive)]), ]), */ - /* TODO: enable - // TODO: document + /// The value of + /// [`FragmentShadingRateState`](crate::pipeline::graphics::fragment_shading_rate::FragmentShadingRateState). + /// + /// Set with + /// [`set_fragment_shading_rate`](crate::command_buffer::RecordingCommandBuffer::set_fragment_shading_rate). FragmentShadingRate = FRAGMENT_SHADING_RATE_KHR RequiresOneOf([ RequiresAllOf([DeviceExtension(khr_fragment_shading_rate)]), - ]), */ + ]), /// The value of /// [`RasterizationState::line_stipple`](crate::pipeline::graphics::rasterization::RasterizationState::line_stipple).