Properly validate shader interfaces between stages (#2418)

* Properly validate shader interfaces between stages

* Update vulkano/src/pipeline/shader.rs

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>

---------

Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com>
This commit is contained in:
Rua 2023-12-01 22:49:33 +01:00 committed by GitHub
parent 5c98e5290a
commit c70876b3b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 1417 additions and 735 deletions

View File

@ -60,8 +60,8 @@ use self::{
viewport::ViewportState,
};
use super::{
cache::PipelineCache, DynamicState, Pipeline, PipelineBindPoint, PipelineCreateFlags,
PipelineLayout, PipelineShaderStageCreateInfo,
cache::PipelineCache, shader::validate_interfaces_compatible, DynamicState, Pipeline,
PipelineBindPoint, PipelineCreateFlags, PipelineLayout, PipelineShaderStageCreateInfo,
};
use crate::{
device::{Device, DeviceOwned, DeviceOwnedDebugWrapper},
@ -1996,29 +1996,50 @@ impl GraphicsPipelineCreateInfo {
.flatten()
.collect();
// TODO: this check is too strict; the output only has to be a superset, any variables
// not used in the input of the next shader are just ignored.
for (output, input) in ordered_stages.iter().zip(ordered_stages.iter().skip(1)) {
if let Err(err) = (input.entry_point.info().input_interface)
.matches(&output.entry_point.info().output_interface)
{
return Err(Box::new(ValidationError {
context: "stages".into(),
problem: format!(
"the output interface of the `ShaderStage::{:?}` stage does not \
match the input interface of the `ShaderStage::{:?}` stage: {}",
ShaderStage::from(output.entry_point.info().execution_model),
ShaderStage::from(input.entry_point.info().execution_model),
err
)
.into(),
vuids: &[
"VUID-VkGraphicsPipelineCreateInfo-pStages-00742",
"VUID-VkGraphicsPipelineCreateInfo-None-04889",
],
..Default::default()
}));
}
let out_spirv = output.entry_point.module().spirv();
let (out_execution_model, out_interface) =
match out_spirv.function(output.entry_point.id()).entry_point() {
Some(&Instruction::EntryPoint {
execution_model,
ref interface,
..
}) => (execution_model, interface),
_ => unreachable!(),
};
let in_spirv = input.entry_point.module().spirv();
let (in_execution_model, in_interface) =
match in_spirv.function(input.entry_point.id()).entry_point() {
Some(&Instruction::EntryPoint {
execution_model,
ref interface,
..
}) => (execution_model, interface),
_ => unreachable!(),
};
validate_interfaces_compatible(
out_spirv,
out_execution_model,
out_interface,
in_spirv,
in_execution_model,
in_interface,
device.enabled_features().maintenance4,
)
.map_err(|mut err| {
err.context = "stages".into();
err.problem = format!(
"the output interface of the `{:?}` stage is not compatible with \
the input interface of the `{:?}` stage: {}",
ShaderStage::from(out_execution_model),
ShaderStage::from(in_execution_model),
err.problem
)
.into();
err
})?;
}
// VUID-VkGraphicsPipelineCreateInfo-layout-01688

View File

@ -8,15 +8,13 @@
//! the CPU). Consequently it is a CPU-intensive operation that should be performed at
//! initialization or during a loading screen.
pub use self::{compute::ComputePipeline, graphics::GraphicsPipeline, layout::PipelineLayout};
pub use self::{
compute::ComputePipeline, graphics::GraphicsPipeline, layout::PipelineLayout, shader::*,
};
use crate::{
device::{Device, DeviceOwned},
device::DeviceOwned,
macros::{vulkan_bitflags, vulkan_enum},
shader::{
spirv::{BuiltIn, Decoration, ExecutionMode, Id, Instruction},
DescriptorBindingRequirements, EntryPoint, ShaderStage,
},
Requires, RequiresAllOf, RequiresOneOf, ValidationError,
shader::DescriptorBindingRequirements,
};
use ahash::HashMap;
use std::sync::Arc;
@ -25,6 +23,7 @@ pub mod cache;
pub mod compute;
pub mod graphics;
pub mod layout;
pub(crate) mod shader;
/// A trait for operations shared between pipeline types.
pub trait Pipeline: DeviceOwned {
@ -290,651 +289,6 @@ vulkan_bitflags! {
]),*/
}
/// Specifies a single shader stage when creating a pipeline.
#[derive(Clone, Debug)]
pub struct PipelineShaderStageCreateInfo {
/// Additional properties of the shader stage.
///
/// The default value is empty.
pub flags: PipelineShaderStageCreateFlags,
/// The shader entry point for the stage, which includes any specialization constants.
///
/// There is no default value.
pub entry_point: EntryPoint,
/// The required subgroup size.
///
/// Requires [`subgroup_size_control`](crate::device::Features::subgroup_size_control). The
/// shader stage must be included in
/// [`required_subgroup_size_stages`](crate::device::Properties::required_subgroup_size_stages).
/// Subgroup size must be power of 2 and within
/// [`min_subgroup_size`](crate::device::Properties::min_subgroup_size)
/// and [`max_subgroup_size`](crate::device::Properties::max_subgroup_size).
///
/// For compute shaders, `max_compute_workgroup_subgroups * required_subgroup_size` must be
/// greater than or equal to `workgroup_size.x * workgroup_size.y * workgroup_size.z`.
///
/// The default value is None.
pub required_subgroup_size: Option<u32>,
pub _ne: crate::NonExhaustive,
}
impl PipelineShaderStageCreateInfo {
/// Returns a `PipelineShaderStageCreateInfo` with the specified `entry_point`.
#[inline]
pub fn new(entry_point: EntryPoint) -> Self {
Self {
flags: PipelineShaderStageCreateFlags::empty(),
entry_point,
required_subgroup_size: None,
_ne: crate::NonExhaustive(()),
}
}
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let &Self {
flags,
ref entry_point,
required_subgroup_size,
_ne: _,
} = self;
let properties = device.physical_device().properties();
flags.validate_device(device).map_err(|err| {
err.add_context("flags")
.set_vuids(&["VUID-VkPipelineShaderStageCreateInfo-flags-parameter"])
})?;
let entry_point_info = entry_point.info();
let stage_enum = ShaderStage::from(entry_point_info.execution_model);
stage_enum.validate_device(device).map_err(|err| {
err.add_context("entry_point.info().execution")
.set_vuids(&["VUID-VkPipelineShaderStageCreateInfo-stage-parameter"])
})?;
// VUID-VkPipelineShaderStageCreateInfo-pName-00707
// Guaranteed by definition of `EntryPoint`.
// TODO:
// VUID-VkPipelineShaderStageCreateInfo-maxClipDistances-00708
// VUID-VkPipelineShaderStageCreateInfo-maxCullDistances-00709
// VUID-VkPipelineShaderStageCreateInfo-maxCombinedClipAndCullDistances-00710
// VUID-VkPipelineShaderStageCreateInfo-maxSampleMaskWords-00711
// VUID-VkPipelineShaderStageCreateInfo-stage-02596
// VUID-VkPipelineShaderStageCreateInfo-stage-02597
match stage_enum {
ShaderStage::Vertex => {
// VUID-VkPipelineShaderStageCreateInfo-stage-00712
// TODO:
}
ShaderStage::TessellationControl | ShaderStage::TessellationEvaluation => {
if !device.enabled_features().tessellation_shader {
return Err(Box::new(ValidationError {
context: "entry_point".into(),
problem: "specifies a `ShaderStage::TessellationControl` or \
`ShaderStage::TessellationEvaluation` entry point"
.into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"tessellation_shader",
)])]),
vuids: &["VUID-VkPipelineShaderStageCreateInfo-stage-00705"],
}));
}
// VUID-VkPipelineShaderStageCreateInfo-stage-00713
// TODO:
}
ShaderStage::Geometry => {
if !device.enabled_features().geometry_shader {
return Err(Box::new(ValidationError {
context: "entry_point".into(),
problem: "specifies a `ShaderStage::Geometry` entry point".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"geometry_shader",
)])]),
vuids: &["VUID-VkPipelineShaderStageCreateInfo-stage-00704"],
}));
}
// TODO:
// VUID-VkPipelineShaderStageCreateInfo-stage-00714
// VUID-VkPipelineShaderStageCreateInfo-stage-00715
}
ShaderStage::Fragment => {
// TODO:
// VUID-VkPipelineShaderStageCreateInfo-stage-00718
// VUID-VkPipelineShaderStageCreateInfo-stage-06685
// VUID-VkPipelineShaderStageCreateInfo-stage-06686
}
ShaderStage::Compute => (),
ShaderStage::Raygen => (),
ShaderStage::AnyHit => (),
ShaderStage::ClosestHit => (),
ShaderStage::Miss => (),
ShaderStage::Intersection => (),
ShaderStage::Callable => (),
ShaderStage::Task => {
if !device.enabled_features().task_shader {
return Err(Box::new(ValidationError {
context: "entry_point".into(),
problem: "specifies a `ShaderStage::Task` entry point".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"task_shader",
)])]),
vuids: &["VUID-VkPipelineShaderStageCreateInfo-stage-02092"],
}));
}
}
ShaderStage::Mesh => {
if !device.enabled_features().mesh_shader {
return Err(Box::new(ValidationError {
context: "entry_point".into(),
problem: "specifies a `ShaderStage::Mesh` entry point".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"mesh_shader",
)])]),
vuids: &["VUID-VkPipelineShaderStageCreateInfo-stage-02091"],
}));
}
}
ShaderStage::SubpassShading => (),
}
let spirv = entry_point.module().spirv();
let entry_point_function = spirv.function(entry_point.id());
let mut clip_distance_array_size = 0;
let mut cull_distance_array_size = 0;
for instruction in spirv.decorations() {
if let Instruction::Decorate {
target,
decoration: Decoration::BuiltIn { built_in },
} = *instruction
{
let variable_array_size = |variable| {
let result_type_id = match *spirv.id(variable).instruction() {
Instruction::Variable { result_type_id, .. } => result_type_id,
_ => return None,
};
let length = match *spirv.id(result_type_id).instruction() {
Instruction::TypeArray { length, .. } => length,
_ => return None,
};
let value = match *spirv.id(length).instruction() {
Instruction::Constant { ref value, .. } => {
if value.len() > 1 {
u32::MAX
} else {
value[0]
}
}
_ => return None,
};
Some(value)
};
match built_in {
BuiltIn::ClipDistance => {
clip_distance_array_size = variable_array_size(target).unwrap();
if clip_distance_array_size > properties.max_clip_distances {
return Err(Box::new(ValidationError {
context: "entry_point".into(),
problem: "the number of elements in the `ClipDistance` built-in \
variable is greater than the \
`max_clip_distances` device limit"
.into(),
vuids: &[
"VUID-VkPipelineShaderStageCreateInfo-maxClipDistances-00708",
],
..Default::default()
}));
}
}
BuiltIn::CullDistance => {
cull_distance_array_size = variable_array_size(target).unwrap();
if cull_distance_array_size > properties.max_cull_distances {
return Err(Box::new(ValidationError {
context: "entry_point".into(),
problem: "the number of elements in the `CullDistance` built-in \
variable is greater than the \
`max_cull_distances` device limit"
.into(),
vuids: &[
"VUID-VkPipelineShaderStageCreateInfo-maxCullDistances-00709",
],
..Default::default()
}));
}
}
BuiltIn::SampleMask => {
if variable_array_size(target).unwrap() > properties.max_sample_mask_words {
return Err(Box::new(ValidationError {
context: "entry_point".into(),
problem: "the number of elements in the `SampleMask` built-in \
variable is greater than the \
`max_sample_mask_words` device limit"
.into(),
vuids: &[
"VUID-VkPipelineShaderStageCreateInfo-maxSampleMaskWords-00711",
],
..Default::default()
}));
}
}
_ => (),
}
}
}
if clip_distance_array_size
.checked_add(cull_distance_array_size)
.map_or(true, |sum| {
sum > properties.max_combined_clip_and_cull_distances
})
{
return Err(Box::new(ValidationError {
context: "entry_point".into(),
problem: "the sum of the number of elements in the `ClipDistance` and \
`CullDistance` built-in variables is greater than the \
`max_combined_clip_and_cull_distances` device limit"
.into(),
vuids: &[
"VUID-VkPipelineShaderStageCreateInfo-maxCombinedClipAndCullDistances-00710",
],
..Default::default()
}));
}
for instruction in entry_point_function.execution_modes() {
if let Instruction::ExecutionMode {
mode: ExecutionMode::OutputVertices { vertex_count },
..
} = *instruction
{
match stage_enum {
ShaderStage::TessellationControl | ShaderStage::TessellationEvaluation => {
if vertex_count == 0 {
return Err(Box::new(ValidationError {
context: "entry_point".into(),
problem: "the `vertex_count` of the \
`ExecutionMode::OutputVertices` is zero"
.into(),
vuids: &["VUID-VkPipelineShaderStageCreateInfo-stage-00713"],
..Default::default()
}));
}
if vertex_count > properties.max_tessellation_patch_size {
return Err(Box::new(ValidationError {
context: "entry_point".into(),
problem: "the `vertex_count` of the \
`ExecutionMode::OutputVertices` is greater than the \
`max_tessellation_patch_size` device limit"
.into(),
vuids: &["VUID-VkPipelineShaderStageCreateInfo-stage-00713"],
..Default::default()
}));
}
}
ShaderStage::Geometry => {
if vertex_count == 0 {
return Err(Box::new(ValidationError {
context: "entry_point".into(),
problem: "the `vertex_count` of the \
`ExecutionMode::OutputVertices` is zero"
.into(),
vuids: &["VUID-VkPipelineShaderStageCreateInfo-stage-00714"],
..Default::default()
}));
}
if vertex_count > properties.max_geometry_output_vertices {
return Err(Box::new(ValidationError {
context: "entry_point".into(),
problem: "the `vertex_count` of the \
`ExecutionMode::OutputVertices` is greater than the \
`max_geometry_output_vertices` device limit"
.into(),
vuids: &["VUID-VkPipelineShaderStageCreateInfo-stage-00714"],
..Default::default()
}));
}
}
_ => (),
}
}
}
let local_size = (spirv
.decorations()
.iter()
.find_map(|instruction| match *instruction {
Instruction::Decorate {
target,
decoration:
Decoration::BuiltIn {
built_in: BuiltIn::WorkgroupSize,
},
} => {
let constituents: &[Id; 3] = match *spirv.id(target).instruction() {
Instruction::ConstantComposite {
ref constituents, ..
} => constituents.as_slice().try_into().unwrap(),
_ => unreachable!(),
};
let local_size = constituents.map(|id| match *spirv.id(id).instruction() {
Instruction::Constant { ref value, .. } => {
assert!(value.len() == 1);
value[0]
}
_ => unreachable!(),
});
Some(local_size)
}
_ => None,
}))
.or_else(|| {
entry_point_function
.execution_modes()
.iter()
.find_map(|instruction| match *instruction {
Instruction::ExecutionMode {
mode:
ExecutionMode::LocalSize {
x_size,
y_size,
z_size,
},
..
} => Some([x_size, y_size, z_size]),
Instruction::ExecutionModeId {
mode:
ExecutionMode::LocalSizeId {
x_size,
y_size,
z_size,
},
..
} => Some([x_size, y_size, z_size].map(
|id| match *spirv.id(id).instruction() {
Instruction::Constant { ref value, .. } => {
assert!(value.len() == 1);
value[0]
}
_ => unreachable!(),
},
)),
_ => None,
})
})
.unwrap_or_default();
let workgroup_size = local_size.into_iter().try_fold(1, u32::checked_mul);
match stage_enum {
ShaderStage::Compute => {
if local_size[0] > properties.max_compute_work_group_size[0] {
return Err(Box::new(ValidationError {
problem: "the `local_size_x` of `entry_point` is greater than \
`max_compute_work_group_size[0]`"
.into(),
vuids: &["VUID-RuntimeSpirv-x-06429"],
..Default::default()
}));
}
if local_size[1] > properties.max_compute_work_group_size[1] {
return Err(Box::new(ValidationError {
problem: "the `local_size_y` of `entry_point` is greater than \
`max_compute_work_group_size[1]`"
.into(),
vuids: &["VUID-RuntimeSpirv-x-06430"],
..Default::default()
}));
}
if local_size[2] > properties.max_compute_work_group_size[2] {
return Err(Box::new(ValidationError {
problem: "the `local_size_x` of `entry_point` is greater than \
`max_compute_work_group_size[2]`"
.into(),
vuids: &["VUID-RuntimeSpirv-x-06431"],
..Default::default()
}));
}
if workgroup_size.map_or(true, |size| {
size > properties.max_compute_work_group_invocations
}) {
return Err(Box::new(ValidationError {
problem: "the product of the `local_size_x`, `local_size_y` and \
`local_size_z` of `entry_point` is greater than the \
`max_compute_work_group_invocations` device limit"
.into(),
vuids: &["VUID-RuntimeSpirv-x-06432"],
..Default::default()
}));
}
}
ShaderStage::Task => {
if local_size[0] > properties.max_task_work_group_size.unwrap_or_default()[0] {
return Err(Box::new(ValidationError {
problem: "the `local_size_x` of `entry_point` is greater than \
`max_task_work_group_size[0]`"
.into(),
vuids: &["VUID-RuntimeSpirv-TaskEXT-07291"],
..Default::default()
}));
}
if local_size[1] > properties.max_task_work_group_size.unwrap_or_default()[1] {
return Err(Box::new(ValidationError {
problem: "the `local_size_y` of `entry_point` is greater than \
`max_task_work_group_size[1]`"
.into(),
vuids: &["VUID-RuntimeSpirv-TaskEXT-07292"],
..Default::default()
}));
}
if local_size[2] > properties.max_task_work_group_size.unwrap_or_default()[2] {
return Err(Box::new(ValidationError {
problem: "the `local_size_x` of `entry_point` is greater than \
`max_task_work_group_size[2]`"
.into(),
vuids: &["VUID-RuntimeSpirv-TaskEXT-07293"],
..Default::default()
}));
}
if workgroup_size.map_or(true, |size| {
size > properties
.max_task_work_group_invocations
.unwrap_or_default()
}) {
return Err(Box::new(ValidationError {
problem: "the product of the `local_size_x`, `local_size_y` and \
`local_size_z` of `entry_point` is greater than the \
`max_task_work_group_invocations` device limit"
.into(),
vuids: &["VUID-RuntimeSpirv-TaskEXT-07294"],
..Default::default()
}));
}
}
ShaderStage::Mesh => {
if local_size[0] > properties.max_mesh_work_group_size.unwrap_or_default()[0] {
return Err(Box::new(ValidationError {
problem: "the `local_size_x` of `entry_point` is greater than \
`max_mesh_work_group_size[0]`"
.into(),
vuids: &["VUID-RuntimeSpirv-MeshEXT-07295"],
..Default::default()
}));
}
if local_size[1] > properties.max_mesh_work_group_size.unwrap_or_default()[1] {
return Err(Box::new(ValidationError {
problem: "the `local_size_y` of `entry_point` is greater than \
`max_mesh_work_group_size[1]`"
.into(),
vuids: &["VUID-RuntimeSpirv-MeshEXT-07296"],
..Default::default()
}));
}
if local_size[2] > properties.max_mesh_work_group_size.unwrap_or_default()[2] {
return Err(Box::new(ValidationError {
problem: "the `local_size_x` of `entry_point` is greater than \
`max_mesh_work_group_size[2]`"
.into(),
vuids: &["VUID-RuntimeSpirv-MeshEXT-07297"],
..Default::default()
}));
}
if workgroup_size.map_or(true, |size| {
size > properties
.max_mesh_work_group_invocations
.unwrap_or_default()
}) {
return Err(Box::new(ValidationError {
problem: "the product of the `local_size_x`, `local_size_y` and \
`local_size_z` of `entry_point` is greater than the \
`max_mesh_work_group_invocations` device limit"
.into(),
vuids: &["VUID-RuntimeSpirv-MeshEXT-07298"],
..Default::default()
}));
}
}
_ => (),
}
let workgroup_size = workgroup_size.unwrap();
if let Some(required_subgroup_size) = required_subgroup_size {
if !device.enabled_features().subgroup_size_control {
return Err(Box::new(ValidationError {
context: "required_subgroup_size".into(),
problem: "is `Some`".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"subgroup_size_control",
)])]),
vuids: &["VUID-VkPipelineShaderStageCreateInfo-pNext-02755"],
}));
}
if !properties
.required_subgroup_size_stages
.unwrap_or_default()
.contains_enum(stage_enum)
{
return Err(Box::new(ValidationError {
problem: "`required_subgroup_size` is `Some`, but the \
`required_subgroup_size_stages` device property does not contain the \
shader stage of `entry_point`"
.into(),
vuids: &["VUID-VkPipelineShaderStageCreateInfo-pNext-02755"],
..Default::default()
}));
}
if !required_subgroup_size.is_power_of_two() {
return Err(Box::new(ValidationError {
context: "required_subgroup_size".into(),
problem: "is not a power of 2".into(),
vuids: &["VUID-VkPipelineShaderStageRequiredSubgroupSizeCreateInfo-requiredSubgroupSize-02760"],
..Default::default()
}));
}
if required_subgroup_size < properties.min_subgroup_size.unwrap_or(1) {
return Err(Box::new(ValidationError {
context: "required_subgroup_size".into(),
problem: "is less than the `min_subgroup_size` device limit".into(),
vuids: &["VUID-VkPipelineShaderStageRequiredSubgroupSizeCreateInfo-requiredSubgroupSize-02761"],
..Default::default()
}));
}
if required_subgroup_size > properties.max_subgroup_size.unwrap_or(128) {
return Err(Box::new(ValidationError {
context: "required_subgroup_size".into(),
problem: "is greater than the `max_subgroup_size` device limit".into(),
vuids: &["VUID-VkPipelineShaderStageRequiredSubgroupSizeCreateInfo-requiredSubgroupSize-02762"],
..Default::default()
}));
}
if matches!(
stage_enum,
ShaderStage::Compute | ShaderStage::Mesh | ShaderStage::Task
) && workgroup_size
> required_subgroup_size
.checked_mul(
properties
.max_compute_workgroup_subgroups
.unwrap_or_default(),
)
.unwrap_or(u32::MAX)
{
return Err(Box::new(ValidationError {
problem: "the product of the `local_size_x`, `local_size_y` and \
`local_size_z` of `entry_point` is greater than the the product \
of `required_subgroup_size` and the \
`max_compute_workgroup_subgroups` device limit"
.into(),
vuids: &["VUID-VkPipelineShaderStageCreateInfo-pNext-02756"],
..Default::default()
}));
}
}
// TODO:
// VUID-VkPipelineShaderStageCreateInfo-module-08987
Ok(())
}
}
vulkan_bitflags! {
#[non_exhaustive]
/// Flags specifying additional properties of a pipeline shader stage.
PipelineShaderStageCreateFlags = PipelineShaderStageCreateFlags(u32);
/* TODO: enable
// TODO: document
ALLOW_VARYING_SUBGROUP_SIZE = ALLOW_VARYING_SUBGROUP_SIZE
RequiresOneOf([
RequiresAllOf([APIVersion(V1_3)]),
RequiresAllOf([DeviceExtension(ext_subgroup_size_control)]),
]),
*/
/* TODO: enable
// TODO: document
REQUIRE_FULL_SUBGROUPS = REQUIRE_FULL_SUBGROUPS
RequiresOneOf([
RequiresAllOf([APIVersion(V1_3)]),
RequiresAllOf([DeviceExtension(ext_subgroup_size_control)]),
]),
*/
}
vulkan_enum! {
#[non_exhaustive]

File diff suppressed because it is too large Load Diff

View File

@ -1115,65 +1115,6 @@ impl ShaderInterface {
pub fn elements(&self) -> &[ShaderInterfaceEntry] {
self.elements.as_ref()
}
/// Checks whether the interface is potentially compatible with another one.
///
/// Returns `Ok` if the two interfaces are compatible.
#[inline]
pub fn matches(&self, other: &ShaderInterface) -> Result<(), Box<ValidationError>> {
if self.elements().len() != other.elements().len() {
return Err(Box::new(ValidationError {
problem: "the number of elements in the shader interfaces are not equal".into(),
..Default::default()
}));
}
for a in self.elements() {
let location_range = a.location..a.location + a.ty.num_locations();
for loc in location_range {
let b = match other
.elements()
.iter()
.find(|e| loc >= e.location && loc < e.location + e.ty.num_locations())
{
None => {
return Err(Box::new(ValidationError {
problem: format!(
"the second shader is missing an interface element at location {}",
loc
)
.into(),
..Default::default()
}));
}
Some(b) => b,
};
if a.ty != b.ty {
return Err(Box::new(ValidationError {
problem: format!(
"the interface element at location {} does not have the same type \
in both shaders",
loc
)
.into(),
..Default::default()
}));
}
// TODO: enforce this?
/*match (a.name, b.name) {
(Some(ref an), Some(ref bn)) => if an != bn { return false },
_ => ()
};*/
}
}
// NOTE: since we check that the number of elements is the same, we don't need to iterate
// over b's elements.
Ok(())
}
}
/// Entry of a shader interface definition.