layers: Validate shader stage input/output limits
This addition adds validation for the following shader stage limits:
- maxVertexOutputComponents
- maxGeometryInputComponents
- maxGeometryOutputComponents
- maxTessellationControlPerVertexInputComponents
- maxTessellationControlPerVertexOutputComponents
- maxTessellationEvaluationInputComponents
- maxTessellationEvaluationInputComponents
- maxFragmentInputComponents
These are all specified in the VkPhysicalDeviceLimits structure.
These checks do only adhere to the Component-decoration
side of these limits. I.e. if maxVertexOutputComponents=128, it's not
possible to pass 128 individual floats (each with its own location), as
there is also a Location-decoration limit:
maxVertexOutputComponents/4=32. Yet, these 128 floats can be specified
to occupy different components of these 32 locations using the
Component-decoration. Thus, the validation performed by this addition
is still valid, even though it does not consider the number of
locations used. (See section 14.1.14/15 of the spec)
A float that is specified to be 64-bit, counts as 2 components,
while all 32-bit (or less) count as 1.
Precision qualifiers are disregarded as SPIR-V spec 2.14 states
"All loads and stores involving relaxed precision still
read and write 32 bits of data, respectively.".
Calculate numComp using built-in decorations
- All checks are performed by checking whether or not a variable is
built-in. If it is, then the components for said variable
(the variable as a whole) are disregarded.
- Added back tests for tessellation stages.
- All tests passed locally, and clang-format has been applied,
so Travis should pass.
The number of components is calculated using built-in decorations
- All checks are performed by checking whether or not a variable is
built-in. If it is, then the components for said variable
(the variable as a whole) are disregarded.
- Added back tests for tessellation stages.
- All tests passed locally, and clang-format has been applied,
so Travis should pass.
Change-Id: I19a39bff3c6f03b6b5c94266b21044e02d762582
diff --git a/layers/shader_validation.cpp b/layers/shader_validation.cpp
index 3edf7c3..6495401 100644
--- a/layers/shader_validation.cpp
+++ b/layers/shader_validation.cpp
@@ -21,6 +21,7 @@
#include <cinttypes>
#include <cassert>
+#include <chrono>
#include <vector>
#include <unordered_map>
#include <string>
@@ -431,6 +432,54 @@
}
}
+static unsigned GetComponentsConsumedByType(shader_module const *src, unsigned type, bool strip_array_level) {
+ auto insn = src->get_def(type);
+ assert(insn != src->end());
+
+ switch (insn.opcode()) {
+ case spv::OpTypePointer:
+ // See through the ptr -- this is only ever at the toplevel for graphics shaders we're never actually passing
+ // pointers around.
+ return GetComponentsConsumedByType(src, insn.word(3), strip_array_level);
+ case spv::OpTypeStruct: {
+ uint32_t sum = 0;
+ for (uint32_t i = 2; i < insn.len(); i++) { // i=2 to skip word(0) and word(1)=ID of struct
+ sum += GetComponentsConsumedByType(src, insn.word(i), false);
+ }
+ return sum;
+ }
+ case spv::OpTypeArray: {
+ uint32_t sum = 0;
+ for (uint32_t i = 2; i < insn.len(); i++) {
+ sum += GetComponentsConsumedByType(src, insn.word(i), false);
+ }
+ return sum;
+ }
+ case spv::OpTypeMatrix:
+ // Num locations is the dimension * element size
+ return insn.word(3) * GetComponentsConsumedByType(src, insn.word(2), false);
+ case spv::OpTypeVector: {
+ auto scalar_type = src->get_def(insn.word(2));
+ auto bit_width =
+ (scalar_type.opcode() == spv::OpTypeInt || scalar_type.opcode() == spv::OpTypeFloat) ? scalar_type.word(2) : 32;
+ // One component is 32-bit
+ return (bit_width * insn.word(3) + 31) / 32;
+ }
+ case spv::OpTypeFloat: {
+ auto bit_width = insn.word(2);
+ return (bit_width + 31) / 32;
+ }
+ case spv::OpTypeInt: {
+ auto bit_width = insn.word(2);
+ return (bit_width + 31) / 32;
+ }
+ case spv::OpConstant:
+ return GetComponentsConsumedByType(src, insn.word(1), false);
+ default:
+ return 0;
+ }
+}
+
static unsigned GetLocationsConsumedByFormat(VkFormat format) {
switch (format) {
case VK_FORMAT_R64G64B64A64_SFLOAT:
@@ -1529,6 +1578,222 @@
return skip;
}
+static bool VariableIsBuiltIn(shader_module const *src, const uint32_t ID, std::vector<uint32_t> const &builtInBlockIDs,
+ std::vector<uint32_t> const &builtInIDs) {
+ auto insn = src->get_def(ID);
+
+ switch (insn.opcode()) {
+ case spv::OpVariable: {
+ // First check if the variable is a "pure" built-in type, e.g. gl_ViewportIndex
+ uint32_t ID = insn.word(2);
+ for (auto builtInID : builtInIDs) {
+ if (ID == builtInID) {
+ return true;
+ }
+ }
+
+ VariableIsBuiltIn(src, insn.word(1), builtInBlockIDs, builtInIDs);
+ break;
+ }
+ case spv::OpTypePointer:
+ VariableIsBuiltIn(src, insn.word(3), builtInBlockIDs, builtInIDs);
+ break;
+ case spv::OpTypeArray:
+ VariableIsBuiltIn(src, insn.word(2), builtInBlockIDs, builtInIDs);
+ break;
+ case spv::OpTypeStruct: {
+ uint32_t ID = insn.word(1); // We only need to check the first member as either all will be, or none will be built-in
+ for (auto builtInBlockID : builtInBlockIDs) {
+ if (ID == builtInBlockID) {
+ return true;
+ }
+ }
+ return false;
+ }
+ default:
+ return false;
+ }
+
+ return false;
+}
+
+static bool ValidateShaderStageInputOutputLimits(layer_data *dev_data, shader_module const *src,
+ VkPipelineShaderStageCreateInfo const *pStage, PIPELINE_STATE *pipeline) {
+ if (pStage->stage == VK_SHADER_STAGE_COMPUTE_BIT || pStage->stage == VK_SHADER_STAGE_ALL_GRAPHICS ||
+ pStage->stage == VK_SHADER_STAGE_ALL) {
+ return false;
+ }
+
+ bool skip = false;
+ auto const &properties = GetPhysDevProperties(dev_data);
+ auto const report_data = GetReportData(dev_data);
+
+ std::vector<uint32_t> builtInBlockIDs;
+ std::vector<uint32_t> builtInIDs;
+ struct Variable {
+ uint32_t baseTypePtrID;
+ uint32_t ID;
+ uint32_t storageClass;
+ };
+ std::vector<Variable> variables;
+
+ for (auto insn : *src) {
+ switch (insn.opcode()) {
+ // Find all built-in member decorations
+ case spv::OpMemberDecorate:
+ if (insn.word(3) == spv::DecorationBuiltIn) {
+ builtInBlockIDs.push_back(insn.word(1));
+ }
+ break;
+ // Find all built-in decorations
+ case spv::OpDecorate:
+ switch (insn.word(2)) {
+ case spv::DecorationBlock: {
+ uint32_t blockID = insn.word(1);
+ for (auto builtInBlockID : builtInBlockIDs) {
+ // Check if one of the members of the block are built-in -> the block is built-in
+ if (blockID == builtInBlockID) {
+ builtInIDs.push_back(blockID);
+ break;
+ }
+ }
+ break;
+ }
+ case spv::DecorationBuiltIn:
+ builtInIDs.push_back(insn.word(1));
+ break;
+ default:
+ break;
+ }
+ break;
+ // Find all input and output variables
+ case spv::OpVariable: {
+ Variable var = {};
+ var.storageClass = insn.word(3);
+ if (var.storageClass == spv::StorageClassInput || var.storageClass == spv::StorageClassOutput) {
+ var.baseTypePtrID = insn.word(1);
+ var.ID = insn.word(2);
+ variables.push_back(var);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ uint32_t numCompIn = 0, numCompOut = 0;
+ for (auto &var : variables) {
+ // Check the variable's ID
+ if (VariableIsBuiltIn(src, var.ID, builtInBlockIDs, builtInIDs)) {
+ continue;
+ }
+ // Check the variable's type's ID - e.g. gl_PerVertex is made of basic types, not built-in types
+ if (VariableIsBuiltIn(src, src->get_def(var.baseTypePtrID).word(3), builtInBlockIDs, builtInIDs)) {
+ continue;
+ }
+
+ if (var.storageClass == spv::StorageClassInput) {
+ numCompIn += GetComponentsConsumedByType(src, var.baseTypePtrID, false);
+ } else { // var.storageClass == spv::StorageClassOutput
+ numCompOut += GetComponentsConsumedByType(src, var.baseTypePtrID, false);
+ }
+ }
+
+ switch (pStage->stage) {
+ case VK_SHADER_STAGE_VERTEX_BIT:
+ if (numCompOut > properties->properties.limits.maxVertexOutputComponents) {
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT,
+ HandleToUint64(pipeline->pipeline), kVUID_Core_Shader_ExceedDeviceLimit,
+ "Invalid Pipeline CreateInfo State: Vertex shader exceeds "
+ "VkPhysicalDeviceLimits::maxVertexOutputComponents of %u "
+ "components by %u components",
+ properties->properties.limits.maxVertexOutputComponents,
+ numCompOut - properties->properties.limits.maxVertexOutputComponents);
+ }
+ break;
+
+ case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT:
+ if (numCompIn > properties->properties.limits.maxTessellationControlPerVertexInputComponents) {
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT,
+ HandleToUint64(pipeline->pipeline), kVUID_Core_Shader_ExceedDeviceLimit,
+ "Invalid Pipeline CreateInfo State: Tessellation control shader exceeds "
+ "VkPhysicalDeviceLimits::maxTessellationControlPerVertexInputComponents of %u "
+ "components by %u components",
+ properties->properties.limits.maxTessellationControlPerVertexInputComponents,
+ numCompIn - properties->properties.limits.maxTessellationControlPerVertexInputComponents);
+ }
+ if (numCompOut > properties->properties.limits.maxTessellationControlPerVertexOutputComponents) {
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT,
+ HandleToUint64(pipeline->pipeline), kVUID_Core_Shader_ExceedDeviceLimit,
+ "Invalid Pipeline CreateInfo State: Tessellation control shader exceeds "
+ "VkPhysicalDeviceLimits::maxTessellationControlPerVertexOutputComponents of %u "
+ "components by %u components",
+ properties->properties.limits.maxTessellationControlPerVertexOutputComponents,
+ numCompOut - properties->properties.limits.maxTessellationControlPerVertexOutputComponents);
+ }
+ break;
+
+ case VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT:
+ if (numCompIn > properties->properties.limits.maxTessellationEvaluationInputComponents) {
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT,
+ HandleToUint64(pipeline->pipeline), kVUID_Core_Shader_ExceedDeviceLimit,
+ "Invalid Pipeline CreateInfo State: Tessellation evaluation shader exceeds "
+ "VkPhysicalDeviceLimits::maxTessellationEvaluationInputComponents of %u "
+ "components by %u components",
+ properties->properties.limits.maxTessellationEvaluationInputComponents,
+ numCompIn - properties->properties.limits.maxTessellationEvaluationInputComponents);
+ }
+ if (numCompOut > properties->properties.limits.maxTessellationEvaluationOutputComponents) {
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT,
+ HandleToUint64(pipeline->pipeline), kVUID_Core_Shader_ExceedDeviceLimit,
+ "Invalid Pipeline CreateInfo State: Tessellation evaluation shader exceeds "
+ "VkPhysicalDeviceLimits::maxTessellationEvaluationOutputComponents of %u "
+ "components by %u components",
+ properties->properties.limits.maxTessellationEvaluationOutputComponents,
+ numCompOut - properties->properties.limits.maxTessellationEvaluationOutputComponents);
+ }
+ break;
+
+ case VK_SHADER_STAGE_GEOMETRY_BIT:
+ if (numCompIn > properties->properties.limits.maxGeometryInputComponents) {
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT,
+ HandleToUint64(pipeline->pipeline), kVUID_Core_Shader_ExceedDeviceLimit,
+ "Invalid Pipeline CreateInfo State: Geometry shader exceeds "
+ "VkPhysicalDeviceLimits::maxGeometryInputComponents of %u "
+ "components by %u components",
+ properties->properties.limits.maxGeometryInputComponents,
+ numCompIn - properties->properties.limits.maxGeometryInputComponents);
+ }
+ if (numCompOut > properties->properties.limits.maxGeometryOutputComponents) {
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT,
+ HandleToUint64(pipeline->pipeline), kVUID_Core_Shader_ExceedDeviceLimit,
+ "Invalid Pipeline CreateInfo State: Geometry shader exceeds "
+ "VkPhysicalDeviceLimits::maxGeometryOutputComponents of %u "
+ "components by %u components",
+ properties->properties.limits.maxGeometryOutputComponents,
+ numCompOut - properties->properties.limits.maxGeometryOutputComponents);
+ }
+ break;
+
+ case VK_SHADER_STAGE_FRAGMENT_BIT:
+ if (numCompIn > properties->properties.limits.maxFragmentInputComponents) {
+ skip |= log_msg(report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT,
+ HandleToUint64(pipeline->pipeline), kVUID_Core_Shader_ExceedDeviceLimit,
+ "Invalid Pipeline CreateInfo State: Fragment shader exceeds "
+ "VkPhysicalDeviceLimits::maxFragmentInputComponents of %u "
+ "components by %u components",
+ properties->properties.limits.maxFragmentInputComponents,
+ numCompIn - properties->properties.limits.maxFragmentInputComponents);
+ }
+ break;
+
+ default:
+ assert(false); // This should never happen
+ }
+ return skip;
+}
+
static uint32_t DescriptorTypeToReqs(shader_module const *module, uint32_t type_id) {
auto type = module->get_def(type_id);
@@ -1717,7 +1982,7 @@
// Validate shader capabilities against enabled device features
skip |= ValidateShaderCapabilities(dev_data, module, pStage->stage, has_writable_descriptor);
-
+ skip |= ValidateShaderStageInputOutputLimits(dev_data, module, pStage, pipeline);
skip |= ValidateSpecializationOffsets(report_data, pStage);
skip |= ValidatePushConstantUsage(report_data, pipeline->pipeline_layout.push_constant_ranges.get(), module, accessible_ids,
pStage->stage);
@@ -2028,4 +2293,4 @@
*spirv_valid = (spv_valid == SPV_SUCCESS);
return skip;
-}
+}
\ No newline at end of file