use serde::Serialize;
use super::{ScalarType, Value};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub struct ResourceId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TextureDim {
D1,
D2,
D3,
Cube,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SampleType {
Float,
Sint,
Uint,
Depth,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BufferKind {
Typed(Value),
Structured {
stride: u32,
},
Raw,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum StorageTarget {
Image {
dim: TextureDim,
arrayed: bool,
sample: SampleType,
},
Buffer(BufferKind),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Access {
Read,
Write,
ReadWrite,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Majorness {
Row,
Column,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ConstantType {
Scalar(ScalarType),
Vector(Value),
Matrix {
scalar: ScalarType,
rows: u8,
columns: u8,
major: Majorness,
stride: u32,
},
Array {
element: Box<ConstantType>,
count: u32,
stride: u32,
},
Struct {
members: Vec<Member>,
},
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub struct Member {
pub name: String,
pub offset: u32,
pub ty: ConstantType,
}
impl ConstantType {
pub fn size(&self) -> Option<u32> {
match self {
Self::Scalar(s) => Some(s.bytes()),
Self::Vector(v) => u32::from(v.width).checked_mul(v.scalar.bytes()),
Self::Matrix {
scalar,
rows,
columns,
major,
stride,
} => {
let (lines, lanes) = match major {
Majorness::Row => (*rows, *columns),
Majorness::Column => (*columns, *rows),
};
if lines == 0 || lanes == 0 {
return Some(0);
}
u32::from(lines.saturating_sub(1))
.checked_mul(*stride)?
.checked_add(u32::from(lanes).checked_mul(scalar.bytes())?)
}
Self::Array {
element,
count,
stride,
} => match count.checked_sub(1) {
None => Some(0),
Some(before) => before.checked_mul(*stride)?.checked_add(element.size()?),
},
Self::Struct { members } => members
.iter()
.try_fold(0u32, |end, m| Some(end.max(m.offset.checked_add(m.ty.size()?)?))),
}
}
pub fn members(&self) -> &[Member] {
match self {
Self::Struct { members } => members,
Self::Array { element, .. } => element.members(),
_ => &[],
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub struct Constant {
pub name: String,
pub offset: u32,
pub ty: ConstantType,
pub default: Option<Vec<u8>>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub struct BufferLayout {
pub size: u32,
pub constants: Vec<Constant>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ResourceKind {
ConstantBuffer {
layout: BufferLayout,
},
Texture {
dim: TextureDim,
arrayed: bool,
multisampled: bool,
sample: SampleType,
},
Sampler {
comparison: bool,
},
Buffer(BufferKind),
Storage {
target: StorageTarget,
access: Access,
},
AccelerationStructure,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum D3dClass {
ConstantBuffer,
ShaderResource,
Sampler,
UnorderedAccess,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub struct D3dBinding {
pub class: D3dClass,
pub register: u32,
pub space: u32,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub struct ResourceProvenance {
pub name: String,
pub d3d: Option<D3dBinding>,
#[serde(skip_serializing_if = "Option::is_none")]
pub counter_of: Option<ResourceId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub layout: Option<BufferLayout>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub struct Resource {
pub id: ResourceId,
pub kind: ResourceKind,
pub count: u32,
pub provenance: ResourceProvenance,
}
impl Resource {
pub const UNBOUNDED: u32 = u32::MAX;
pub fn is_unbounded(&self) -> bool {
self.count == Self::UNBOUNDED
}
pub fn is_array(&self) -> bool {
self.count != 1
}
}
#[cfg(test)]
mod tests {
use super::*;
fn float(width: u8) -> ConstantType {
ConstantType::Vector(Value {
scalar: ScalarType::Float,
width,
})
}
#[test]
fn sizes_end_at_the_last_byte_of_data() {
assert_eq!(ConstantType::Scalar(ScalarType::Double).size(), Some(8));
assert_eq!(float(3).size(), Some(12));
let matrix = |rows, columns, major| ConstantType::Matrix {
scalar: ScalarType::Float,
rows,
columns,
major,
stride: 16,
};
assert_eq!(matrix(3, 3, Majorness::Row).size(), Some(44));
assert_eq!(matrix(4, 3, Majorness::Column).size(), Some(48));
let offsets = ConstantType::Array {
element: Box::new(float(2)),
count: 3,
stride: 16,
};
assert_eq!(offsets.size(), Some(40));
let empty = ConstantType::Array {
element: Box::new(float(2)),
count: 0,
stride: 16,
};
assert_eq!(empty.size(), Some(0));
assert_eq!(matrix(0, 3, Majorness::Row).size(), Some(0));
assert_eq!(matrix(3, 0, Majorness::Row).size(), Some(0));
let record = ConstantType::Struct {
members: vec![
Member {
name: "a".into(),
offset: 0,
ty: float(3),
},
Member {
name: "b".into(),
offset: 12,
ty: ConstantType::Scalar(ScalarType::Float),
},
Member {
name: "c".into(),
offset: 16,
ty: offsets,
},
],
};
assert_eq!(record.size(), Some(56));
let overflow = ConstantType::Array {
element: Box::new(float(4)),
count: u32::MAX,
stride: 16,
};
assert_eq!(overflow.size(), None);
}
#[test]
fn members_are_found_through_arrays_of_structures() {
let inner = ConstantType::Struct {
members: vec![Member {
name: "x".into(),
offset: 0,
ty: float(1),
}],
};
let array = ConstantType::Array {
element: Box::new(inner),
count: 2,
stride: 16,
};
assert_eq!(array.members().len(), 1);
assert!(float(4).members().is_empty());
}
#[test]
fn a_resource_serializes_by_kind_with_its_provenance() {
let r = Resource {
id: ResourceId(2),
kind: ResourceKind::Texture {
dim: TextureDim::D2,
arrayed: false,
multisampled: false,
sample: SampleType::Float,
},
count: 1,
provenance: ResourceProvenance {
name: "Texture2D_8".into(),
d3d: Some(D3dBinding {
class: D3dClass::ShaderResource,
register: 8,
space: 0,
}),
counter_of: None,
layout: None,
},
};
assert_eq!(
serde_json::to_value(&r).unwrap(),
serde_json::json!({
"id": 2,
"kind": {"texture": {"dim": "d2", "arrayed": false, "multisampled": false, "sample": "float"}},
"count": 1,
"provenance": {
"name": "Texture2D_8",
"d3d": {"class": "shader_resource", "register": 8, "space": 0},
},
})
);
}
}