mirror of
https://github.com/gfx-rs/wgpu.git
synced 2026-04-22 03:02:01 -04:00
436 lines
17 KiB
Rust
436 lines
17 KiB
Rust
use super::typifier::{ResolveContext, ResolveError, Typifier};
|
|
use crate::arena::{Arena, Handle};
|
|
|
|
const MAX_BIND_GROUPS: u32 = 8;
|
|
const MAX_LOCATIONS: u32 = 64; // using u64 mask
|
|
const MAX_BIND_INDICES: u32 = 64; // using u64 mask
|
|
const MAX_WORKGROUP_SIZE: u32 = 0x4000;
|
|
|
|
#[derive(Debug)]
|
|
pub struct Validator {
|
|
//Note: this is a bit tricky: some of the front-ends as well as backends
|
|
// already have to use the typifier, so the work here is redundant in a way.
|
|
typifier: Typifier,
|
|
}
|
|
|
|
#[derive(Clone, Debug, thiserror::Error)]
|
|
pub enum GlobalVariableError {
|
|
#[error("Usage isn't compatible with the storage class")]
|
|
InvalidUsage,
|
|
#[error("Type isn't compatible with the storage class")]
|
|
InvalidType,
|
|
#[error("Interpolation is not valid")]
|
|
InvalidInterpolation,
|
|
#[error("Storage access {seen:?} exceed the allowed {allowed:?}")]
|
|
InvalidStorageAccess {
|
|
allowed: crate::StorageAccess,
|
|
seen: crate::StorageAccess,
|
|
},
|
|
#[error("Binding decoration is missing or not applicable")]
|
|
InvalidBinding,
|
|
#[error("Binding is out of range")]
|
|
OutOfRangeBinding,
|
|
}
|
|
|
|
#[derive(Clone, Debug, thiserror::Error)]
|
|
pub enum FunctionError {
|
|
#[error(transparent)]
|
|
Resolve(#[from] ResolveError),
|
|
#[error("There are instructions after `return`/`break`/`continue`")]
|
|
InvalidControlFlowExitTail,
|
|
}
|
|
|
|
#[derive(Clone, Debug, thiserror::Error)]
|
|
pub enum EntryPointError {
|
|
#[error("Early depth test is not applicable")]
|
|
UnexpectedEarlyDepthTest,
|
|
#[error("Workgroup size is not applicable")]
|
|
UnexpectedWorkgroupSize,
|
|
#[error("Workgroup size is out of range")]
|
|
OutOfRangeWorkgroupSize,
|
|
#[error("Global variable {0:?} is used incorrectly as {1:?}")]
|
|
InvalidGlobalUsage(Handle<crate::GlobalVariable>, crate::GlobalUse),
|
|
#[error("Bindings for {0:?} conflict with other global variables")]
|
|
BindingCollision(Handle<crate::GlobalVariable>),
|
|
#[error("Built-in {0:?} is not applicable to this entry point")]
|
|
InvalidBuiltIn(crate::BuiltIn),
|
|
#[error("Interpolation of an integer has to be flat")]
|
|
InvalidIntegerInterpolation,
|
|
#[error(transparent)]
|
|
Function(#[from] FunctionError),
|
|
}
|
|
|
|
#[derive(Clone, Debug, thiserror::Error)]
|
|
pub enum ValidationError {
|
|
#[error("The type {0:?} width {1} is not supported")]
|
|
InvalidTypeWidth(crate::ScalarKind, crate::Bytes),
|
|
#[error("The type handle {0:?} can not be resolved")]
|
|
UnresolvedType(Handle<crate::Type>),
|
|
#[error("Global variable {handle:?} '{name}' is invalid: {error:?}")]
|
|
GlobalVariable {
|
|
handle: Handle<crate::GlobalVariable>,
|
|
name: String,
|
|
error: GlobalVariableError,
|
|
},
|
|
#[error("Function {0:?} is invalid: {1:?}")]
|
|
Function(Handle<crate::Function>, FunctionError),
|
|
#[error("Entry point {name} at {stage:?} is invalid: {error:?}")]
|
|
EntryPoint {
|
|
stage: crate::ShaderStage,
|
|
name: String,
|
|
error: EntryPointError,
|
|
},
|
|
}
|
|
|
|
impl crate::GlobalVariable {
|
|
fn forbid_interpolation(&self) -> Result<(), GlobalVariableError> {
|
|
match self.interpolation {
|
|
Some(_) => Err(GlobalVariableError::InvalidInterpolation),
|
|
None => Ok(()),
|
|
}
|
|
}
|
|
|
|
fn check_resource(&self) -> Result<(), GlobalVariableError> {
|
|
match self.binding {
|
|
Some(crate::Binding::BuiltIn(_)) => {} // validated per entry point
|
|
Some(crate::Binding::Resource { group, binding }) => {
|
|
if group > MAX_BIND_GROUPS || binding > MAX_BIND_INDICES {
|
|
return Err(GlobalVariableError::OutOfRangeBinding);
|
|
}
|
|
}
|
|
Some(crate::Binding::Location(_)) | None => {
|
|
return Err(GlobalVariableError::InvalidBinding)
|
|
}
|
|
}
|
|
self.forbid_interpolation()
|
|
}
|
|
}
|
|
|
|
fn storage_usage(access: crate::StorageAccess) -> crate::GlobalUse {
|
|
let mut storage_usage = crate::GlobalUse::empty();
|
|
if access.contains(crate::StorageAccess::LOAD) {
|
|
storage_usage |= crate::GlobalUse::LOAD;
|
|
}
|
|
if access.contains(crate::StorageAccess::STORE) {
|
|
storage_usage |= crate::GlobalUse::STORE;
|
|
}
|
|
storage_usage
|
|
}
|
|
|
|
impl Validator {
|
|
/// Construct a new validator instance.
|
|
pub fn new() -> Self {
|
|
Validator {
|
|
typifier: Typifier::new(),
|
|
}
|
|
}
|
|
|
|
fn validate_global_var(
|
|
&self,
|
|
var: &crate::GlobalVariable,
|
|
types: &Arena<crate::Type>,
|
|
) -> Result<(), GlobalVariableError> {
|
|
log::debug!("var {:?}", var);
|
|
let allowed_storage_access = match var.class {
|
|
crate::StorageClass::Function => return Err(GlobalVariableError::InvalidUsage),
|
|
crate::StorageClass::Input | crate::StorageClass::Output => {
|
|
match var.binding {
|
|
Some(crate::Binding::BuiltIn(_)) => {
|
|
// validated per entry point
|
|
var.forbid_interpolation()?
|
|
}
|
|
Some(crate::Binding::Location(loc)) => {
|
|
if loc > MAX_LOCATIONS {
|
|
return Err(GlobalVariableError::OutOfRangeBinding);
|
|
}
|
|
match types[var.ty].inner {
|
|
crate::TypeInner::Scalar { .. }
|
|
| crate::TypeInner::Vector { .. }
|
|
| crate::TypeInner::Matrix { .. } => {}
|
|
_ => return Err(GlobalVariableError::InvalidType),
|
|
}
|
|
}
|
|
Some(crate::Binding::Resource { .. }) => {
|
|
return Err(GlobalVariableError::InvalidBinding)
|
|
}
|
|
None => {
|
|
match types[var.ty].inner {
|
|
//TODO: check the member types
|
|
crate::TypeInner::Struct { members: _ } => {
|
|
var.forbid_interpolation()?
|
|
}
|
|
_ => return Err(GlobalVariableError::InvalidType),
|
|
}
|
|
}
|
|
}
|
|
crate::StorageAccess::empty()
|
|
}
|
|
crate::StorageClass::Storage => {
|
|
var.check_resource()?;
|
|
crate::StorageAccess::all()
|
|
}
|
|
crate::StorageClass::Uniform => {
|
|
var.check_resource()?;
|
|
crate::StorageAccess::empty()
|
|
}
|
|
crate::StorageClass::Handle => {
|
|
var.check_resource()?;
|
|
match types[var.ty].inner {
|
|
crate::TypeInner::Image {
|
|
class: crate::ImageClass::Storage(_),
|
|
..
|
|
} => crate::StorageAccess::all(),
|
|
_ => crate::StorageAccess::empty(),
|
|
}
|
|
}
|
|
crate::StorageClass::Private | crate::StorageClass::WorkGroup => {
|
|
if var.binding.is_some() {
|
|
return Err(GlobalVariableError::InvalidBinding);
|
|
}
|
|
var.forbid_interpolation()?;
|
|
crate::StorageAccess::empty()
|
|
}
|
|
crate::StorageClass::PushConstant => {
|
|
//TODO
|
|
return Err(GlobalVariableError::InvalidStorageAccess {
|
|
allowed: crate::StorageAccess::empty(),
|
|
seen: crate::StorageAccess::empty(),
|
|
});
|
|
}
|
|
};
|
|
|
|
if !allowed_storage_access.contains(var.storage_access) {
|
|
return Err(GlobalVariableError::InvalidStorageAccess {
|
|
allowed: allowed_storage_access,
|
|
seen: var.storage_access,
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_function(
|
|
&mut self,
|
|
fun: &crate::Function,
|
|
module: &crate::Module,
|
|
) -> Result<(), FunctionError> {
|
|
let resolve_ctx = ResolveContext {
|
|
constants: &module.constants,
|
|
global_vars: &module.global_variables,
|
|
local_vars: &fun.local_variables,
|
|
functions: &module.functions,
|
|
parameter_types: &fun.parameter_types,
|
|
};
|
|
self.typifier
|
|
.resolve_all(&fun.expressions, &module.types, &resolve_ctx)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_entry_point(
|
|
&mut self,
|
|
ep: &crate::EntryPoint,
|
|
stage: crate::ShaderStage,
|
|
module: &crate::Module,
|
|
) -> Result<(), EntryPointError> {
|
|
if ep.early_depth_test.is_some() && stage != crate::ShaderStage::Fragment {
|
|
return Err(EntryPointError::UnexpectedEarlyDepthTest);
|
|
}
|
|
if stage == crate::ShaderStage::Compute {
|
|
if ep
|
|
.workgroup_size
|
|
.iter()
|
|
.any(|&s| s == 0 || s > MAX_WORKGROUP_SIZE)
|
|
{
|
|
return Err(EntryPointError::OutOfRangeWorkgroupSize);
|
|
}
|
|
} else if ep.workgroup_size != [0; 3] {
|
|
return Err(EntryPointError::UnexpectedWorkgroupSize);
|
|
}
|
|
|
|
let mut bind_group_masks = [0u64; MAX_BIND_GROUPS as usize];
|
|
let mut location_in_mask = 0u64;
|
|
let mut location_out_mask = 0u64;
|
|
for ((var_handle, var), &usage) in module
|
|
.global_variables
|
|
.iter()
|
|
.zip(&ep.function.global_usage)
|
|
{
|
|
if usage.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
if let Some(crate::Binding::Location(_)) = var.binding {
|
|
match (stage, var.class) {
|
|
(crate::ShaderStage::Vertex, crate::StorageClass::Output)
|
|
| (crate::ShaderStage::Fragment, crate::StorageClass::Input) => {
|
|
match module.types[var.ty].inner.scalar_kind() {
|
|
Some(crate::ScalarKind::Float) => {}
|
|
Some(_) if var.interpolation != Some(crate::Interpolation::Flat) => {
|
|
return Err(EntryPointError::InvalidIntegerInterpolation);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
let allowed_usage = match var.class {
|
|
crate::StorageClass::Function => unreachable!(),
|
|
crate::StorageClass::Input => {
|
|
let mask = match var.binding {
|
|
Some(crate::Binding::BuiltIn(built_in)) => match (stage, built_in) {
|
|
(crate::ShaderStage::Vertex, crate::BuiltIn::BaseInstance)
|
|
| (crate::ShaderStage::Vertex, crate::BuiltIn::BaseVertex)
|
|
| (crate::ShaderStage::Vertex, crate::BuiltIn::InstanceIndex)
|
|
| (crate::ShaderStage::Vertex, crate::BuiltIn::VertexIndex)
|
|
| (crate::ShaderStage::Fragment, crate::BuiltIn::PointSize)
|
|
| (crate::ShaderStage::Fragment, crate::BuiltIn::FragCoord)
|
|
| (crate::ShaderStage::Fragment, crate::BuiltIn::FrontFacing)
|
|
| (crate::ShaderStage::Fragment, crate::BuiltIn::SampleIndex)
|
|
| (crate::ShaderStage::Compute, crate::BuiltIn::GlobalInvocationId)
|
|
| (crate::ShaderStage::Compute, crate::BuiltIn::LocalInvocationId)
|
|
| (crate::ShaderStage::Compute, crate::BuiltIn::LocalInvocationIndex)
|
|
| (crate::ShaderStage::Compute, crate::BuiltIn::WorkGroupId) => 0,
|
|
_ => return Err(EntryPointError::InvalidBuiltIn(built_in)),
|
|
},
|
|
Some(crate::Binding::Location(loc)) => 1 << loc,
|
|
Some(crate::Binding::Resource { .. }) => unreachable!(),
|
|
None => 0,
|
|
};
|
|
if location_in_mask & mask != 0 {
|
|
return Err(EntryPointError::BindingCollision(var_handle));
|
|
}
|
|
location_in_mask |= mask;
|
|
crate::GlobalUse::LOAD
|
|
}
|
|
crate::StorageClass::Output => {
|
|
let mask = match var.binding {
|
|
Some(crate::Binding::BuiltIn(built_in)) => match (stage, built_in) {
|
|
(crate::ShaderStage::Vertex, crate::BuiltIn::Position)
|
|
| (crate::ShaderStage::Vertex, crate::BuiltIn::PointSize)
|
|
| (crate::ShaderStage::Vertex, crate::BuiltIn::ClipDistance)
|
|
| (crate::ShaderStage::Fragment, crate::BuiltIn::FragDepth) => 0,
|
|
_ => return Err(EntryPointError::InvalidBuiltIn(built_in)),
|
|
},
|
|
Some(crate::Binding::Location(loc)) => 1 << loc,
|
|
Some(crate::Binding::Resource { .. }) => unreachable!(),
|
|
None => 0,
|
|
};
|
|
if location_out_mask & mask != 0 {
|
|
return Err(EntryPointError::BindingCollision(var_handle));
|
|
}
|
|
location_out_mask |= mask;
|
|
crate::GlobalUse::LOAD | crate::GlobalUse::STORE
|
|
}
|
|
crate::StorageClass::Uniform => crate::GlobalUse::LOAD,
|
|
crate::StorageClass::Storage => storage_usage(var.storage_access),
|
|
crate::StorageClass::Handle => match module.types[var.ty].inner {
|
|
crate::TypeInner::Image {
|
|
class: crate::ImageClass::Storage(_),
|
|
..
|
|
} => storage_usage(var.storage_access),
|
|
_ => crate::GlobalUse::LOAD,
|
|
},
|
|
crate::StorageClass::Private | crate::StorageClass::WorkGroup => {
|
|
crate::GlobalUse::all()
|
|
}
|
|
crate::StorageClass::PushConstant => crate::GlobalUse::LOAD,
|
|
};
|
|
if !allowed_usage.contains(usage) {
|
|
log::warn!("\tUsage error for: {:?}", var);
|
|
log::warn!(
|
|
"\tAllowed usage: {:?}, requested: {:?}",
|
|
allowed_usage,
|
|
usage
|
|
);
|
|
return Err(EntryPointError::InvalidGlobalUsage(var_handle, usage));
|
|
}
|
|
|
|
if let Some(crate::Binding::Resource { group, binding }) = var.binding {
|
|
let mask = 1 << binding;
|
|
let group_mask = &mut bind_group_masks[group as usize];
|
|
if *group_mask & mask != 0 {
|
|
return Err(EntryPointError::BindingCollision(var_handle));
|
|
}
|
|
*group_mask |= mask;
|
|
}
|
|
}
|
|
|
|
self.validate_function(&ep.function, module)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Check the given module to be valid.
|
|
pub fn validate(&mut self, module: &crate::Module) -> Result<(), ValidationError> {
|
|
// check the types
|
|
for (handle, ty) in module.types.iter() {
|
|
use crate::TypeInner as Ti;
|
|
match ty.inner {
|
|
Ti::Scalar { kind, width } | Ti::Vector { kind, width, .. } => {
|
|
let expected = match kind {
|
|
crate::ScalarKind::Bool => 1,
|
|
_ => 4,
|
|
};
|
|
if width != expected {
|
|
return Err(ValidationError::InvalidTypeWidth(kind, width));
|
|
}
|
|
}
|
|
Ti::Matrix { width, .. } => {
|
|
if width != 4 {
|
|
return Err(ValidationError::InvalidTypeWidth(
|
|
crate::ScalarKind::Float,
|
|
width,
|
|
));
|
|
}
|
|
}
|
|
Ti::Pointer { base, class: _ } => {
|
|
if base >= handle {
|
|
return Err(ValidationError::UnresolvedType(base));
|
|
}
|
|
}
|
|
Ti::Array { base, .. } => {
|
|
if base >= handle {
|
|
return Err(ValidationError::UnresolvedType(base));
|
|
}
|
|
}
|
|
Ti::Struct { ref members } => {
|
|
//TODO: check that offsets are not intersecting?
|
|
for member in members {
|
|
if member.ty >= handle {
|
|
return Err(ValidationError::UnresolvedType(member.ty));
|
|
}
|
|
}
|
|
}
|
|
Ti::Image { .. } => {}
|
|
Ti::Sampler { comparison: _ } => {}
|
|
}
|
|
}
|
|
|
|
for (var_handle, var) in module.global_variables.iter() {
|
|
self.validate_global_var(var, &module.types)
|
|
.map_err(|error| ValidationError::GlobalVariable {
|
|
handle: var_handle,
|
|
name: var.name.clone().unwrap_or_default(),
|
|
error,
|
|
})?;
|
|
}
|
|
|
|
for (fun_handle, fun) in module.functions.iter() {
|
|
self.validate_function(fun, module)
|
|
.map_err(|e| ValidationError::Function(fun_handle, e))?;
|
|
}
|
|
|
|
for (&(stage, ref name), entry_point) in module.entry_points.iter() {
|
|
self.validate_entry_point(entry_point, stage, module)
|
|
.map_err(|error| ValidationError::EntryPoint {
|
|
stage,
|
|
name: name.to_string(),
|
|
error,
|
|
})?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|