Add partial support for khr_fragment_shading_rate (#2574)

* support `khr_fragment_shading_rate`

* Add taskgraph `set_fragment_shading_rate`

* Add `set_fragment_shading_rate` validation

* Cleanup `FragmentShadingRateState` validation

* Consolidate fragment_shading_rate validation

* Add docs for `fragment_shading_rate`

* Update vulkano-taskgraph/src/command_buffer/commands/dynamic_state.rs

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>

* Format fragment_shading_rate validation messages

* Apply suggestions from code review

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>

---------

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>
This commit is contained in:
Lachlan Deakin 2024-10-19 16:34:19 +11:00 committed by GitHub
parent dc619148b0
commit 6981040486
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 443 additions and 5 deletions

View File

@ -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
}
}

View File

@ -20,6 +20,7 @@ use crate::{
graphics::{
color_blend::LogicOp,
depth_stencil::{CompareOp, StencilOps},
fragment_shading_rate::FragmentShadingRateState,
input_assembly::PrimitiveTopology,
rasterization::{
ConservativeRasterizationMode, CullMode, DepthBiasState, FrontFace, LineStipple,
@ -1331,6 +1332,7 @@ pub(in crate::command_buffer) struct CommandBufferBuilderState {
pub(in crate::command_buffer) conservative_rasterization_mode:
Option<ConservativeRasterizationMode>,
pub(in crate::command_buffer) extra_primitive_overestimation_size: Option<f32>,
pub(in crate::command_buffer) fragment_shading_rate: Option<FragmentShadingRateState>,
// Active queries
pub(in crate::command_buffer) queries: HashMap<QueryType, QueryState>,
@ -1362,7 +1364,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,

View File

@ -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<L> RecordingCommandBuffer<L> {
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<ValidationError>> {
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<ValidationError>> {
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<ValidationError>> {
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
}
}

View File

@ -2633,7 +2633,25 @@ impl<L> RecordingCommandBuffer<L> {
}
}
// 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 {

View File

@ -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<ValidationError>> {
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
}
}

View File

@ -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<DiscardRectangleState>,
fragment_shading_rate_state: Option<FragmentShadingRateState>,
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<FragmentTestsStages> {
@ -723,6 +739,11 @@ pub struct GraphicsPipelineCreateInfo {
/// The default value is `None`.
pub discard_rectangle_state: Option<DiscardRectangleState>,
/// The fragment shading rate state.
///
/// The default value is `None`.
pub fragment_shading_rate_state: Option<FragmentShadingRateState>,
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<ash::vk::PipelineDiscardRectangleStateCreateInfoEXT<'a>>,
pub(crate) rendering_vk: Option<ash::vk::PipelineRenderingCreateInfo<'a>>,
pub(crate) fragment_shading_rate_vk:
Option<ash::vk::PipelineFragmentShadingRateStateCreateInfoKHR<'a>>,
}
pub(crate) struct GraphicsPipelineCreateInfoFields1Vk<'a> {

View File

@ -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).