use crate::{binding_model::BindEntryMap, FastHashMap};
use std::collections::hash_map::Entry;
use thiserror::Error;
use wgt::{BindGroupLayoutEntry, BindingType};
#[derive(Debug)]
enum ResourceType {
Buffer {
size: wgt::BufferSize,
},
Texture {
dim: naga::ImageDimension,
arrayed: bool,
class: naga::ImageClass,
},
Sampler {
comparison: bool,
},
}
#[derive(Debug)]
struct Resource {
group: u32,
binding: u32,
ty: ResourceType,
class: naga::StorageClass,
}
#[derive(Clone, Copy, Debug)]
enum NumericDimension {
Scalar,
Vector(naga::VectorSize),
Matrix(naga::VectorSize, naga::VectorSize),
}
#[derive(Clone, Copy, Debug)]
pub struct NumericType {
dim: NumericDimension,
kind: naga::ScalarKind,
width: naga::Bytes,
}
#[derive(Debug)]
enum Varying {
Local { location: u32, ty: NumericType },
BuiltIn(naga::BuiltIn),
}
#[derive(Debug)]
struct SpecializationConstant {
id: u32,
ty: NumericType,
}
#[derive(Debug, Default)]
struct EntryPoint {
inputs: Vec<Varying>,
outputs: Vec<Varying>,
resources: Vec<(naga::Handle<Resource>, naga::GlobalUse)>,
spec_constants: Vec<SpecializationConstant>,
}
#[derive(Debug)]
pub struct Interface {
resources: naga::Arena<Resource>,
entry_points: FastHashMap<(naga::ShaderStage, String), EntryPoint>,
}
#[derive(Clone, Debug, Error)]
#[error("buffer usage is {actual:?} which does not contain required usage {expected:?}")]
pub struct MissingBufferUsageError {
pub(crate) actual: wgt::BufferUsage,
pub(crate) expected: wgt::BufferUsage,
}
pub fn check_buffer_usage(
actual: wgt::BufferUsage,
expected: wgt::BufferUsage,
) -> Result<(), MissingBufferUsageError> {
if !actual.contains(expected) {
Err(MissingBufferUsageError { actual, expected })
} else {
Ok(())
}
}
#[derive(Clone, Debug, Error)]
#[error("texture usage is {actual:?} which does not contain required usage {expected:?}")]
pub struct MissingTextureUsageError {
pub(crate) actual: wgt::TextureUsage,
pub(crate) expected: wgt::TextureUsage,
}
pub fn check_texture_usage(
actual: wgt::TextureUsage,
expected: wgt::TextureUsage,
) -> Result<(), MissingTextureUsageError> {
if !actual.contains(expected) {
Err(MissingTextureUsageError { actual, expected })
} else {
Ok(())
}
}
#[derive(Clone, Debug, Error)]
pub enum BindingError {
#[error("binding is missing from the pipeline layout")]
Missing,
#[error("visibility flags don't include the shader stage")]
Invisible,
#[error("load/store access flags {0:?} don't match the shader")]
WrongUsage(naga::GlobalUse),
#[error("type on the shader side does not match the pipeline binding")]
WrongType,
#[error("buffer structure size {0}, added to one element of an unbound array, if it's the last field, ended up greater than the given `min_binding_size`")]
WrongBufferSize(wgt::BufferSize),
#[error("view dimension {dim:?} (is array: {is_array}) doesn't match the shader")]
WrongTextureViewDimension {
dim: naga::ImageDimension,
is_array: bool,
},
#[error("texture class {binding:?} doesn't match the shader {shader:?}")]
WrongTextureClass {
binding: naga::ImageClass,
shader: naga::ImageClass,
},
#[error("comparison flag doesn't match the shader")]
WrongSamplerComparison,
#[error("derived bind group layout type is not consistent between stages")]
InconsistentlyDerivedType,
#[error("texture format {0:?} is not supported for storage use")]
BadStorageFormat(wgt::TextureFormat),
}
#[derive(Clone, Debug, Error)]
pub enum InputError {
#[error("input is not provided by the earlier stage in the pipeline")]
Missing,
#[error("input type is not compatible with the provided")]
WrongType,
}
#[derive(Clone, Debug, Error)]
pub enum StageError {
#[error("shader module is invalid")]
InvalidModule,
#[error("unable to find an entry point at {0:?} stage")]
MissingEntryPoint(String),
#[error("error matching global binding at index {binding} in group {group} against the pipeline layout: {error}")]
Binding {
group: u32,
binding: u32,
error: BindingError,
},
#[error(
"error matching the stage input at {location} against the previous stage outputs: {error}"
)]
Input {
location: wgt::ShaderLocation,
error: InputError,
},
}
fn get_aligned_type_size(
module: &naga::Module,
handle: naga::Handle<naga::Type>,
allow_unbound: bool,
) -> wgt::BufferAddress {
use naga::TypeInner as Ti;
match module.types[handle].inner {
Ti::Scalar { kind: _, width } => width as wgt::BufferAddress,
Ti::Vector {
size,
kind: _,
width,
} => size as wgt::BufferAddress * width as wgt::BufferAddress,
Ti::Matrix {
rows,
columns,
width,
} => {
rows as wgt::BufferAddress * columns as wgt::BufferAddress * width as wgt::BufferAddress
}
Ti::Pointer { .. } => 4,
Ti::Array {
base,
size: naga::ArraySize::Constant(const_handle),
stride,
} => {
let base_size = match stride {
Some(stride) => stride.get() as wgt::BufferAddress,
None => get_aligned_type_size(module, base, false),
};
let count = match module.constants[const_handle].inner {
naga::ConstantInner::Scalar {
value: naga::ScalarValue::Uint(value),
width: _,
} => value,
ref other => panic!("Invalid array size constant: {:?}", other),
};
base_size * count
}
Ti::Array {
base,
size: naga::ArraySize::Dynamic,
stride,
} if allow_unbound => match stride {
Some(stride) => stride.get() as wgt::BufferAddress,
None => get_aligned_type_size(module, base, false),
},
Ti::Struct {
block: _,
ref members,
} => {
let mut offset = 0;
for member in members {
offset += match member.span {
Some(span) => span.get() as wgt::BufferAddress,
None => get_aligned_type_size(module, member.ty, false),
}
}
offset
}
_ => panic!("Unexpected struct field"),
}
}
fn map_storage_format_to_naga(format: wgt::TextureFormat) -> Option<naga::StorageFormat> {
use naga::StorageFormat as Sf;
use wgt::TextureFormat as Tf;
Some(match format {
Tf::R32Uint => Sf::R32Uint,
Tf::R32Sint => Sf::R32Sint,
Tf::R32Float => Sf::R32Float,
Tf::Rgba8Unorm => Sf::Rgba8Unorm,
Tf::Rgba8Snorm => Sf::Rgba8Snorm,
Tf::Rgba8Uint => Sf::Rgba8Uint,
Tf::Rgba8Sint => Sf::Rgba8Sint,
Tf::Rg32Uint => Sf::Rg32Uint,
Tf::Rg32Sint => Sf::Rg32Sint,
Tf::Rg32Float => Sf::Rg32Float,
Tf::Rgba16Uint => Sf::Rgba16Uint,
Tf::Rgba16Sint => Sf::Rgba16Sint,
Tf::Rgba16Float => Sf::Rgba16Float,
Tf::Rgba32Uint => Sf::Rgba32Uint,
Tf::Rgba32Sint => Sf::Rgba32Sint,
Tf::Rgba32Float => Sf::Rgba32Float,
_ => return None,
})
}
fn map_storage_format_from_naga(format: naga::StorageFormat) -> wgt::TextureFormat {
use naga::StorageFormat as Sf;
use wgt::TextureFormat as Tf;
match format {
Sf::R8Unorm => Tf::R8Unorm,
Sf::R8Snorm => Tf::R8Snorm,
Sf::R8Uint => Tf::R8Uint,
Sf::R8Sint => Tf::R8Sint,
Sf::R16Uint => Tf::R16Uint,
Sf::R16Sint => Tf::R16Sint,
Sf::R16Float => Tf::R16Float,
Sf::Rg8Unorm => Tf::Rg8Unorm,
Sf::Rg8Snorm => Tf::Rg8Snorm,
Sf::Rg8Uint => Tf::Rg8Uint,
Sf::Rg8Sint => Tf::Rg8Sint,
Sf::R32Uint => Tf::R32Uint,
Sf::R32Sint => Tf::R32Sint,
Sf::R32Float => Tf::R32Float,
Sf::Rg16Uint => Tf::Rg16Uint,
Sf::Rg16Sint => Tf::Rg16Sint,
Sf::Rg16Float => Tf::Rg16Float,
Sf::Rgba8Unorm => Tf::Rgba8Unorm,
Sf::Rgba8Snorm => Tf::Rgba8Snorm,
Sf::Rgba8Uint => Tf::Rgba8Uint,
Sf::Rgba8Sint => Tf::Rgba8Sint,
Sf::Rgb10a2Unorm => Tf::Rgb10a2Unorm,
Sf::Rg11b10Float => Tf::Rg11b10Float,
Sf::Rg32Uint => Tf::Rg32Uint,
Sf::Rg32Sint => Tf::Rg32Sint,
Sf::Rg32Float => Tf::Rg32Float,
Sf::Rgba16Uint => Tf::Rgba16Uint,
Sf::Rgba16Sint => Tf::Rgba16Sint,
Sf::Rgba16Float => Tf::Rgba16Float,
Sf::Rgba32Uint => Tf::Rgba32Uint,
Sf::Rgba32Sint => Tf::Rgba32Sint,
Sf::Rgba32Float => Tf::Rgba32Float,
}
}
impl Resource {
fn check_binding_use(
&self,
entry: &BindGroupLayoutEntry,
shader_usage: naga::GlobalUse,
) -> Result<(), BindingError> {
let allowed_usage = match self.ty {
ResourceType::Buffer { size } => {
let (allowed_usage, min_size) = match entry.ty {
BindingType::Buffer {
ty,
has_dynamic_offset: _,
min_binding_size,
} => {
let global_use = match ty {
wgt::BufferBindingType::Uniform
| wgt::BufferBindingType::Storage { read_only: true } => {
naga::GlobalUse::READ
}
wgt::BufferBindingType::Storage { read_only: _ } => {
naga::GlobalUse::all()
}
};
(global_use, min_binding_size)
}
_ => return Err(BindingError::WrongType),
};
match min_size {
Some(non_zero) if non_zero < size => {
return Err(BindingError::WrongBufferSize(size))
}
_ => (),
}
allowed_usage
}
ResourceType::Sampler { comparison } => match entry.ty {
BindingType::Sampler {
filtering: _,
comparison: cmp,
} => {
if cmp == comparison {
naga::GlobalUse::READ
} else {
return Err(BindingError::WrongSamplerComparison);
}
}
_ => return Err(BindingError::WrongType),
},
ResourceType::Texture {
dim,
arrayed,
class,
} => {
let view_dimension = match entry.ty {
BindingType::Texture { view_dimension, .. }
| BindingType::StorageTexture { view_dimension, .. } => view_dimension,
_ => {
return Err(BindingError::WrongTextureViewDimension {
dim,
is_array: true,
})
}
};
if arrayed {
match (dim, view_dimension) {
(naga::ImageDimension::D2, wgt::TextureViewDimension::D2Array) => (),
(naga::ImageDimension::Cube, wgt::TextureViewDimension::CubeArray) => (),
_ => {
return Err(BindingError::WrongTextureViewDimension {
dim,
is_array: true,
})
}
}
} else {
match (dim, view_dimension) {
(naga::ImageDimension::D1, wgt::TextureViewDimension::D1) => (),
(naga::ImageDimension::D2, wgt::TextureViewDimension::D2) => (),
(naga::ImageDimension::D3, wgt::TextureViewDimension::D3) => (),
(naga::ImageDimension::Cube, wgt::TextureViewDimension::Cube) => (),
_ => {
return Err(BindingError::WrongTextureViewDimension {
dim,
is_array: false,
})
}
}
}
let (expected_class, usage) = match entry.ty {
BindingType::Texture {
sample_type,
view_dimension: _,
multisampled: multi,
} => {
let class = match sample_type {
wgt::TextureSampleType::Float { .. } => naga::ImageClass::Sampled {
kind: naga::ScalarKind::Float,
multi,
},
wgt::TextureSampleType::Sint => naga::ImageClass::Sampled {
kind: naga::ScalarKind::Sint,
multi,
},
wgt::TextureSampleType::Uint => naga::ImageClass::Sampled {
kind: naga::ScalarKind::Uint,
multi,
},
wgt::TextureSampleType::Depth => naga::ImageClass::Depth,
};
(class, naga::GlobalUse::READ)
}
BindingType::StorageTexture {
access,
format,
view_dimension: _,
} => {
let naga_format = map_storage_format_to_naga(format)
.ok_or(BindingError::BadStorageFormat(format))?;
let usage = match access {
wgt::StorageTextureAccess::ReadOnly => naga::GlobalUse::READ,
wgt::StorageTextureAccess::WriteOnly => naga::GlobalUse::WRITE,
wgt::StorageTextureAccess::ReadWrite => naga::GlobalUse::all(),
};
(naga::ImageClass::Storage(naga_format), usage)
}
_ => return Err(BindingError::WrongType),
};
if class != expected_class {
return Err(BindingError::WrongTextureClass {
binding: expected_class,
shader: class,
});
}
usage
}
};
if allowed_usage.contains(shader_usage) {
Ok(())
} else {
Err(BindingError::WrongUsage(shader_usage))
}
}
fn derive_binding_type(
&self,
shader_usage: naga::GlobalUse,
) -> Result<BindingType, BindingError> {
Ok(match self.ty {
ResourceType::Buffer { size } => BindingType::Buffer {
ty: match self.class {
naga::StorageClass::Uniform => wgt::BufferBindingType::Uniform,
naga::StorageClass::Storage => wgt::BufferBindingType::Storage {
read_only: !shader_usage.contains(naga::GlobalUse::WRITE),
},
_ => return Err(BindingError::WrongType),
},
has_dynamic_offset: false,
min_binding_size: Some(size),
},
ResourceType::Sampler { comparison } => BindingType::Sampler {
filtering: true,
comparison,
},
ResourceType::Texture {
dim,
arrayed,
class,
} => {
let view_dimension = match dim {
naga::ImageDimension::D1 => wgt::TextureViewDimension::D1,
naga::ImageDimension::D2 if arrayed => wgt::TextureViewDimension::D2Array,
naga::ImageDimension::D2 => wgt::TextureViewDimension::D2,
naga::ImageDimension::D3 => wgt::TextureViewDimension::D3,
naga::ImageDimension::Cube if arrayed => wgt::TextureViewDimension::CubeArray,
naga::ImageDimension::Cube => wgt::TextureViewDimension::Cube,
};
match class {
naga::ImageClass::Sampled { multi, kind } => BindingType::Texture {
sample_type: match kind {
naga::ScalarKind::Float => {
wgt::TextureSampleType::Float { filterable: true }
}
naga::ScalarKind::Sint => wgt::TextureSampleType::Sint,
naga::ScalarKind::Uint => wgt::TextureSampleType::Uint,
naga::ScalarKind::Bool => unreachable!(),
},
view_dimension,
multisampled: multi,
},
naga::ImageClass::Depth => BindingType::Texture {
sample_type: wgt::TextureSampleType::Depth,
view_dimension,
multisampled: false,
},
naga::ImageClass::Storage(format) => BindingType::StorageTexture {
access: if shader_usage.contains(naga::GlobalUse::WRITE) {
wgt::StorageTextureAccess::WriteOnly
} else {
wgt::StorageTextureAccess::ReadOnly
},
view_dimension,
format: {
let f = map_storage_format_from_naga(format);
let original = map_storage_format_to_naga(f)
.ok_or(BindingError::BadStorageFormat(f))?;
debug_assert_eq!(format, original);
f
},
},
}
}
})
}
}
impl NumericType {
pub fn from_vertex_format(format: wgt::VertexFormat) -> Self {
use naga::{ScalarKind as Sk, VectorSize as Vs};
use wgt::VertexFormat as Vf;
let (dim, kind, width) = match format {
Vf::Uchar2 | Vf::Ushort2 => (NumericDimension::Vector(Vs::Bi), Sk::Uint, 4),
Vf::Uchar4 | Vf::Ushort4 => (NumericDimension::Vector(Vs::Quad), Sk::Uint, 4),
Vf::Char2 | Vf::Short2 => (NumericDimension::Vector(Vs::Bi), Sk::Sint, 4),
Vf::Char4 | Vf::Short4 => (NumericDimension::Vector(Vs::Quad), Sk::Sint, 4),
Vf::Uchar2Norm | Vf::Char2Norm | Vf::Ushort2Norm | Vf::Short2Norm | Vf::Half2 => {
(NumericDimension::Vector(Vs::Bi), Sk::Float, 4)
}
Vf::Uchar4Norm | Vf::Char4Norm | Vf::Ushort4Norm | Vf::Short4Norm | Vf::Half4 => {
(NumericDimension::Vector(Vs::Quad), Sk::Float, 4)
}
Vf::Float => (NumericDimension::Scalar, Sk::Float, 4),
Vf::Float2 => (NumericDimension::Vector(Vs::Bi), Sk::Float, 4),
Vf::Float3 => (NumericDimension::Vector(Vs::Tri), Sk::Float, 4),
Vf::Float4 => (NumericDimension::Vector(Vs::Quad), Sk::Float, 4),
Vf::Uint => (NumericDimension::Scalar, Sk::Uint, 4),
Vf::Uint2 => (NumericDimension::Vector(Vs::Bi), Sk::Uint, 4),
Vf::Uint3 => (NumericDimension::Vector(Vs::Tri), Sk::Uint, 4),
Vf::Uint4 => (NumericDimension::Vector(Vs::Quad), Sk::Uint, 4),
Vf::Int => (NumericDimension::Scalar, Sk::Sint, 4),
Vf::Int2 => (NumericDimension::Vector(Vs::Bi), Sk::Sint, 4),
Vf::Int3 => (NumericDimension::Vector(Vs::Tri), Sk::Sint, 4),
Vf::Int4 => (NumericDimension::Vector(Vs::Quad), Sk::Sint, 4),
Vf::Double => (NumericDimension::Scalar, Sk::Float, 8),
Vf::Double2 => (NumericDimension::Vector(Vs::Bi), Sk::Float, 8),
Vf::Double3 => (NumericDimension::Vector(Vs::Tri), Sk::Float, 8),
Vf::Double4 => (NumericDimension::Vector(Vs::Quad), Sk::Float, 8),
};
NumericType {
dim,
kind,
width,
}
}
fn from_texture_format(format: wgt::TextureFormat) -> Self {
use naga::{ScalarKind as Sk, VectorSize as Vs};
use wgt::TextureFormat as Tf;
let (dim, kind) = match format {
Tf::R8Unorm | Tf::R8Snorm | Tf::R16Float | Tf::R32Float => {
(NumericDimension::Scalar, Sk::Float)
}
Tf::R8Uint | Tf::R16Uint | Tf::R32Uint => (NumericDimension::Scalar, Sk::Uint),
Tf::R8Sint | Tf::R16Sint | Tf::R32Sint => (NumericDimension::Scalar, Sk::Sint),
Tf::Rg8Unorm | Tf::Rg8Snorm | Tf::Rg16Float | Tf::Rg32Float => {
(NumericDimension::Vector(Vs::Bi), Sk::Float)
}
Tf::Rg8Uint | Tf::Rg16Uint | Tf::Rg32Uint => {
(NumericDimension::Vector(Vs::Bi), Sk::Uint)
}
Tf::Rg8Sint | Tf::Rg16Sint | Tf::Rg32Sint => {
(NumericDimension::Vector(Vs::Bi), Sk::Sint)
}
Tf::Rgba8Unorm
| Tf::Rgba8UnormSrgb
| Tf::Rgba8Snorm
| Tf::Bgra8Unorm
| Tf::Bgra8UnormSrgb
| Tf::Rgb10a2Unorm
| Tf::Rgba16Float
| Tf::Rgba32Float => (NumericDimension::Vector(Vs::Quad), Sk::Float),
Tf::Rgba8Uint | Tf::Rgba16Uint | Tf::Rgba32Uint => {
(NumericDimension::Vector(Vs::Quad), Sk::Uint)
}
Tf::Rgba8Sint | Tf::Rgba16Sint | Tf::Rgba32Sint => {
(NumericDimension::Vector(Vs::Quad), Sk::Sint)
}
Tf::Rg11b10Float => (NumericDimension::Vector(Vs::Tri), Sk::Float),
Tf::Depth32Float | Tf::Depth24Plus | Tf::Depth24PlusStencil8 => {
panic!("Unexpected depth format")
}
Tf::Bc1RgbaUnorm
| Tf::Bc1RgbaUnormSrgb
| Tf::Bc2RgbaUnorm
| Tf::Bc2RgbaUnormSrgb
| Tf::Bc3RgbaUnorm
| Tf::Bc3RgbaUnormSrgb
| Tf::Bc7RgbaUnorm
| Tf::Bc7RgbaUnormSrgb
| Tf::Etc2RgbA1Unorm
| Tf::Etc2RgbA1UnormSrgb
| Tf::Etc2RgbA8Unorm
| Tf::Etc2RgbA8UnormSrgb
| Tf::Astc4x4RgbaUnorm
| Tf::Astc4x4RgbaUnormSrgb
| Tf::Astc5x4RgbaUnorm
| Tf::Astc5x4RgbaUnormSrgb
| Tf::Astc5x5RgbaUnorm
| Tf::Astc5x5RgbaUnormSrgb
| Tf::Astc6x5RgbaUnorm
| Tf::Astc6x5RgbaUnormSrgb
| Tf::Astc6x6RgbaUnorm
| Tf::Astc6x6RgbaUnormSrgb
| Tf::Astc8x5RgbaUnorm
| Tf::Astc8x5RgbaUnormSrgb
| Tf::Astc8x6RgbaUnorm
| Tf::Astc8x6RgbaUnormSrgb
| Tf::Astc10x5RgbaUnorm
| Tf::Astc10x5RgbaUnormSrgb
| Tf::Astc10x6RgbaUnorm
| Tf::Astc10x6RgbaUnormSrgb
| Tf::Astc8x8RgbaUnorm
| Tf::Astc8x8RgbaUnormSrgb
| Tf::Astc10x8RgbaUnorm
| Tf::Astc10x8RgbaUnormSrgb
| Tf::Astc10x10RgbaUnorm
| Tf::Astc10x10RgbaUnormSrgb
| Tf::Astc12x10RgbaUnorm
| Tf::Astc12x10RgbaUnormSrgb
| Tf::Astc12x12RgbaUnorm
| Tf::Astc12x12RgbaUnormSrgb => (NumericDimension::Vector(Vs::Quad), Sk::Float),
Tf::Bc4RUnorm | Tf::Bc4RSnorm | Tf::EacRUnorm | Tf::EacRSnorm => {
(NumericDimension::Scalar, Sk::Float)
}
Tf::Bc5RgUnorm | Tf::Bc5RgSnorm | Tf::EtcRgUnorm | Tf::EtcRgSnorm => {
(NumericDimension::Vector(Vs::Bi), Sk::Float)
}
Tf::Bc6hRgbUfloat | Tf::Bc6hRgbSfloat | Tf::Etc2RgbUnorm | Tf::Etc2RgbUnormSrgb => {
(NumericDimension::Vector(Vs::Tri), Sk::Float)
}
};
NumericType {
dim,
kind,
width: 4,
}
}
fn is_subtype_of(&self, other: &NumericType) -> bool {
if self.width > other.width {
return false;
}
if self.kind != other.kind {
return false;
}
match (self.dim, other.dim) {
(NumericDimension::Scalar, NumericDimension::Scalar) => true,
(NumericDimension::Scalar, NumericDimension::Vector(_)) => true,
(NumericDimension::Vector(s0), NumericDimension::Vector(s1)) => s0 <= s1,
(NumericDimension::Matrix(c0, r0), NumericDimension::Matrix(c1, r1)) => {
c0 == c1 && r0 == r1
}
_ => false,
}
}
}
pub fn check_texture_format(format: wgt::TextureFormat, output: &NumericType) -> bool {
NumericType::from_texture_format(format).is_subtype_of(output)
}
pub type StageIo = FastHashMap<wgt::ShaderLocation, NumericType>;
impl Interface {
pub fn new(module: &naga::Module) -> Self {
let mut resources = naga::Arena::new();
let mut resource_mapping = FastHashMap::default();
for (var_handle, var) in module.global_variables.iter() {
let (group, binding) = match var.binding {
Some(naga::Binding::Resource { group, binding }) => (group, binding),
_ => continue,
};
let ty = match module.types[var.ty].inner {
naga::TypeInner::Struct {
block: true,
ref members,
} => {
let mut actual_size = 0;
for (i, member) in members.iter().enumerate() {
actual_size +=
get_aligned_type_size(module, member.ty, i + 1 == members.len());
}
ResourceType::Buffer {
size: wgt::BufferSize::new(actual_size).unwrap(),
}
}
naga::TypeInner::Image {
dim,
arrayed,
class,
} => ResourceType::Texture {
dim,
arrayed,
class,
},
naga::TypeInner::Sampler { comparison } => ResourceType::Sampler { comparison },
ref other => panic!("Unexpected resource type: {:?}", other),
};
let handle = resources.append(Resource {
group,
binding,
ty,
class: var.class,
});
resource_mapping.insert(var_handle, handle);
}
let mut entry_points = FastHashMap::default();
entry_points.reserve(module.entry_points.len());
for (&(stage, ref ep_name), entry_point) in module.entry_points.iter() {
let mut ep = EntryPoint::default();
for ((var_handle, var), &usage) in module
.global_variables
.iter()
.zip(&entry_point.function.global_usage)
{
if usage.is_empty() {
continue;
}
let varying = match var.binding {
Some(naga::Binding::Resource { .. }) => {
ep.resources.push((resource_mapping[&var_handle], usage));
None
}
Some(naga::Binding::Location(location)) => {
let ty = match module.types[var.ty].inner {
naga::TypeInner::Scalar { kind, width } => NumericType {
dim: NumericDimension::Scalar,
kind,
width,
},
naga::TypeInner::Vector { size, kind, width } => NumericType {
dim: NumericDimension::Vector(size),
kind,
width,
},
naga::TypeInner::Matrix {
columns,
rows,
width,
} => NumericType {
dim: NumericDimension::Matrix(columns, rows),
kind: naga::ScalarKind::Float,
width,
},
ref other => panic!("Unexpected varying type: {:?}", other),
};
Some(Varying::Local { location, ty })
}
Some(naga::Binding::BuiltIn(built_in)) => Some(Varying::BuiltIn(built_in)),
_ => None,
};
if let Some(varying) = varying {
match var.class {
naga::StorageClass::Input => ep.inputs.push(varying),
naga::StorageClass::Output => ep.outputs.push(varying),
_ => (),
}
}
}
entry_points.insert((stage, ep_name.clone()), ep);
}
Interface {
resources,
entry_points,
}
}
pub fn check_stage(
&self,
given_layouts: Option<&[&BindEntryMap]>,
derived_layouts: &mut [BindEntryMap],
entry_point_name: &str,
stage_bit: wgt::ShaderStage,
inputs: StageIo,
) -> Result<StageIo, StageError> {
let shader_stage = match stage_bit {
wgt::ShaderStage::VERTEX => naga::ShaderStage::Vertex,
wgt::ShaderStage::FRAGMENT => naga::ShaderStage::Fragment,
wgt::ShaderStage::COMPUTE => naga::ShaderStage::Compute,
_ => unreachable!(),
};
let pair = (shader_stage, entry_point_name.to_string());
let entry_point = self
.entry_points
.get(&pair)
.ok_or(StageError::MissingEntryPoint(pair.1))?;
for &(handle, usage) in entry_point.resources.iter() {
let res = &self.resources[handle];
let result = match given_layouts {
Some(layouts) => layouts
.get(res.group as usize)
.and_then(|map| map.get(&res.binding))
.ok_or(BindingError::Missing)
.and_then(|entry| {
if entry.visibility.contains(stage_bit) {
Ok(entry)
} else {
Err(BindingError::Invisible)
}
})
.and_then(|entry| res.check_binding_use(entry, usage)),
None => derived_layouts
.get_mut(res.group as usize)
.ok_or(BindingError::Missing)
.and_then(|set| {
let ty = res.derive_binding_type(usage)?;
Ok(match set.entry(res.binding) {
Entry::Occupied(e) if e.get().ty != ty => {
return Err(BindingError::InconsistentlyDerivedType)
}
Entry::Occupied(e) => {
e.into_mut().visibility |= stage_bit;
}
Entry::Vacant(e) => {
e.insert(BindGroupLayoutEntry {
binding: res.binding,
ty,
visibility: stage_bit,
count: None,
});
}
})
}),
};
if let Err(error) = result {
return Err(StageError::Binding {
group: res.group,
binding: res.binding,
error,
});
}
}
for input in entry_point.inputs.iter() {
match *input {
Varying::Local { location, ty } => {
let result =
inputs
.get(&location)
.ok_or(InputError::Missing)
.and_then(|provided| {
if ty.is_subtype_of(provided) {
Ok(())
} else {
Err(InputError::WrongType)
}
});
if let Err(error) = result {
return Err(StageError::Input { location, error });
}
}
Varying::BuiltIn(_) => {}
}
}
let outputs = entry_point
.outputs
.iter()
.filter_map(|output| match *output {
Varying::Local { location, ty } => Some((location, ty)),
Varying::BuiltIn(_) => None,
})
.collect();
Ok(outputs)
}
}