blob: bd274cf6f2035e916abe1a108d7686b09585518f [file] [log] [blame]
/* Copyright (c) 2015-2023 The Khronos Group Inc.
* Copyright (c) 2015-2023 Valve Corporation
* Copyright (c) 2015-2023 LunarG, Inc.
* Copyright (C) 2015-2023 Google Inc.
* Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cassert>
#include <sstream>
#include <string>
#include <vector>
#include <vulkan/vk_enum_string_helper.h>
#include "core_validation.h"
#include "generated/spirv_grammar_helper.h"
#include "utils/shader_utils.h"
bool CoreChecks::ValidateInterfaceVertexInput(const PIPELINE_STATE &pipeline, const SPIRV_MODULE_STATE &module_state,
const EntryPoint &entrypoint, const Location &create_info_loc) const {
bool skip = false;
safe_VkPipelineVertexInputStateCreateInfo const *vi = pipeline.vertex_input_state->input_state;
const Location vi_loc = create_info_loc.dot(Field::pVertexInputState);
struct AttribInputPair {
const VkFormat *attribute_input = nullptr;
const Instruction *shader_input = nullptr;
uint32_t attribute_index = 0;
};
// For vertex input, we only need to care about Location.
// You are not allowed to offset into the Component words
// or have 2 variables in a location
std::map<uint32_t, AttribInputPair> location_map;
if (vi) {
for (uint32_t i = 0; i < vi->vertexAttributeDescriptionCount; ++i) {
// Vertex input attributes use VkFormat, but only to make use of how they define sizes, things such as
// depth/multi-plane/compressed will never be used here because they would mean nothing. So we can ensure these are
// "standard" color formats being used
const VkFormat format = vi->pVertexAttributeDescriptions[i].format;
const uint32_t format_size = vkuFormatElementSize(format);
// Vulkan Spec: Location is made up of 16 bytes, never can have 0 Locations
const uint32_t bytes_in_location = 16;
const uint32_t num_locations = ((format_size - 1) / bytes_in_location) + 1;
for (uint32_t j = 0; j < num_locations; ++j) {
const uint32_t index = vi->pVertexAttributeDescriptions[i].location + j;
location_map[index].attribute_input = &(vi->pVertexAttributeDescriptions[i].format);
location_map[index].attribute_index = i;
}
}
}
for (const auto *variable_ptr : entrypoint.user_defined_interface_variables) {
const auto &variable = *variable_ptr;
if ((variable.storage_class != spv::StorageClassInput)) {
continue; // not an input interface
}
// It is possible to have a struct block for the vertex input.
// All members of struct must all have Locations or none of them will.
// If the interface variable doesn't have the Locations, find them inside the struct members
if (!variable.type_struct_info) {
for (const auto &slot : variable.interface_slots) {
location_map[slot.Location()].shader_input = &variable.base_type;
}
} else if (variable.decorations.location != kInvalidSpirvValue) {
// Variable is decorated with Location
uint32_t location = variable.decorations.location;
for (uint32_t i = 0; i < variable.type_struct_info->members.size(); i++) {
const auto &member = variable.type_struct_info->members[i];
// can be 64-bit formats in the struct
const uint32_t num_locations = module_state.GetLocationsConsumedByType(member.id);
for (uint32_t j = 0; j < num_locations; ++j) {
location_map[location + j].shader_input = member.insn;
}
location += num_locations;
}
} else {
// Can't be nested so only need to look at first level of members
for (const auto &member : variable.type_struct_info->members) {
location_map[member.decorations->location].shader_input = member.insn;
}
}
}
for (const auto &location_it : location_map) {
const auto location = location_it.first;
const auto attribute_input = location_it.second.attribute_input;
const auto shader_input = location_it.second.shader_input;
if (attribute_input && !shader_input) {
skip |= LogPerformanceWarning(kVUID_Core_Shader_OutputNotConsumed, module_state.handle(), vi_loc,
"Vertex attribute at location %" PRIu32 " not consumed by vertex shader.", location);
} else if (!attribute_input && shader_input) {
skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-Input-07904", module_state.handle(),
vi_loc.dot(Field::pVertexAttributeDescriptions),
"does not have a Location %" PRIu32 " but vertex shader has an input variable at that Location.",
location);
} else if (attribute_input && shader_input) {
const VkFormat attribute_format = *attribute_input;
const auto attribute_type = GetFormatType(attribute_format);
const uint32_t var_base_type_id = shader_input->ResultId();
const auto var_numeric_type = module_state.GetNumericType(var_base_type_id);
// Type checking
if (!(attribute_type & var_numeric_type)) {
skip |=
LogError("VUID-VkGraphicsPipelineCreateInfo-Input-08733", module_state.handle(),
vi_loc.dot(Field::pVertexAttributeDescriptions, location_it.second.attribute_index).dot(Field::format),
"(%s) at Location %" PRIu32 " does not match vertex shader input type (%s).",
string_VkFormat(attribute_format), location, module_state.DescribeType(var_base_type_id).c_str());
} else {
// 64-bit can't be used if both the Vertex Attribute AND Shader Input Variable are both not 64-bit.
const bool attribute64 = vkuFormatIs64bit(attribute_format);
const bool shader64 = module_state.GetBaseTypeInstruction(var_base_type_id)->GetBitWidth() == 64;
if (attribute64 && !shader64) {
skip |= LogError(
"VUID-VkGraphicsPipelineCreateInfo-pVertexInputState-08929", module_state.handle(),
vi_loc.dot(Field::pVertexAttributeDescriptions, location_it.second.attribute_index).dot(Field::format),
"(%s) is a 64-bit format, but at Location %" PRIu32 " the vertex shader input is 32-bit type (%s).",
string_VkFormat(attribute_format), location, module_state.DescribeType(var_base_type_id).c_str());
} else if (!attribute64 && shader64) {
skip |= LogError(
"VUID-VkGraphicsPipelineCreateInfo-pVertexInputState-08930", module_state.handle(),
vi_loc.dot(Field::pVertexAttributeDescriptions, location_it.second.attribute_index).dot(Field::format),
"(%s) is a 64-bit format, but at Location %" PRIu32 " the vertex shader input is 64-bit type (%s).",
string_VkFormat(attribute_format), location, module_state.DescribeType(var_base_type_id).c_str());
} else if (attribute64 && shader64) {
// Unlike 32-bit, the components for 64-bit inputs have to match exactly
const uint32_t attribute_components = vkuFormatComponentCount(attribute_format);
const uint32_t input_components = module_state.GetNumComponentsInBaseType(shader_input);
if (attribute_components < input_components) {
skip |= LogError(
"VUID-VkGraphicsPipelineCreateInfo-pVertexInputState-09198", module_state.handle(),
vi_loc.dot(Field::pVertexAttributeDescriptions, location_it.second.attribute_index).dot(Field::format),
"(%s) is a %" PRIu32 "-wide 64-bit format, but at location %" PRIu32
" the vertex shader input is %" PRIu32
"-wide 64-bit type (%s). (64-bit vertex input don't have default values and require "
"components to match what is used in the shader)",
string_VkFormat(attribute_format), attribute_components, location, input_components,
module_state.DescribeType(var_base_type_id).c_str());
}
}
}
} else { // !attrib && !input
assert(false); // at least one exists in the map
}
}
return skip;
}
bool CoreChecks::ValidateInterfaceFragmentOutput(const PIPELINE_STATE &pipeline, const SPIRV_MODULE_STATE &module_state,
const EntryPoint &entrypoint, const Location &create_info_loc) const {
bool skip = false;
const auto *ms_state = pipeline.MultisampleState();
if (!pipeline.IsDynamic(VK_DYNAMIC_STATE_ALPHA_TO_COVERAGE_ENABLE_EXT) && ms_state && ms_state->alphaToCoverageEnable) {
// TODO - DualSource blend has two outputs at location zero, so Index == 0 is the one that's required.
// Currently lack support to test each index.
if (!entrypoint.has_alpha_to_coverage_variable && !pipeline.DualSourceBlending()) {
skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-alphaToCoverageEnable-08891", module_state.handle(),
create_info_loc.dot(Field::pMultisampleState).dot(Field::alphaToCoverageEnable),
"is VK_TRUE, but the fragment shader doesn't declare a variable that covers "
"Location 0, Component 3.");
}
}
return skip;
}
bool CoreChecks::ValidateBuiltinLimits(const SPIRV_MODULE_STATE &module_state, const EntryPoint &entrypoint,
const StageCreateInfo &create_info, const Location &loc) const {
bool skip = false;
// Currently all builtin tested are only found in fragment shaders
if (entrypoint.execution_model != spv::ExecutionModelFragment) {
return skip;
}
for (const auto *variable : entrypoint.built_in_variables) {
// Currently don't need to search in structs
// Handles both the input and output sampleMask
if (variable->decorations.builtin == spv::BuiltInSampleMask &&
variable->array_size > phys_dev_props.limits.maxSampleMaskWords) {
const char *vuid = create_info.pipeline ? "VUID-VkPipelineShaderStageCreateInfo-maxSampleMaskWords-00711"
: "VUID-VkShaderCreateInfoEXT-pCode-08451";
skip |= LogError(vuid, module_state.handle(), loc,
"The BuiltIns SampleMask array sizes is %" PRIu32
" which exceeds "
"maxSampleMaskWords of %" PRIu32 ".",
variable->array_size, phys_dev_props.limits.maxSampleMaskWords);
break;
}
}
return skip;
}
bool CoreChecks::ValidatePrimitiveTopology(const SPIRV_MODULE_STATE &module_state, const EntryPoint &entrypoint,
const StageCreateInfo &create_info, const Location &loc) const {
bool skip = false;
if (!create_info.pipeline || !create_info.pipeline->pre_raster_state || !create_info.pipeline->InputAssemblyState() ||
entrypoint.stage != VK_SHADER_STAGE_GEOMETRY_BIT) {
return skip;
}
const auto &pipeline = *create_info.pipeline;
bool has_tess = false;
VkPrimitiveTopology topology = pipeline.InputAssemblyState()->topology;
for (uint32_t i = 0; i < pipeline.stage_states.size(); i++) {
auto &stage_state = pipeline.stage_states[i];
const VkShaderStageFlagBits stage = stage_state.GetStage();
if (stage == VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT || stage == VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) {
has_tess = true;
if (stage == VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) {
topology = stage_state.entrypoint->execution_mode.primitive_topology;
}
}
}
VkPrimitiveTopology geom_topology = entrypoint.execution_mode.input_primitive_topology;
bool mismatch = false;
mismatch |= (topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST && geom_topology != VK_PRIMITIVE_TOPOLOGY_POINT_LIST);
mismatch |=
IsValueIn(topology, {VK_PRIMITIVE_TOPOLOGY_LINE_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP,
VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY}) &&
!IsValueIn(geom_topology,
{VK_PRIMITIVE_TOPOLOGY_LINE_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP,
VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY});
mismatch |= IsValueIn(topology, {VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY}) &&
!IsValueIn(geom_topology, {VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY});
if (mismatch) {
if (has_tess) {
skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pStages-00739", module_state.handle(), loc,
"SPIR-V (Geometry stage) expects input topology %s, but tessellation evaluation shader output topology is %s.",
string_VkPrimitiveTopology(geom_topology), string_VkPrimitiveTopology(topology));
} else {
skip |= LogError("VUID-VkGraphicsPipelineCreateInfo-pStages-00738", module_state.handle(), loc,
"SPIR-V (Geometry stage) expects input topology %s, but pipeline was created with primitive topology %s.",
string_VkPrimitiveTopology(geom_topology), string_VkPrimitiveTopology(topology));
}
}
return skip;
}
bool CoreChecks::ValidateShaderStageInputOutputLimits(const SPIRV_MODULE_STATE &module_state, VkShaderStageFlagBits stage,
const EntryPoint &entrypoint, const Location &loc) const {
if (stage == VK_SHADER_STAGE_COMPUTE_BIT || stage == VK_SHADER_STAGE_ALL_GRAPHICS || stage == VK_SHADER_STAGE_ALL) {
return false;
}
bool skip = false;
auto const &limits = phys_dev_props.limits;
const uint32_t num_vertices = entrypoint.execution_mode.output_vertices;
const uint32_t num_primitives = entrypoint.execution_mode.output_primitives;
const bool is_iso_lines = entrypoint.execution_mode.Has(ExecutionModeSet::iso_lines_bit);
const bool is_point_mode = entrypoint.execution_mode.Has(ExecutionModeSet::point_mode_bit);
// The max is a combiniation of both the user defined variables largest values
// and
// The total components used by built ins
const auto max_input_slot =
(entrypoint.max_input_slot_variable && entrypoint.max_input_slot) ? *entrypoint.max_input_slot : InterfaceSlot(0, 0, 0, 0);
const auto max_output_slot = (entrypoint.max_output_slot_variable && entrypoint.max_output_slot) ? *entrypoint.max_output_slot
: InterfaceSlot(0, 0, 0, 0);
const uint32_t total_input_components = max_input_slot.slot + entrypoint.builtin_input_components;
const uint32_t total_output_components = max_output_slot.slot + entrypoint.builtin_output_components;
switch (stage) {
case VK_SHADER_STAGE_VERTEX_BIT:
if (total_output_components >= limits.maxVertexOutputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Vertex stage) output interface variable (%s) along with %" PRIu32
" built-in components, "
"exceeds component limit maxVertexOutputComponents (%" PRIu32 ").",
max_output_slot.Describe().c_str(), entrypoint.builtin_output_components,
limits.maxVertexOutputComponents);
}
break;
case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT:
if (total_input_components >= limits.maxTessellationControlPerVertexInputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Tessellation control stage) input interface variable (%s) along with %" PRIu32
" built-in components, "
"exceeds component limit maxTessellationControlPerVertexInputComponents (%" PRIu32 ").",
max_input_slot.Describe().c_str(), entrypoint.builtin_input_components,
limits.maxTessellationControlPerVertexInputComponents);
}
if (entrypoint.max_input_slot_variable) {
if (entrypoint.max_input_slot_variable->is_patch &&
total_output_components >= limits.maxTessellationControlPerPatchOutputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Tessellation control stage) output interface variable (%s) along with %" PRIu32
" built-in components, "
"exceeds component limit maxTessellationControlPerPatchOutputComponents (%" PRIu32 ").",
max_output_slot.Describe().c_str(), entrypoint.builtin_output_components,
limits.maxTessellationControlPerPatchOutputComponents);
}
if (!entrypoint.max_input_slot_variable->is_patch &&
total_output_components >= limits.maxTessellationControlPerVertexOutputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Tessellation control stage) output interface variable (%s) along with %" PRIu32
" built-in components, "
"exceeds component limit maxTessellationControlPerVertexOutputComponents (%" PRIu32 ").",
max_output_slot.Describe().c_str(), entrypoint.builtin_output_components,
limits.maxTessellationControlPerVertexOutputComponents);
}
}
break;
case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT:
if (total_input_components >= limits.maxTessellationEvaluationInputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Tessellation evaluation stage) input interface variable (%s) along with %" PRIu32
" built-in components, "
"exceeds component limit maxTessellationEvaluationInputComponents (%" PRIu32 ").",
max_input_slot.Describe().c_str(), entrypoint.builtin_input_components,
limits.maxTessellationEvaluationInputComponents);
}
if (total_output_components >= limits.maxTessellationEvaluationOutputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Tessellation evaluation stage) output interface variable (%s) along with %" PRIu32
" built-in components, "
"exceeds component limit maxTessellationEvaluationOutputComponents (%" PRIu32 ").",
max_output_slot.Describe().c_str(), entrypoint.builtin_output_components,
limits.maxTessellationEvaluationOutputComponents);
}
// Portability validation
if (IsExtEnabled(device_extensions.vk_khr_portability_subset)) {
if (is_iso_lines && (VK_FALSE == enabled_features.portability_subset_features.tessellationIsolines)) {
skip |= LogError("VUID-RuntimeSpirv-tessellationShader-06326", module_state.handle(), loc,
"(portability error) SPIR-V (Tessellation evaluation stage)"
" is using abstract patch type IsoLines, but this is not supported on this platform.");
}
if (is_point_mode && (VK_FALSE == enabled_features.portability_subset_features.tessellationPointMode)) {
skip |= LogError("VUID-RuntimeSpirv-tessellationShader-06327", module_state.handle(), loc,
"(portability error) SPIR-V (Tessellation evaluation stage)"
" is using abstract patch type PointMode, but this is not supported on this platform.");
}
}
break;
case VK_SHADER_STAGE_GEOMETRY_BIT:
if (total_input_components >= limits.maxGeometryInputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Geometry stage) input interface variable (%s) along with %" PRIu32
" built-in components, "
"exceeds component limit maxGeometryInputComponents (%" PRIu32 ").",
max_input_slot.Describe().c_str(), entrypoint.builtin_input_components,
limits.maxGeometryInputComponents);
}
if (total_output_components >= limits.maxGeometryOutputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Geometry stage) output interface variable (%s) along with %" PRIu32
" built-in components, "
"exceeds component limit maxGeometryOutputComponents (%" PRIu32 ").",
max_output_slot.Describe().c_str(), entrypoint.builtin_output_components,
limits.maxGeometryOutputComponents);
}
break;
case VK_SHADER_STAGE_FRAGMENT_BIT:
if (total_input_components >= limits.maxFragmentInputComponents) {
skip |= LogError("VUID-RuntimeSpirv-Location-06272", module_state.handle(), loc,
"SPIR-V (Fragment stage) input interface variable (%s) along with %" PRIu32
" built-in components, "
"exceeds component limit maxFragmentInputComponents (%" PRIu32 ").",
max_input_slot.Describe().c_str(), entrypoint.builtin_input_components,
limits.maxFragmentInputComponents);
}
break;
case VK_SHADER_STAGE_RAYGEN_BIT_KHR:
case VK_SHADER_STAGE_ANY_HIT_BIT_KHR:
case VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR:
case VK_SHADER_STAGE_MISS_BIT_KHR:
case VK_SHADER_STAGE_INTERSECTION_BIT_KHR:
case VK_SHADER_STAGE_CALLABLE_BIT_KHR:
case VK_SHADER_STAGE_TASK_BIT_EXT:
break;
// Shader stage is an alias, but the ExecutionModel is not
case VK_SHADER_STAGE_MESH_BIT_EXT:
if (entrypoint.execution_model == spv::ExecutionModelMeshNV) {
if (num_vertices > phys_dev_ext_props.mesh_shader_props_nv.maxMeshOutputVertices) {
skip |= LogError("VUID-RuntimeSpirv-MeshNV-07113", module_state.handle(), loc,
"SPIR-V (Mesh stage) output vertices count exceeds the "
"maxMeshOutputVertices of %" PRIu32 " by %" PRIu32 ".",
phys_dev_ext_props.mesh_shader_props_nv.maxMeshOutputVertices,
num_vertices - phys_dev_ext_props.mesh_shader_props_nv.maxMeshOutputVertices);
}
if (num_primitives > phys_dev_ext_props.mesh_shader_props_nv.maxMeshOutputPrimitives) {
skip |= LogError("VUID-RuntimeSpirv-MeshNV-07114", module_state.handle(), loc,
"SPIR-V (Mesh stage) output primitives count exceeds the "
"maxMeshOutputPrimitives of %" PRIu32 " by %" PRIu32 ".",
phys_dev_ext_props.mesh_shader_props_nv.maxMeshOutputPrimitives,
num_primitives - phys_dev_ext_props.mesh_shader_props_nv.maxMeshOutputPrimitives);
}
} else if (entrypoint.execution_model == spv::ExecutionModelMeshEXT) {
if (num_vertices > phys_dev_ext_props.mesh_shader_props_ext.maxMeshOutputVertices) {
skip |= LogError("VUID-RuntimeSpirv-MeshEXT-07115", module_state.handle(), loc,
"SPIR-V (Mesh stage) output vertices count exceeds the "
"maxMeshOutputVertices of %" PRIu32 " by %" PRIu32 ".",
phys_dev_ext_props.mesh_shader_props_ext.maxMeshOutputVertices,
num_vertices - phys_dev_ext_props.mesh_shader_props_ext.maxMeshOutputVertices);
}
if (num_primitives > phys_dev_ext_props.mesh_shader_props_ext.maxMeshOutputPrimitives) {
skip |= LogError("VUID-RuntimeSpirv-MeshEXT-07116", module_state.handle(), loc,
"SPIR-V (Mesh stage) output primitives count exceeds the "
"maxMeshOutputPrimitives of %" PRIu32 " by %" PRIu32 ".",
phys_dev_ext_props.mesh_shader_props_ext.maxMeshOutputPrimitives,
num_primitives - phys_dev_ext_props.mesh_shader_props_ext.maxMeshOutputPrimitives);
}
}
break;
default:
assert(false); // This should never happen
}
return skip;
}
bool CoreChecks::ValidateInterfaceBetweenStages(const SPIRV_MODULE_STATE &producer, const EntryPoint &producer_entrypoint,
const SPIRV_MODULE_STATE &consumer, const EntryPoint &consumer_entrypoint,
const Location &create_info_loc) const {
bool skip = false;
if (producer_entrypoint.has_passthrough) {
return skip; // PassthroughNV doesn't have to do Location matching
}
const VkShaderStageFlagBits producer_stage = producer_entrypoint.stage;
const VkShaderStageFlagBits consumer_stage = consumer_entrypoint.stage;
// build up a mapping of which slots are used and then go through it and look for gaps
struct ComponentInfo {
const StageInteraceVariable *output = nullptr;
uint32_t output_type = 0;
uint32_t output_width = 0;
const StageInteraceVariable *input = nullptr;
uint32_t input_type = 0;
uint32_t input_width = 0;
};
// <Location, Components[4]> (only 4 components in a Location)
vvl::unordered_map<uint32_t, std::array<ComponentInfo, 4>> slot_map;
for (const auto &interface_slot : producer_entrypoint.output_interface_slots) {
auto &slot = slot_map[interface_slot.first.Location()][interface_slot.first.Component()];
if (interface_slot.second->nested_struct || interface_slot.second->physical_storage_buffer) {
return skip; // TODO workaround
}
slot.output = interface_slot.second;
slot.output_type = interface_slot.first.type;
slot.output_width = interface_slot.first.bit_width;
}
for (const auto &interface_slot : consumer_entrypoint.input_interface_slots) {
auto &slot = slot_map[interface_slot.first.Location()][interface_slot.first.Component()];
if (interface_slot.second->nested_struct || interface_slot.second->physical_storage_buffer) {
return skip; // TODO workaround
}
slot.input = interface_slot.second;
slot.input_type = interface_slot.first.type;
slot.input_width = interface_slot.first.bit_width;
}
for (const auto &slot : slot_map) {
// Found that sometimes there is a big mismatch and printing out EVERY slot adds a lot of noise
if (skip) break;
const uint32_t location = slot.first;
for (uint32_t component = 0; component < 4; component++) {
const auto &component_info = slot.second[component];
const auto *input_var = component_info.input;
const auto *output_var = component_info.output;
if ((input_var == nullptr) && (output_var == nullptr)) {
continue; // both empty
} else if ((input_var != nullptr) && (output_var != nullptr)) {
// if matched, need to check type
// Only the OpType has to match, signed vs unsigned in not important
if ((component_info.output_type != component_info.input_type) ||
(component_info.output_width != component_info.input_width)) {
const LogObjectList objlist(producer.handle(), consumer.handle());
skip |=
LogError("VUID-RuntimeSpirv-OpEntryPoint-07754", objlist, create_info_loc,
"(SPIR-V Interface) Type mismatch on Location %" PRIu32 " Component %" PRIu32
", between\n%s stage:\n%s\n%s stage:\n%s\n",
location, component, string_VkShaderStageFlagBits(producer_stage),
producer.DescribeType(output_var->type_id).c_str(), string_VkShaderStageFlagBits(consumer_stage),
consumer.DescribeType(input_var->type_id).c_str());
}
// Tessellation needs to match Patch vs Vertex
if ((producer_stage == VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT) &&
(consumer_stage == VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) &&
(input_var->is_patch != output_var->is_patch)) {
const LogObjectList objlist(producer.handle(), consumer.handle());
skip |= LogError("VUID-RuntimeSpirv-OpVariable-08746", objlist, create_info_loc,
"(SPIR-V Interface) at Location %" PRIu32 " Component %" PRIu32
" Tessellation Control is %s while Tessellation Evaluation is %s",
location, component, input_var->is_patch ? "patch" : "vertex",
output_var->is_patch ? "patch" : "vertex");
}
// If using maintenance4 need to check Vectors incase different sizes
if (!enabled_features.core13.maintenance4 && (output_var->base_type.Opcode() == spv::OpTypeVector) &&
(input_var->base_type.Opcode() == spv::OpTypeVector)) {
// Note the "Component Count" in the VU refers to OpTypeVector's operand and NOT the "Component slot"
const uint32_t output_vec_size = output_var->base_type.Word(3);
const uint32_t input_vec_size = input_var->base_type.Word(3);
if (output_vec_size > input_vec_size) {
const LogObjectList objlist(producer.handle(), consumer.handle());
skip |= LogError("VUID-RuntimeSpirv-maintenance4-06817", objlist, create_info_loc,
"(SPIR-V Interface) starting at Location %" PRIu32 " Component %" PRIu32
" the Output (%s) has a Vec%" PRIu32 " while Input (%s) as a Vec%" PRIu32
". Enable VK_KHR_maintenance4 device extension to allow relaxed interface matching "
"between input and output vectors.",
location, component, string_VkShaderStageFlagBits(producer_stage), output_vec_size,
string_VkShaderStageFlagBits(consumer_stage), input_vec_size);
break; // Only need to report for the first component found
}
}
} else if ((input_var == nullptr) && (output_var != nullptr)) {
// Missing input slot
// It is not an error if a stage does not consume all outputs from the previous stage
// The values will be undefined, but still legal
// Don't give any warning if maintenance4 with vectors
if (!enabled_features.core13.maintenance4 && (output_var->base_type.Opcode() != spv::OpTypeVector)) {
const LogObjectList objlist(producer.handle(), consumer.handle());
skip |= LogPerformanceWarning(kVUID_Core_Shader_OutputNotConsumed, objlist, create_info_loc,
"(SPIR-V Interface) %s declared to output location %" PRIu32 " Component %" PRIu32
" but is not an Input declared by %s.",
string_VkShaderStageFlagBits(producer_stage), location, component,
string_VkShaderStageFlagBits(consumer_stage));
}
} else if ((input_var != nullptr) && (output_var == nullptr)) {
// Missing output slot
if ((consumer_stage & (VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT |
VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT)) &&
(input_var->base_type.Opcode() == spv::OpTypeArray)) {
break; // When going inbetween Tessellation or Geometry, array size can be different
}
const LogObjectList objlist(producer.handle(), consumer.handle());
skip |= LogError("VUID-RuntimeSpirv-OpEntryPoint-08743", objlist, create_info_loc,
"(SPIR-V Interface) %s declared input at Location %" PRIu32 " Component %" PRIu32
" but it is not an Output declared in %s",
string_VkShaderStageFlagBits(consumer_stage), location, component,
string_VkShaderStageFlagBits(producer_stage));
break; // Only need to report for the first component found
}
}
}
// Need to check the BuiltIn interface (if not going into Fragment)
if (consumer_stage == VK_SHADER_STAGE_FRAGMENT_BIT) {
return skip;
}
std::vector<uint32_t> input_builtins_block;
std::vector<uint32_t> output_builtins_block;
for (const auto *variable : producer_entrypoint.built_in_variables) {
if (variable->storage_class == spv::StorageClassOutput && !variable->builtin_block.empty()) {
output_builtins_block = variable->builtin_block;
break;
}
}
for (const auto *variable : consumer_entrypoint.built_in_variables) {
if (variable->storage_class == spv::StorageClassInput && !variable->builtin_block.empty()) {
input_builtins_block = variable->builtin_block;
break;
}
}
bool mismatch = false;
if (input_builtins_block.empty() || output_builtins_block.empty()) {
// TODO - Nothing about this in spec, need to add language to confirm this is correct
return skip;
} else if (input_builtins_block.size() != output_builtins_block.size()) {
mismatch = true;
} else {
for (size_t i = 0; i < input_builtins_block.size(); i++) {
const uint32_t input_builtin = input_builtins_block[i];
const uint32_t output_builtin = output_builtins_block[i];
if (input_builtin == kInvalidSpirvValue || output_builtin == kInvalidSpirvValue) {
continue; // some stages (TessControl -> TessEval) can have legal block vs non-block mistmatch
} else if (input_builtin != output_builtin) {
mismatch = true;
}
}
}
if (mismatch) {
std::stringstream msg;
msg << string_VkShaderStageFlagBits(producer_stage) << " Output Block {\n";
for (size_t i = 0; i < output_builtins_block.size(); i++) {
msg << "\t" << i << ": " << string_SpvBuiltIn(output_builtins_block[i]) << "\n";
}
msg << "}\n";
msg << string_VkShaderStageFlagBits(consumer_stage) << " Input Block {\n";
for (size_t i = 0; i < input_builtins_block.size(); i++) {
msg << "\t" << i << ": " << string_SpvBuiltIn(input_builtins_block[i]) << "\n";
}
msg << "}\n";
const LogObjectList objlist(producer.handle(), consumer.handle());
skip |= LogError("VUID-RuntimeSpirv-OpVariable-08746", objlist, create_info_loc,
"(SPIR-V Interface) Mistmatch in BuiltIn blocks:\n %s", msg.str().c_str());
}
return skip;
}
// Validate that the shaders used by the given pipeline and store the active_slots
// that are actually used by the pipeline into pPipeline->active_slots
bool CoreChecks::ValidateGraphicsPipelineShaderState(const PIPELINE_STATE &pipeline, const Location &create_info_loc) const {
bool skip = false;
if (!(pipeline.pre_raster_state || pipeline.fragment_shader_state)) {
// Only validate pipelines that contain shader stages
return skip;
}
const PipelineStageState *vertex_stage = nullptr, *fragment_stage = nullptr;
for (uint32_t i = 0; i < pipeline.stage_states.size(); i++) {
auto &stage_state = pipeline.stage_states[i];
const VkShaderStageFlagBits stage = stage_state.GetStage();
// Only validate the shader state once when added, not again when linked
if ((stage & pipeline.linking_shaders) == 0) {
StageCreateInfo stage_create_info(&pipeline);
skip |= ValidatePipelineShaderStage(stage_create_info, stage_state, create_info_loc.dot(Field::pStages, i));
}
if (stage == VK_SHADER_STAGE_VERTEX_BIT) {
vertex_stage = &stage_state;
}
if (stage == VK_SHADER_STAGE_FRAGMENT_BIT) {
fragment_stage = &stage_state;
}
}
// if the shader stages are no good individually, cross-stage validation is pointless.
if (skip) return true;
if (pipeline.vertex_input_state && vertex_stage && vertex_stage->entrypoint && vertex_stage->spirv_state &&
!pipeline.IsDynamic(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT)) {
skip |=
ValidateInterfaceVertexInput(pipeline, *vertex_stage->spirv_state.get(), *vertex_stage->entrypoint, create_info_loc);
}
if (pipeline.fragment_shader_state && fragment_stage && fragment_stage->entrypoint && fragment_stage->spirv_state) {
skip |= ValidateInterfaceFragmentOutput(pipeline, *fragment_stage->spirv_state.get(), *fragment_stage->entrypoint,
create_info_loc);
}
for (size_t i = 1; i < pipeline.stage_states.size(); i++) {
const auto &producer = pipeline.stage_states[i - 1];
const auto &consumer = pipeline.stage_states[i];
const std::shared_ptr<const SPIRV_MODULE_STATE> &producer_spirv =
producer.spirv_state ? producer.spirv_state : producer.module_state->spirv;
const std::shared_ptr<const SPIRV_MODULE_STATE> &consumer_spirv =
consumer.spirv_state ? consumer.spirv_state : consumer.module_state->spirv;
assert(producer.module_state);
if (&producer == fragment_stage) {
break;
}
if (consumer_spirv && producer_spirv && consumer.entrypoint && producer.entrypoint) {
skip |= ValidateInterfaceBetweenStages(*producer_spirv.get(), *producer.entrypoint, *consumer_spirv.get(),
*consumer.entrypoint, create_info_loc);
}
}
return skip;
}