use crate::meta::{
texture_shape_from_name, AirType, FragMeta, FragRole, FunctionConstant, KernMeta, KernRole,
TextureComponent, TextureDimension, TextureShape, VertMeta, VertOutRole, VertRole,
};
pub const REFLECTION_VERSION: u32 = 2;
pub const RESOURCE_DESCRIPTOR_SET: u32 = 0;
pub const BUFFER_BINDING_BASE: u32 = 0;
pub const TEXTURE_BINDING_BASE: u32 = 32;
pub const SAMPLER_BINDING_BASE: u32 = 64;
pub const COLOR_INPUT_BINDING_BASE: u32 = 96;
pub const ADDRESS_SPACE_CONSTANT: u32 = 2;
pub const ADDRESS_SPACE_THREADGROUP: u32 = 3;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ShaderStage {
Vertex,
Fragment,
Kernel,
}
impl From<crate::passes::Stage> for ShaderStage {
fn from(stage: crate::passes::Stage) -> Self {
match stage {
crate::passes::Stage::Vertex => ShaderStage::Vertex,
crate::passes::Stage::Fragment => ShaderStage::Fragment,
crate::passes::Stage::Kernel => ShaderStage::Kernel,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ResourceKind {
Buffer,
ThreadgroupBuffer,
Texture,
TextureArray,
StorageImage,
Sampler,
ColorInput,
AccelerationStructureShadow,
EmbeddedArgBufferTexture,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DescriptorLocation {
pub set: u32,
pub binding: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ResourceAccess {
ReadOnly,
ReadWrite,
Sampled,
Storage,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EmbeddedArgBuffer {
pub buffer_index: u32,
pub field_offset: u32,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ResourceBinding {
pub kind: ResourceKind,
pub metal_index: u32,
pub descriptor: Option<DescriptorLocation>,
pub param_index: Option<u32>,
pub address_space: Option<u32>,
pub declared_size: Option<u32>,
pub type_layout: Option<AirType>,
pub type_name: Option<String>,
pub texture_shape: Option<TextureShape>,
pub embedded_source: Option<EmbeddedArgBuffer>,
pub access: Option<ResourceAccess>,
}
impl ResourceBinding {
fn descriptor_at(base: u32, metal_index: u32) -> Option<DescriptorLocation> {
Some(DescriptorLocation {
set: RESOURCE_DESCRIPTOR_SET,
binding: base.saturating_add(metal_index),
})
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VertexAttribute {
pub location: u32,
pub type_name: Option<String>,
pub name: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Varying {
pub location: u32,
pub type_name: Option<String>,
pub name: Option<String>,
pub user_semantic: Option<String>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VertexBuiltins {
pub uses_vertex_index: bool,
pub uses_instance_index: bool,
pub writes_position: bool,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RenderTarget {
pub member_index: u32,
pub location: u32,
pub type_name: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ImageblockLayout {
pub param_index: u32,
pub type_layout: AirType,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ShaderReflection {
pub reflection_version: u32,
pub stage: ShaderStage,
pub entry_point: Option<String>,
pub bindings: Vec<ResourceBinding>,
pub vertex_attributes: Vec<VertexAttribute>,
pub varyings: Vec<Varying>,
pub render_targets: Vec<RenderTarget>,
pub depth_members: Vec<u32>,
pub stencil_members: Vec<u32>,
pub local_size: Option<[u32; 3]>,
pub vertex_builtins: Option<VertexBuiltins>,
pub imageblock_layouts: Vec<ImageblockLayout>,
pub datalayout: Option<String>,
pub function_constants: Vec<FunctionConstant>,
}
impl ShaderReflection {
pub fn from_fragment(meta: &FragMeta, entry_point: Option<&str>) -> Self {
let mut bindings = Vec::new();
for (idx, role) in &meta.roles {
let idx = *idx;
let binding = match role {
FragRole::Buffer(n) => ResourceBinding {
kind: ResourceKind::Buffer,
metal_index: *n,
descriptor: ResourceBinding::descriptor_at(BUFFER_BINDING_BASE, *n),
param_index: Some(idx),
address_space: meta.buffer_address_spaces.get(&idx).copied(),
declared_size: meta.buffer_type_sizes.get(&idx).copied(),
type_layout: meta.buffer_layouts.get(&idx).cloned(),
type_name: None,
texture_shape: None,
embedded_source: None,
access: None,
},
FragRole::Texture(n) => texture_binding(*n, Some(idx), &meta.texture_type_names),
FragRole::Sampler(n) => sampler_binding(*n, Some(idx)),
FragRole::ColorInput(n) => ResourceBinding {
kind: ResourceKind::ColorInput,
metal_index: *n,
descriptor: ResourceBinding::descriptor_at(COLOR_INPUT_BINDING_BASE, *n),
param_index: Some(idx),
address_space: None,
declared_size: None,
type_layout: None,
type_name: None,
texture_shape: None,
embedded_source: None,
access: None,
},
FragRole::Position | FragRole::Varying(_) | FragRole::Other => continue,
};
bindings.push(binding);
}
let varyings = meta
.varying_types
.keys()
.chain(meta.varying_names.keys())
.chain(meta.varying_user_semantics.keys())
.copied()
.collect::<std::collections::BTreeSet<_>>()
.into_iter()
.map(|loc| Varying {
location: loc,
type_name: meta.varying_types.get(&loc).cloned(),
name: meta.varying_names.get(&loc).cloned(),
user_semantic: meta.varying_user_semantics.get(&loc).cloned(),
})
.collect();
let render_targets = meta
.render_target_members
.iter()
.map(|(member, location)| RenderTarget {
member_index: *member,
location: *location,
type_name: meta.render_target_type_names.get(member).cloned(),
})
.collect();
ShaderReflection {
reflection_version: REFLECTION_VERSION,
stage: ShaderStage::Fragment,
entry_point: entry_point.map(str::to_string),
bindings,
vertex_attributes: Vec::new(),
varyings,
render_targets,
depth_members: meta.depth_members.clone(),
stencil_members: meta.stencil_members.clone(),
local_size: None,
vertex_builtins: None,
imageblock_layouts: Vec::new(),
datalayout: None,
function_constants: Vec::new(),
}
}
pub fn from_vertex(meta: &VertMeta, entry_point: Option<&str>) -> Self {
let vertex_builtins = VertexBuiltins {
uses_vertex_index: meta.roles.iter().any(|(_, r)| *r == VertRole::VertexId),
uses_instance_index: meta.roles.iter().any(|(_, r)| *r == VertRole::InstanceId),
writes_position: meta.output_roles.contains(&VertOutRole::Position),
};
let mut bindings = Vec::new();
for (idx, role) in &meta.roles {
let idx = *idx;
let binding = match role {
VertRole::Buffer(n) => ResourceBinding {
kind: ResourceKind::Buffer,
metal_index: *n,
descriptor: ResourceBinding::descriptor_at(BUFFER_BINDING_BASE, *n),
param_index: Some(idx),
address_space: meta.buffer_address_spaces.get(&idx).copied(),
declared_size: meta.buffer_type_sizes.get(&idx).copied(),
type_layout: meta.buffer_layouts.get(&idx).cloned(),
type_name: None,
texture_shape: None,
embedded_source: None,
access: None,
},
VertRole::Texture(n) => texture_binding(*n, Some(idx), &meta.texture_type_names),
VertRole::Sampler(n) => sampler_binding(*n, Some(idx)),
VertRole::VertexInput(_)
| VertRole::VertexId
| VertRole::InstanceId
| VertRole::Other => continue,
};
bindings.push(binding);
}
let vertex_attributes = meta
.vertex_input_types
.keys()
.chain(meta.vertex_input_names.keys())
.copied()
.collect::<std::collections::BTreeSet<_>>()
.into_iter()
.map(|loc| VertexAttribute {
location: loc,
type_name: meta.vertex_input_types.get(&loc).cloned(),
name: meta.vertex_input_names.get(&loc).cloned(),
})
.collect();
let varyings = meta
.output_roles
.iter()
.filter_map(|role| match role {
VertOutRole::Varying(loc) => Some(Varying {
location: *loc,
type_name: None,
name: None,
user_semantic: None,
}),
_ => None,
})
.collect();
ShaderReflection {
reflection_version: REFLECTION_VERSION,
stage: ShaderStage::Vertex,
entry_point: entry_point.map(str::to_string),
bindings,
vertex_attributes,
varyings,
render_targets: Vec::new(),
depth_members: Vec::new(),
stencil_members: Vec::new(),
local_size: None,
vertex_builtins: Some(vertex_builtins),
imageblock_layouts: Vec::new(),
datalayout: None,
function_constants: Vec::new(),
}
}
pub fn from_kernel(meta: &KernMeta, entry_point: Option<&str>, local_size: [u32; 3]) -> Self {
let mut bindings = Vec::new();
for (idx, role) in &meta.roles {
let idx = *idx;
let binding = match role {
KernRole::Buffer(n) => {
let address_space = meta.buffer_address_spaces.get(&idx).copied();
let threadgroup = address_space == Some(ADDRESS_SPACE_THREADGROUP);
let kind = if threadgroup {
ResourceKind::ThreadgroupBuffer
} else {
ResourceKind::Buffer
};
ResourceBinding {
kind,
metal_index: *n,
descriptor: if threadgroup {
None
} else {
ResourceBinding::descriptor_at(BUFFER_BINDING_BASE, *n)
},
param_index: Some(idx),
address_space,
declared_size: meta.buffer_type_sizes.get(&idx).copied(),
type_layout: meta.buffer_layouts.get(&idx).cloned(),
type_name: meta.buffer_type_names.get(&idx).cloned(),
texture_shape: None,
embedded_source: None,
access: buffer_access_from_address_space(address_space),
}
}
KernRole::Texture(n) => texture_binding(*n, Some(idx), &meta.texture_type_names),
KernRole::Sampler(n) => sampler_binding(*n, Some(idx)),
KernRole::AccelerationStructureShadow(n) => ResourceBinding {
kind: ResourceKind::AccelerationStructureShadow,
metal_index: *n,
descriptor: ResourceBinding::descriptor_at(BUFFER_BINDING_BASE, *n),
param_index: Some(idx),
address_space: None,
declared_size: None,
type_layout: None,
type_name: None,
texture_shape: None,
embedded_source: None,
access: None,
},
_ => continue,
};
bindings.push(binding);
}
for embedded in &meta.embedded_textures {
bindings.push(ResourceBinding {
kind: ResourceKind::EmbeddedArgBufferTexture,
metal_index: embedded.synthetic_texture_index,
descriptor: ResourceBinding::descriptor_at(
TEXTURE_BINDING_BASE,
embedded.synthetic_texture_index,
),
param_index: None,
address_space: None,
declared_size: None,
type_layout: None,
type_name: None,
texture_shape: Some(TextureShape {
dimension: TextureDimension::from_spirv_dim(embedded.dim),
arrayed: false,
multisampled: false,
component: TextureComponent::from_image_comp(embedded.comp),
writable: false,
array_ref: false,
storage_format: None,
}),
embedded_source: Some(EmbeddedArgBuffer {
buffer_index: embedded.buffer_index,
field_offset: embedded.field_offset,
}),
access: Some(ResourceAccess::Sampled),
});
}
ShaderReflection {
reflection_version: REFLECTION_VERSION,
stage: ShaderStage::Kernel,
entry_point: entry_point.map(str::to_string),
bindings,
vertex_attributes: Vec::new(),
varyings: Vec::new(),
render_targets: Vec::new(),
depth_members: Vec::new(),
stencil_members: Vec::new(),
local_size: Some(local_size),
vertex_builtins: None,
imageblock_layouts: {
let mut ibs: Vec<ImageblockLayout> = meta
.imageblock_layouts
.iter()
.map(|(idx, ty)| ImageblockLayout {
param_index: *idx,
type_layout: ty.clone(),
})
.collect();
ibs.sort_by_key(|ib| ib.param_index);
ibs
},
datalayout: None,
function_constants: Vec::new(),
}
}
pub fn binding_at(&self, kind: ResourceKind, metal_index: u32) -> Option<&ResourceBinding> {
self.bindings
.iter()
.find(|b| b.kind == kind && b.metal_index == metal_index)
}
}
fn texture_binding(
n: u32,
param_index: Option<u32>,
type_names: &std::collections::HashMap<u32, String>,
) -> ResourceBinding {
let type_name = param_index.and_then(|idx| type_names.get(&idx).cloned());
let texture_shape = type_name.as_deref().map(texture_shape_from_name);
let (kind, access) = classify_texture(texture_shape.as_ref());
ResourceBinding {
kind,
metal_index: n,
descriptor: ResourceBinding::descriptor_at(TEXTURE_BINDING_BASE, n),
param_index,
address_space: None,
declared_size: None,
type_layout: None,
type_name,
texture_shape,
embedded_source: None,
access: Some(access),
}
}
fn classify_texture(shape: Option<&TextureShape>) -> (ResourceKind, ResourceAccess) {
let Some(shape) = shape else {
return (ResourceKind::Texture, ResourceAccess::Sampled);
};
if shape.array_ref {
(ResourceKind::TextureArray, ResourceAccess::Sampled)
} else if shape.writable {
(ResourceKind::StorageImage, ResourceAccess::Storage)
} else {
(ResourceKind::Texture, ResourceAccess::Sampled)
}
}
fn buffer_access_from_address_space(address_space: Option<u32>) -> Option<ResourceAccess> {
match address_space {
Some(ADDRESS_SPACE_CONSTANT) => Some(ResourceAccess::ReadOnly),
_ => None,
}
}
fn sampler_binding(n: u32, param_index: Option<u32>) -> ResourceBinding {
ResourceBinding {
kind: ResourceKind::Sampler,
metal_index: n,
descriptor: ResourceBinding::descriptor_at(SAMPLER_BINDING_BASE, n),
param_index,
address_space: None,
declared_size: None,
type_layout: None,
type_name: None,
texture_shape: None,
embedded_source: None,
access: None,
}
}
#[cfg(test)]
mod tests;