use serde::Serialize;
use iridia_interface::{AlphaComparison, Label, MatrixLines, OutputUse, ResourceId};
use super::encoding::{LaneAffine, NormalEncoding, NormalSpace, ScalarEncoding};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub enum Attribute {
Position,
Normal,
Tangent(u8),
TexCoord(u8),
Color(u8),
Joints(u8),
Weights(u8),
ShaderInput(u8),
Point(u8),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum InputRole {
Stream(Attribute),
Lanes(Vec<LaneStream>),
Previous,
PreviousOr {
fallback: super::InputFallback,
why: String,
},
System,
Mapped {
lanes: Vec<LaneStream>,
map: LaneAffine,
},
Instance(Vec<InstanceLanes>),
ScreenTriangle(ScreenTriangleValue),
Unknown {
why: String,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ScreenTriangleValue {
Position,
TextureCoordinate,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
pub struct LaneStream {
pub semantic: Attribute,
pub first: u8,
pub count: u8,
pub component: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
pub struct InstanceLanes {
pub value: InstanceValue,
pub first: u8,
pub count: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum InstanceValue {
Translation,
Scale,
Axis(u8),
LodBlend,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ViewParameter {
CameraPosition,
View,
Projection,
ViewProjection,
CameraRelativeViewProjection,
InverseView,
InverseProjection,
InverseViewProjection,
ViewportSize,
InverseViewportSize,
NearPlane,
FarPlane,
InverseFarPlane,
ProjectionFlip,
PreviousViewProjection,
ResolutionScale,
Perspective,
DepthRange,
DeviceDepthOffset,
DeviceDepthScale,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ObjectMatrix {
World,
WorldView,
WorldViewProjection,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ObjectParameter {
World,
InverseWorld,
WorldViewProjection,
WorldView,
ViewToObject,
PreviousFrame(ObjectMatrix),
CameraRelative(ObjectMatrix),
CameraRelativePreviousFrame(ObjectMatrix),
InstanceColour,
TextureScroll(u8),
BonePalette,
ClothCancelPalette,
EnvironmentData,
MatrixBase,
InstanceBase,
ClothSimulation,
ClipPlane,
TextureTransformRow(u8),
PaletteCoordinate,
CameraFadeEnabled,
CameraFadeEnd,
CameraFadeInverseRange,
DitherAlphaEnabled,
DitherEnabled,
Fade,
WindOff,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum FrameParameter {
Time,
TimeOfDay,
PreExposure,
InversePreExposure,
Exposure,
InverseExposure,
ColourSlot,
LightDirection,
DirectionToLight,
LightColour,
LightSpecularColour,
DirectionalAmbientColour,
LightCount(super::LightTableKind),
LightTileSize,
LightTilesPerRow,
LocalShadowAtlasSize,
InverseLocalShadowAtlasSize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ItemParameter {
LightRecord,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ConstantRole {
Material(Vec<u32>),
View(ViewParameter),
Object(ObjectParameter),
Frame(FrameParameter),
Item(ItemParameter),
AlphaReference {
value: f32,
compared: AlphaComparison,
},
Unknown {
why: String,
},
Default {
words: Vec<u32>,
why: String,
},
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct ConstantBinding {
pub resource: ResourceId,
pub offset: u32,
pub size: u32,
pub name: Option<String>,
pub role: ConstantRole,
#[serde(skip_serializing_if = "Option::is_none")]
pub lines: Option<MatrixLines>,
}
impl ConstantRole {
pub fn is_matrix(&self) -> bool {
match self {
ConstantRole::View(v) => matches!(
v,
ViewParameter::View
| ViewParameter::Projection
| ViewParameter::ViewProjection
| ViewParameter::CameraRelativeViewProjection
| ViewParameter::InverseView
| ViewParameter::InverseProjection
| ViewParameter::InverseViewProjection
| ViewParameter::PreviousViewProjection
),
ConstantRole::Object(o) => matches!(
o,
ObjectParameter::World
| ObjectParameter::InverseWorld
| ObjectParameter::WorldViewProjection
| ObjectParameter::WorldView
| ObjectParameter::ViewToObject
| ObjectParameter::PreviousFrame(_)
| ObjectParameter::CameraRelative(_)
| ObjectParameter::CameraRelativePreviousFrame(_)
| ObjectParameter::BonePalette
| ObjectParameter::ClothCancelPalette
),
ConstantRole::Material(_)
| ConstantRole::Frame(_)
| ConstantRole::Item(_)
| ConstantRole::AlphaReference { .. }
| ConstantRole::Unknown { .. }
| ConstantRole::Default { .. } => false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum FrameTexture {
SceneDepth,
SceneColour,
ShadowMap,
ScreenSpaceShadows,
AmbientOcclusion,
ReflectionProbe,
LightProbeVolume,
DecalBuffer(u8),
}
impl FrameTexture {
pub fn dim(self) -> iridia_interface::TextureDim {
match self {
Self::ReflectionProbe => iridia_interface::TextureDim::Cube,
Self::LightProbeVolume => iridia_interface::TextureDim::D3,
Self::SceneDepth
| Self::SceneColour
| Self::ShadowMap
| Self::ScreenSpaceShadows
| Self::AmbientOcclusion
| Self::DecalBuffer(_) => iridia_interface::TextureDim::D2,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct BufferBinding {
pub resource: ResourceId,
pub source: BufferSource,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BufferSource {
Table(BufferTable),
Unknown {
why: String,
},
Graph(crate::render_graph::BufferId),
Lights(super::LightTable),
LightLists(super::LightListPart),
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum BufferTable {
ObjectMatrices {
rows: u8,
},
HeaderedMatrices {
rows: u8,
},
SkinnedMatrices {
rows: u8,
joint_stride: u8,
prefix: u8,
first: SkinPrefix,
},
Instances {
stride: u8,
matrix_start: u8,
matrices: ResourceId,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum SkinPrefix {
Translation,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum Occluded {
AllLight,
NaturalAmbient,
ArtificialAmbient,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum OutputRole {
Albedo,
Normal {
encoding: NormalEncoding,
space: NormalSpace,
},
Gloss {
encoding: ScalarEncoding,
},
Roughness,
Metallic,
Occlusion {
of: Occluded,
encoding: ScalarEncoding,
},
SpecularOcclusion,
Translucency,
Reflectance {
encoding: ScalarEncoding,
},
SpecularIntensity {
encoding: ScalarEncoding,
},
SpecularExponent {
encoding: ScalarEncoding,
},
Fresnel,
MotionVector,
Emission {
encoding: ScalarEncoding,
},
Colour,
Coverage,
Opacity,
Unknown {
why: String,
},
}
impl OutputRole {
pub fn unknown(why: impl Into<String>) -> OutputRole {
OutputRole::Unknown { why: why.into() }
}
pub fn scalar_encoding(&self) -> Option<ScalarEncoding> {
match *self {
OutputRole::Gloss { encoding }
| OutputRole::Occlusion { encoding, .. }
| OutputRole::Reflectance { encoding }
| OutputRole::SpecularIntensity { encoding }
| OutputRole::SpecularExponent { encoding }
| OutputRole::Emission { encoding } => Some(encoding),
_ => None,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct OutputBinding {
pub label: Label,
pub lanes: u8,
pub role: OutputRole,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct InputBinding {
pub label: Label,
pub role: InputRole,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub struct VariantKey {
pub field: String,
pub value: u32,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub parts: Vec<KeyPart>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub struct KeyPart {
pub name: String,
pub value: u64,
}
pub fn state_lines(
constants: &mut [ConstantBinding],
mut lines_at: impl FnMut(&ConstantBinding) -> MatrixLines,
) -> usize {
let mut unshown = 0usize;
for c in constants.iter_mut().filter(|c| c.role.is_matrix()) {
let lines = lines_at(c);
if matches!(lines, MatrixLines::Unshown(_)) {
unshown = unshown.saturating_add(1);
}
c.lines = Some(lines);
}
unshown
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct OutputUses {
pub coverage: u32,
pub opacity: u32,
}
pub fn bind_output_uses(outputs: &mut Vec<OutputBinding>, uses: &[OutputUse]) -> OutputUses {
let mut counts = OutputUses::default();
let mut bound = Vec::with_capacity(outputs.len());
for o in outputs.drain(..) {
let OutputRole::Unknown { why } = &o.role else {
bound.push(o);
continue;
};
let Some(seen) = uses.iter().find(|u| u.label == o.label) else {
bound.push(o);
continue;
};
let opacity = o.lanes & seen.blend_weight;
let coverage = o.lanes & seen.discard_tested & !opacity;
let rest = o.lanes & !(opacity | coverage);
if rest != 0 {
bound.push(OutputBinding {
label: o.label.clone(),
lanes: rest,
role: OutputRole::unknown(why.clone()),
});
}
for (lanes, role) in [(coverage, OutputRole::Coverage), (opacity, OutputRole::Opacity)] {
if lanes != 0 {
bound.push(OutputBinding {
label: o.label.clone(),
lanes,
role,
});
}
}
counts.coverage = counts.coverage.saturating_add(coverage.count_ones());
counts.opacity = counts.opacity.saturating_add(opacity.count_ones());
}
*outputs = bound;
counts
}
#[cfg(test)]
#[allow(clippy::unwrap_used, reason = "hand-worked values")]
mod tests {
use super::*;
#[test]
fn a_camera_relative_view_projection_is_not_the_plain_one() {
assert_ne!(
ConstantRole::View(ViewParameter::CameraRelativeViewProjection),
ConstantRole::View(ViewParameter::ViewProjection)
);
assert_eq!(
serde_json::to_value(ViewParameter::CameraRelativeViewProjection).unwrap(),
serde_json::json!("camera_relative_view_projection")
);
}
fn target(n: u32) -> Label {
Label {
name: "SV_Target".into(),
index: n,
}
}
fn seen(n: u32, blend_weight: u8, discard_tested: u8) -> OutputUse {
OutputUse {
label: target(n),
target: n,
blend_weight,
discard_tested,
}
}
#[test]
fn the_uses_seen_bind_the_unknown_lanes() {
let unknown = |n: u32, lanes: u8| OutputBinding {
label: target(n),
lanes,
role: OutputRole::unknown(format!("target {n}: no role")),
};
let mut outputs = vec![
OutputBinding {
label: target(0),
lanes: 0b0111,
role: OutputRole::Albedo,
},
unknown(0, 0b1000),
unknown(1, 0b1111),
unknown(2, 0b1000),
unknown(3, 0b1111),
];
let uses = [
seen(0, 0, 0b1111),
seen(1, 0b1000, 0b1001),
seen(2, 0, 0),
];
let counts = bind_output_uses(&mut outputs, &uses);
let roles: Vec<_> = outputs
.iter()
.map(|o| (o.label.index, o.lanes, o.role.clone()))
.collect();
assert_eq!(
roles,
[
(0, 0b0111, OutputRole::Albedo),
(0, 0b1000, OutputRole::Coverage),
(1, 0b0110, OutputRole::unknown("target 1: no role")),
(1, 0b0001, OutputRole::Coverage),
(1, 0b1000, OutputRole::Opacity),
(2, 0b1000, OutputRole::unknown("target 2: no role")),
(3, 0b1111, OutputRole::unknown("target 3: no role")),
]
);
assert_eq!(
counts,
OutputUses {
coverage: 2,
opacity: 1
}
);
}
#[test]
fn the_lines_of_each_matrix_role_are_stated() {
let constant = |offset: u32, role: ConstantRole| ConstantBinding {
resource: ResourceId(0),
offset,
size: 64,
name: None,
role,
lines: None,
};
let mut constants = vec![
constant(0, ConstantRole::View(ViewParameter::ViewProjection)),
constant(64, ConstantRole::Object(ObjectParameter::BonePalette)),
constant(128, ConstantRole::View(ViewParameter::CameraPosition)),
constant(144, ConstantRole::View(ViewParameter::InverseView)),
];
let unshown = state_lines(&mut constants, |c| match c.offset {
0 => MatrixLines::Columns,
64 => MatrixLines::Rows,
_ => MatrixLines::Unshown(iridia_interface::Unshown::NotApplied),
});
assert_eq!(unshown, 1);
let lines: Vec<_> = constants.iter().map(|c| c.lines.clone()).collect();
assert_eq!(
lines,
[
Some(MatrixLines::Columns),
Some(MatrixLines::Rows),
None,
Some(MatrixLines::Unshown(iridia_interface::Unshown::NotApplied)),
]
);
}
}