use windows::Win32::Graphics::Direct3D12::*;
use crate::gfx::render_graph::{GraphResourceClass, ReadStages, ResourceState};
fn read_state(stages: ReadStages) -> D3D12_RESOURCE_STATES {
match (
stages.contains(ReadStages::FRAGMENT),
stages.contains(ReadStages::COMPUTE),
) {
(true, true) => {
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
| D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE
}
(false, true) => D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
(true, false) | (false, false) => D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
}
}
pub(super) fn d3d12_state(
class: GraphResourceClass,
state: ResourceState,
read_stages: ReadStages,
) -> D3D12_RESOURCE_STATES {
match (class, state) {
(_, ResourceState::Undefined) => D3D12_RESOURCE_STATE_COMMON,
(GraphResourceClass::IndirectBuffer, ResourceState::Read) => {
D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT
}
(GraphResourceClass::UnorderedBuffer, ResourceState::Read) => {
D3D12_RESOURCE_STATE_UNORDERED_ACCESS
}
(_, ResourceState::Read) => read_state(read_stages),
(GraphResourceClass::ColorTarget, ResourceState::Write) => {
D3D12_RESOURCE_STATE_RENDER_TARGET
}
(GraphResourceClass::DepthTarget, ResourceState::Write) => D3D12_RESOURCE_STATE_DEPTH_WRITE,
(
GraphResourceClass::StorageImage
| GraphResourceClass::IndirectBuffer
| GraphResourceClass::StorageBuffer
| GraphResourceClass::UnorderedBuffer,
ResourceState::Write,
) => D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
}
}
pub(super) fn d3d12_transition(
class: GraphResourceClass,
resting: D3D12_RESOURCE_STATES,
from: ResourceState,
to: ResourceState,
read_stages: ReadStages,
) -> Option<(D3D12_RESOURCE_STATES, D3D12_RESOURCE_STATES)> {
let before = if from == ResourceState::Undefined {
resting
} else {
d3d12_state(class, from, read_stages)
};
let after = d3d12_state(class, to, read_stages);
(before != after).then_some((before, after))
}
pub(super) fn d3d12_restore(
class: GraphResourceClass,
resting: D3D12_RESOURCE_STATES,
state: ResourceState,
read_stages: ReadStages,
) -> Option<(D3D12_RESOURCE_STATES, D3D12_RESOURCE_STATES)> {
if state == ResourceState::Undefined {
return None;
}
let before = d3d12_state(class, state, read_stages);
(before != resting).then_some((before, resting))
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(super) enum DxBarrier {
Transition(D3D12_RESOURCE_STATES, D3D12_RESOURCE_STATES),
Uav,
}
pub(super) fn d3d12_barrier(
class: GraphResourceClass,
resting: D3D12_RESOURCE_STATES,
from: ResourceState,
to: ResourceState,
read_stages: ReadStages,
) -> Option<DxBarrier> {
if let Some((before, after)) = d3d12_transition(class, resting, from, to, read_stages) {
return Some(DxBarrier::Transition(before, after));
}
let orders_a_write = matches!(
(from, to),
(ResourceState::Write, ResourceState::Write)
| (ResourceState::Write, ResourceState::Read)
| (ResourceState::Read, ResourceState::Write)
);
let unordered = matches!(
class,
GraphResourceClass::StorageImage
| GraphResourceClass::IndirectBuffer
| GraphResourceClass::StorageBuffer
| GraphResourceClass::UnorderedBuffer
);
(orders_a_write && unordered).then_some(DxBarrier::Uav)
}
#[cfg(test)]
mod tests {
use super::*;
const FRAG: ReadStages = ReadStages::FRAGMENT;
#[test]
fn class_state_mapping_is_pinned() {
assert_eq!(
d3d12_state(GraphResourceClass::ColorTarget, ResourceState::Read, FRAG),
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
);
assert_eq!(
d3d12_state(GraphResourceClass::ColorTarget, ResourceState::Write, FRAG),
D3D12_RESOURCE_STATE_RENDER_TARGET
);
assert_eq!(
d3d12_state(GraphResourceClass::DepthTarget, ResourceState::Read, FRAG),
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
);
assert_eq!(
d3d12_state(GraphResourceClass::DepthTarget, ResourceState::Write, FRAG),
D3D12_RESOURCE_STATE_DEPTH_WRITE
);
assert_eq!(
d3d12_state(GraphResourceClass::StorageImage, ResourceState::Write, FRAG),
D3D12_RESOURCE_STATE_UNORDERED_ACCESS
);
assert_eq!(
d3d12_state(GraphResourceClass::StorageImage, ResourceState::Read, FRAG),
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
);
}
#[test]
fn read_state_maps_by_consuming_stage() {
assert_eq!(
d3d12_state(
GraphResourceClass::ColorTarget,
ResourceState::Read,
ReadStages::FRAGMENT
),
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
);
assert_eq!(
d3d12_state(
GraphResourceClass::ColorTarget,
ResourceState::Read,
ReadStages::COMPUTE
),
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE
);
assert_eq!(
d3d12_state(
GraphResourceClass::ColorTarget,
ResourceState::Read,
ReadStages::FRAGMENT | ReadStages::COMPUTE
),
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
| D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE
);
assert_eq!(
d3d12_state(
GraphResourceClass::ColorTarget,
ResourceState::Read,
ReadStages::empty()
),
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
);
}
#[test]
fn transition_resolves_resting_and_skips_no_ops() {
assert_eq!(
d3d12_transition(
GraphResourceClass::ColorTarget,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState::Undefined,
ResourceState::Write,
FRAG,
),
Some((
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET
))
);
assert_eq!(
d3d12_transition(
GraphResourceClass::ColorTarget,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState::Write,
ResourceState::Read,
FRAG,
),
Some((
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
))
);
assert_eq!(
d3d12_transition(
GraphResourceClass::DepthTarget,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState::Undefined,
ResourceState::Write,
FRAG,
),
Some((
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_DEPTH_WRITE
))
);
assert_eq!(
d3d12_transition(
GraphResourceClass::DepthTarget,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState::Write,
ResourceState::Read,
FRAG,
),
Some((
D3D12_RESOURCE_STATE_DEPTH_WRITE,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
))
);
assert_eq!(
d3d12_transition(
GraphResourceClass::StorageImage,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState::Undefined,
ResourceState::Write,
FRAG,
),
Some((
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_UNORDERED_ACCESS
))
);
assert_eq!(
d3d12_transition(
GraphResourceClass::StorageImage,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState::Write,
ResourceState::Read,
FRAG,
),
Some((
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
))
);
assert_eq!(
d3d12_transition(
GraphResourceClass::DepthTarget,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
ResourceState::Undefined,
ResourceState::Write,
FRAG,
),
None
);
}
#[test]
fn transition_threads_compute_read_stage() {
assert_eq!(
d3d12_transition(
GraphResourceClass::ColorTarget,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState::Write,
ResourceState::Read,
ReadStages::COMPUTE,
),
Some((
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE
))
);
assert_eq!(
d3d12_transition(
GraphResourceClass::ColorTarget,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState::Write,
ResourceState::Read,
ReadStages::FRAGMENT | ReadStages::COMPUTE,
),
Some((
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
| D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE
))
);
assert_eq!(
d3d12_transition(
GraphResourceClass::ColorTarget,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState::Read,
ResourceState::Write,
ReadStages::COMPUTE,
),
Some((
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET
))
);
}
#[test]
fn unordered_buffer_edges_fall_back_to_a_uav_barrier() {
const UAV: D3D12_RESOURCE_STATES = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
for (from, to) in [
(ResourceState::Write, ResourceState::Read),
(ResourceState::Write, ResourceState::Write),
(ResourceState::Read, ResourceState::Write),
] {
assert_eq!(
d3d12_barrier(GraphResourceClass::UnorderedBuffer, UAV, from, to, FRAG),
Some(DxBarrier::Uav),
"{from:?} -> {to:?}"
);
}
assert_eq!(
d3d12_barrier(
GraphResourceClass::UnorderedBuffer,
UAV,
ResourceState::Undefined,
ResourceState::Write,
FRAG,
),
None
);
assert_eq!(
d3d12_barrier(
GraphResourceClass::UnorderedBuffer,
UAV,
ResourceState::Read,
ResourceState::Read,
FRAG,
),
None
);
}
#[test]
fn a_frame_that_ends_off_resting_takes_one_restore() {
assert_eq!(
d3d12_restore(
GraphResourceClass::StorageImage,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
ResourceState::Write,
ReadStages::empty(),
),
Some((
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE
))
);
assert_eq!(
d3d12_restore(
GraphResourceClass::DepthTarget,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
ResourceState::Read,
FRAG,
),
Some((
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_DEPTH_WRITE
))
);
assert_eq!(
d3d12_restore(
GraphResourceClass::DepthTarget,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
ResourceState::Write,
ReadStages::empty(),
),
None
);
assert_eq!(
d3d12_restore(
GraphResourceClass::DepthTarget,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
ResourceState::Undefined,
ReadStages::empty(),
),
None
);
}
#[test]
fn ordered_classes_take_a_transition_not_a_uav_barrier() {
assert_eq!(
d3d12_barrier(
GraphResourceClass::IndirectBuffer,
D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT,
ResourceState::Write,
ResourceState::Read,
FRAG,
),
Some(DxBarrier::Transition(
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT
))
);
assert_eq!(
d3d12_barrier(
GraphResourceClass::StorageBuffer,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState::Write,
ResourceState::Read,
FRAG,
),
Some(DxBarrier::Transition(
D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
))
);
assert_eq!(
d3d12_barrier(
GraphResourceClass::ColorTarget,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState::Write,
ResourceState::Write,
FRAG,
),
None
);
}
}