diff options
author | Spencer Fricke <spencerfricke@gmail.com> | 2022-09-23 18:06:46 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-23 18:06:46 +0300 |
commit | b53d7a8affabb230a47fb4123b040573a3062d4a (patch) | |
tree | 17ad86d1bfe312ac8a0d4b8fc4b6f4185287b3ae | |
parent | 265b455c996f7314ff995ecc3f5e3548365dbe87 (diff) |
spirv-val: Add initial SPV_EXT_mesh_shader validation (#4924)
* Move TaskEXT check to OpEmitMeshTasksEXT
* Add MeshNV for Execution Model alias
-rw-r--r-- | Android.mk | 1 | ||||
-rw-r--r-- | BUILD.gn | 1 | ||||
-rw-r--r-- | source/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/val/validate.cpp | 14 | ||||
-rw-r--r-- | source/val/validate.h | 3 | ||||
-rw-r--r-- | source/val/validate_cfg.cpp | 6 | ||||
-rw-r--r-- | source/val/validate_memory.cpp | 13 | ||||
-rw-r--r-- | source/val/validate_mesh_shading.cpp | 123 | ||||
-rw-r--r-- | source/val/validate_mode_setting.cpp | 47 | ||||
-rw-r--r-- | source/val/validate_ray_tracing.cpp | 11 | ||||
-rw-r--r-- | source/val/validation_state.cpp | 17 | ||||
-rw-r--r-- | test/val/val_id_test.cpp | 4 | ||||
-rw-r--r-- | test/val/val_memory_test.cpp | 49 | ||||
-rw-r--r-- | test/val/val_mesh_shading_test.cpp | 509 |
14 files changed, 788 insertions, 11 deletions
diff --git a/Android.mk b/Android.mk index c32732d5c..80c61b080 100644 --- a/Android.mk +++ b/Android.mk @@ -61,6 +61,7 @@ SPVTOOLS_SRC_FILES := \ source/val/validate_instruction.cpp \ source/val/validate_memory.cpp \ source/val/validate_memory_semantics.cpp \ + source/val/validate_mesh_shading.cpp \ source/val/validate_misc.cpp \ source/val/validate_mode_setting.cpp \ source/val/validate_layout.cpp \ @@ -526,6 +526,7 @@ static_library("spvtools_val") { "source/val/validate_memory.cpp", "source/val/validate_memory_semantics.cpp", "source/val/validate_memory_semantics.h", + "source/val/validate_mesh_shading.h", "source/val/validate_misc.cpp", "source/val/validate_mode_setting.cpp", "source/val/validate_non_uniform.cpp", diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index ab4578b99..668579ac4 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -318,6 +318,7 @@ set(SPIRV_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_logicals.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_memory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_memory_semantics.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_mesh_shading.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_misc.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_mode_setting.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_non_uniform.cpp diff --git a/source/val/validate.cpp b/source/val/validate.cpp index 9a685f229..efb9225e6 100644 --- a/source/val/validate.cpp +++ b/source/val/validate.cpp @@ -209,6 +209,8 @@ spv_result_t ValidateBinaryUsingContextAndValidationState( return error; } + bool has_mask_task_nv = false; + bool has_mask_task_ext = false; std::vector<Instruction*> visited_entry_points; for (auto& instruction : vstate->ordered_instructions()) { { @@ -247,6 +249,11 @@ spv_result_t ValidateBinaryUsingContextAndValidationState( } } visited_entry_points.push_back(inst); + + has_mask_task_nv |= (execution_model == SpvExecutionModelTaskNV || + execution_model == SpvExecutionModelMeshNV); + has_mask_task_ext |= (execution_model == SpvExecutionModelTaskEXT || + execution_model == SpvExecutionModelMeshEXT); } if (inst->opcode() == SpvOpFunctionCall) { if (!vstate->in_function_body()) { @@ -298,6 +305,12 @@ spv_result_t ValidateBinaryUsingContextAndValidationState( return vstate->diag(SPV_ERROR_INVALID_LAYOUT, nullptr) << "Missing required OpSamplerImageAddressingModeNV instruction."; + if (has_mask_task_ext && has_mask_task_nv) + return vstate->diag(SPV_ERROR_INVALID_LAYOUT, nullptr) + << vstate->VkErrorID(7102) + << "Module can't mix MeshEXT/TaskEXT with MeshNV/TaskNV Execution " + "Model."; + // Catch undefined forward references before performing further checks. if (auto error = ValidateForwardDecls(*vstate)) return error; @@ -352,6 +365,7 @@ spv_result_t ValidateBinaryUsingContextAndValidationState( if (auto error = LiteralsPass(*vstate, &instruction)) return error; if (auto error = RayQueryPass(*vstate, &instruction)) return error; if (auto error = RayTracingPass(*vstate, &instruction)) return error; + if (auto error = MeshShadingPass(*vstate, &instruction)) return error; } // Validate the preconditions involving adjacent instructions. e.g. SpvOpPhi diff --git a/source/val/validate.h b/source/val/validate.h index 85c32d385..4b953ba31 100644 --- a/source/val/validate.h +++ b/source/val/validate.h @@ -203,6 +203,9 @@ spv_result_t RayQueryPass(ValidationState_t& _, const Instruction* inst); /// Validates correctness of ray tracing instructions. spv_result_t RayTracingPass(ValidationState_t& _, const Instruction* inst); +/// Validates correctness of mesh shading instructions. +spv_result_t MeshShadingPass(ValidationState_t& _, const Instruction* inst); + /// Calculates the reachability of basic blocks. void ReachabilityPass(ValidationState_t& _); diff --git a/source/val/validate_cfg.cpp b/source/val/validate_cfg.cpp index c684a9918..cf22dea6a 100644 --- a/source/val/validate_cfg.cpp +++ b/source/val/validate_cfg.cpp @@ -1068,6 +1068,7 @@ spv_result_t CfgPass(ValidationState_t& _, const Instruction* inst) { case SpvOpTerminateRayKHR: case SpvOpEmitMeshTasksEXT: _.current_function().RegisterBlockEnd(std::vector<uint32_t>()); + // Ops with dedicated passes check for the Execution Model there if (opcode == SpvOpKill) { _.current_function().RegisterExecutionModelLimitation( SpvExecutionModelFragment, @@ -1088,11 +1089,6 @@ spv_result_t CfgPass(ValidationState_t& _, const Instruction* inst) { SpvExecutionModelAnyHitKHR, "OpTerminateRayKHR requires AnyHitKHR execution model"); } - if (opcode == SpvOpEmitMeshTasksEXT) { - _.current_function().RegisterExecutionModelLimitation( - SpvExecutionModelTaskEXT, - "OpEmitMeshTasksEXT requires TaskEXT execution model"); - } break; default: diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp index f939542f4..58d691150 100644 --- a/source/val/validate_memory.cpp +++ b/source/val/validate_memory.cpp @@ -640,6 +640,19 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { } } + if (inst->operands().size() > 3) { + if (storage_class == SpvStorageClassTaskPayloadWorkgroupEXT) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "OpVariable, <id> '" << _.getIdName(inst->id()) + << "', initializer are not allowed for TaskPayloadWorkgroupEXT"; + } + if (storage_class == SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "OpVariable, <id> '" << _.getIdName(inst->id()) + << "', initializer are not allowed for Input"; + } + } + if (storage_class == SpvStorageClassPhysicalStorageBuffer) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "PhysicalStorageBuffer must not be used with OpVariable."; diff --git a/source/val/validate_mesh_shading.cpp b/source/val/validate_mesh_shading.cpp new file mode 100644 index 000000000..a7f072672 --- /dev/null +++ b/source/val/validate_mesh_shading.cpp @@ -0,0 +1,123 @@ +// Copyright (c) 2022 The Khronos Group Inc. +// +// 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. + +// Validates ray query instructions from SPV_KHR_ray_query + +#include "source/opcode.h" +#include "source/val/instruction.h" +#include "source/val/validate.h" +#include "source/val/validation_state.h" + +namespace spvtools { +namespace val { + +spv_result_t MeshShadingPass(ValidationState_t& _, const Instruction* inst) { + const SpvOp opcode = inst->opcode(); + switch (opcode) { + case SpvOpEmitMeshTasksEXT: { + _.function(inst->function()->id()) + ->RegisterExecutionModelLimitation( + [](SpvExecutionModel model, std::string* message) { + if (model != SpvExecutionModelTaskEXT) { + if (message) { + *message = + "OpEmitMeshTasksEXT requires TaskEXT execution model"; + } + return false; + } + return true; + }); + + const uint32_t group_count_x = _.GetOperandTypeId(inst, 0); + if (!_.IsUnsignedIntScalarType(group_count_x) || + _.GetBitWidth(group_count_x) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Group Count X must be a 32-bit unsigned int scalar"; + } + + const uint32_t group_count_y = _.GetOperandTypeId(inst, 1); + if (!_.IsUnsignedIntScalarType(group_count_y) || + _.GetBitWidth(group_count_y) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Group Count Y must be a 32-bit unsigned int scalar"; + } + + const uint32_t group_count_z = _.GetOperandTypeId(inst, 2); + if (!_.IsUnsignedIntScalarType(group_count_z) || + _.GetBitWidth(group_count_z) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Group Count Z must be a 32-bit unsigned int scalar"; + } + + if (inst->operands().size() == 4) { + const auto payload = _.FindDef(inst->GetOperandAs<uint32_t>(3)); + if (payload->opcode() != SpvOpVariable) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Payload must be the result of a OpVariable"; + } + if (SpvStorageClass(payload->GetOperandAs<uint32_t>(2)) != + SpvStorageClassTaskPayloadWorkgroupEXT) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Payload OpVariable must have a storage class of " + "TaskPayloadWorkgroupEXT"; + } + } + break; + } + + case SpvOpSetMeshOutputsEXT: { + _.function(inst->function()->id()) + ->RegisterExecutionModelLimitation( + [](SpvExecutionModel model, std::string* message) { + if (model != SpvExecutionModelMeshEXT) { + if (message) { + *message = + "OpSetMeshOutputsEXT requires MeshEXT execution model"; + } + return false; + } + return true; + }); + + const uint32_t vertex_count = _.GetOperandTypeId(inst, 0); + if (!_.IsUnsignedIntScalarType(vertex_count) || + _.GetBitWidth(vertex_count) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Vertex Count must be a 32-bit unsigned int scalar"; + } + + const uint32_t primitive_count = _.GetOperandTypeId(inst, 1); + if (!_.IsUnsignedIntScalarType(primitive_count) || + _.GetBitWidth(primitive_count) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Primitive Count must be a 32-bit unsigned int scalar"; + } + + break; + } + + case SpvOpWritePackedPrimitiveIndices4x8NV: { + // No validation rules (for the moment). + break; + } + + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace val +} // namespace spvtools diff --git a/source/val/validate_mode_setting.cpp b/source/val/validate_mode_setting.cpp index 09a9d48e1..11e11e9a8 100644 --- a/source/val/validate_mode_setting.cpp +++ b/source/val/validate_mode_setting.cpp @@ -241,6 +241,39 @@ spv_result_t ValidateEntryPoint(ValidationState_t& _, const Instruction* inst) { "OutputTriangleStrip execution modes."; } break; + case SpvExecutionModelMeshEXT: + if (!execution_modes || + 1 != std::count_if(execution_modes->begin(), execution_modes->end(), + [](const SpvExecutionMode& mode) { + switch (mode) { + case SpvExecutionModeOutputPoints: + case SpvExecutionModeOutputLinesEXT: + case SpvExecutionModeOutputTrianglesEXT: + return true; + default: + return false; + } + })) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "MeshEXT execution model entry points must specify exactly " + "one of OutputPoints, OutputLinesEXT, or " + "OutputTrianglesEXT Execution Modes."; + } else if (2 != std::count_if( + execution_modes->begin(), execution_modes->end(), + [](const SpvExecutionMode& mode) { + switch (mode) { + case SpvExecutionModeOutputPrimitivesEXT: + case SpvExecutionModeOutputVertices: + return true; + default: + return false; + } + })) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "MeshEXT execution model entry points must specify both " + "OutputPrimitivesEXT and OutputVertices Execution Modes."; + } + break; default: break; } @@ -443,6 +476,20 @@ spv_result_t ValidateExecutionMode(ValidationState_t& _, } } break; + case SpvExecutionModeOutputLinesEXT: + case SpvExecutionModeOutputTrianglesEXT: + case SpvExecutionModeOutputPrimitivesEXT: + if (!std::all_of(models->begin(), models->end(), + [](const SpvExecutionModel& model) { + return (model == SpvExecutionModelMeshEXT || + model == SpvExecutionModelMeshNV); + })) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Execution mode can only be used with the MeshEXT or MeshNV " + "execution " + "model."; + } + break; case SpvExecutionModePixelCenterInteger: case SpvExecutionModeOriginUpperLeft: case SpvExecutionModeOriginLowerLeft: diff --git a/source/val/validate_ray_tracing.cpp b/source/val/validate_ray_tracing.cpp index 78bac19b5..5b5c8da1e 100644 --- a/source/val/validate_ray_tracing.cpp +++ b/source/val/validate_ray_tracing.cpp @@ -112,8 +112,10 @@ spv_result_t RayTracingPass(ValidationState_t& _, const Instruction* inst) { if (payload->opcode() != SpvOpVariable) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Payload must be the result of a OpVariable"; - } else if (payload->word(3) != SpvStorageClassRayPayloadKHR && - payload->word(3) != SpvStorageClassIncomingRayPayloadKHR) { + } else if (payload->GetOperandAs<uint32_t>(2) != + SpvStorageClassRayPayloadKHR && + payload->GetOperandAs<uint32_t>(2) != + SpvStorageClassIncomingRayPayloadKHR) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Payload must have storage class RayPayloadKHR or " "IncomingRayPayloadKHR"; @@ -185,8 +187,9 @@ spv_result_t RayTracingPass(ValidationState_t& _, const Instruction* inst) { if (callable_data->opcode() != SpvOpVariable) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Callable Data must be the result of a OpVariable"; - } else if (callable_data->word(3) != SpvStorageClassCallableDataKHR && - callable_data->word(3) != + } else if (callable_data->GetOperandAs<uint32_t>(2) != + SpvStorageClassCallableDataKHR && + callable_data->GetOperandAs<uint32_t>(2) != SpvStorageClassIncomingCallableDataKHR) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Callable Data must have storage class CallableDataKHR or " diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp index fa8c624ed..47f6ba01d 100644 --- a/source/val/validation_state.cpp +++ b/source/val/validation_state.cpp @@ -765,6 +765,21 @@ void ValidationState_t::RegisterStorageClassConsumer( } return true; }); + } else if (storage_class == SpvStorageClassTaskPayloadWorkgroupEXT) { + function(consumer->function()->id()) + ->RegisterExecutionModelLimitation( + [](SpvExecutionModel model, std::string* message) { + if (model != SpvExecutionModelTaskEXT && + model != SpvExecutionModelMeshEXT) { + if (message) { + *message = + "TaskPayloadWorkgroupEXT Storage Class is limited to " + "TaskEXT and MeshKHR execution model"; + } + return false; + } + return true; + }); } } @@ -2110,6 +2125,8 @@ std::string ValidationState_t::VkErrorID(uint32_t id, return VUID_WRAP(VUID-StandaloneSpirv-Uniform-06925); case 6997: return VUID_WRAP(VUID-StandaloneSpirv-SubgroupVoteKHR-06997); + case 7102: + return VUID_WRAP(VUID-StandaloneSpirv-MeshEXT-07102); case 7320: return VUID_WRAP(VUID-StandaloneSpirv-ExecutionModel-07320); case 7290: diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp index 3cf7575a1..deda95b49 100644 --- a/test/val/val_id_test.cpp +++ b/test/val/val_id_test.cpp @@ -2091,9 +2091,9 @@ TEST_F(ValidateIdWithMessage, OpVariableGood) { TEST_F(ValidateIdWithMessage, OpVariableInitializerConstantGood) { std::string spirv = kGLSL450MemoryModel + R"( %1 = OpTypeInt 32 0 -%2 = OpTypePointer Input %1 +%2 = OpTypePointer Output %1 %3 = OpConstant %1 42 -%4 = OpVariable %2 Input %3)"; +%4 = OpVariable %2 Output %3)"; CompileSuccessfully(spirv.c_str()); EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); } diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp index ec1a00077..35e8af756 100644 --- a/test/val/val_memory_test.cpp +++ b/test/val/val_memory_test.cpp @@ -472,6 +472,55 @@ OpFunctionEnd "= OpVariable %_ptr_Input_float Input %float_1\n")); } +TEST_F(ValidateMemory, UniversalInitializerWithDisallowedStorageClassesBad) { + std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +%float = OpTypeFloat 32 +%float_ptr = OpTypePointer Input %float +%init_val = OpConstant %float 1.0 +%1 = OpVariable %float_ptr Input %init_val +%void = OpTypeVoid +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%2 = OpLabel +OpReturn +OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpVariable, <id> '5[%5]', initializer are not allowed for Input")); +} + +TEST_F(ValidateMemory, InitializerWithTaskPayloadWorkgroupEXT) { + std::string spirv = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint TaskEXT %main "main" %payload + %void = OpTypeVoid + %func = OpTypeFunction %void + %uint = OpTypeInt 32 0 +%_ptr_TaskPayloadWorkgroupEXT = OpTypePointer TaskPayloadWorkgroupEXT %uint + %uint_1 = OpConstant %uint 1 + %payload = OpVariable %_ptr_TaskPayloadWorkgroupEXT TaskPayloadWorkgroupEXT %uint_1 + %main = OpFunction %void None %func + %label = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpVariable, <id> '2[%2]', initializer are not allowed " + "for TaskPayloadWorkgroupEXT")); +} + TEST_F(ValidateMemory, ArrayLenCorrectResultType) { std::string spirv = R"( OpCapability Shader diff --git a/test/val/val_mesh_shading_test.cpp b/test/val/val_mesh_shading_test.cpp index d10f40d87..ce6999dcb 100644 --- a/test/val/val_mesh_shading_test.cpp +++ b/test/val/val_mesh_shading_test.cpp @@ -90,6 +90,515 @@ TEST_F(ValidateMeshShading, EmitMeshTasksEXTNotLastInstructionVulkan) { HasSubstr("Return must appear in a block")); } +TEST_F(ValidateMeshShading, BasicTaskSuccess) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint TaskEXT %main "main" + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + %label = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); +} + +TEST_F(ValidateMeshShading, BasicMeshSuccess) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint MeshEXT %main "main" + OpExecutionMode %main OutputVertices 1 + OpExecutionMode %main OutputPrimitivesEXT 1 + OpExecutionMode %main OutputTrianglesEXT + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + %label = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); +} + +TEST_F(ValidateMeshShading, VulkanBasicMeshAndTaskSuccess) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpExtension "SPV_NV_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint MeshEXT %mainMesh "mainMesh" + OpEntryPoint TaskEXT %mainTask "mainTask" + OpExecutionMode %mainMesh OutputVertices 1 + OpExecutionMode %mainMesh OutputPrimitivesEXT 1 + OpExecutionMode %mainMesh OutputTrianglesEXT + %void = OpTypeVoid + %func = OpTypeFunction %void + %mainMesh = OpFunction %void None %func + %labelMesh = OpLabel + OpReturn + OpFunctionEnd + %mainTask = OpFunction %void None %func + %labelTask = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_VULKAN_1_2); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2)); +} + +TEST_F(ValidateMeshShading, VulkanBasicMeshAndTaskBad) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpCapability MeshShadingNV + OpExtension "SPV_EXT_mesh_shader" + OpExtension "SPV_NV_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint MeshEXT %mainMesh "mainMesh" + OpEntryPoint TaskNV %mainTask "mainTask" + OpExecutionMode %mainMesh OutputVertices 1 + OpExecutionMode %mainMesh OutputPrimitivesEXT 1 + OpExecutionMode %mainMesh OutputTrianglesEXT + %void = OpTypeVoid + %func = OpTypeFunction %void + %mainMesh = OpFunction %void None %func + %labelMesh = OpLabel + OpReturn + OpFunctionEnd + %mainTask = OpFunction %void None %func + %labelTask = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_VULKAN_1_2); + EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions(SPV_ENV_VULKAN_1_2)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-MeshEXT-07102")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Module can't mix MeshEXT/TaskEXT with MeshNV/TaskNV " + "Execution Model.")); +} + +TEST_F(ValidateMeshShading, MeshMissingOutputVertices) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint MeshEXT %main "main" + OpExecutionMode %main OutputPrimitivesEXT 1 + OpExecutionMode %main OutputTrianglesEXT + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + %label = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("MeshEXT execution model entry points must specify both " + "OutputPrimitivesEXT and OutputVertices Execution Modes.")); +} + +TEST_F(ValidateMeshShading, MeshMissingOutputPrimitivesEXT) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint MeshEXT %main "main" + OpExecutionMode %main OutputVertices 1 + OpExecutionMode %main OutputTrianglesEXT + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + %label = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("MeshEXT execution model entry points must specify both " + "OutputPrimitivesEXT and OutputVertices Execution Modes.")); +} + +TEST_F(ValidateMeshShading, MeshMissingOutputType) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint MeshEXT %main "main" + OpExecutionMode %main OutputVertices 1 + OpExecutionMode %main OutputPrimitivesEXT 1 + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + %label = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MeshEXT execution model entry points must specify " + "exactly one of OutputPoints, OutputLinesEXT, or " + "OutputTrianglesEXT Execution Modes.")); +} + +TEST_F(ValidateMeshShading, MeshMultipleOutputType) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint MeshEXT %main "main" + OpExecutionMode %main OutputVertices 1 + OpExecutionMode %main OutputPrimitivesEXT 1 + OpExecutionMode %main OutputLinesEXT + OpExecutionMode %main OutputTrianglesEXT + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + %label = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MeshEXT execution model entry points must specify " + "exactly one of OutputPoints, OutputLinesEXT, or " + "OutputTrianglesEXT Execution Modes.")); +} + +TEST_F(ValidateMeshShading, BadExecutionModelOutputLinesEXT) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" + OpExecutionMode %main OutputLinesEXT + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + %label = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Execution mode can only be used with the MeshEXT or " + "MeshNV execution model.")); +} + +TEST_F(ValidateMeshShading, BadExecutionModelOutputTrianglesEXT) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" + OpExecutionMode %main OutputTrianglesEXT + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + %label = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Execution mode can only be used with the MeshEXT or " + "MeshNV execution model.")); +} + +TEST_F(ValidateMeshShading, BadExecutionModelOutputPrimitivesEXT) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" + OpExecutionMode %main OutputPrimitivesEXT 1 + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + %label = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Execution mode can only be used with the MeshEXT or " + "MeshNV execution model.")); +} + +TEST_F(ValidateMeshShading, OpEmitMeshTasksBadGroupCountSignedInt) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint TaskEXT %main "main" + %void = OpTypeVoid + %func = OpTypeFunction %void + %int = OpTypeInt 32 1 + %uint = OpTypeInt 32 0 + %int_1 = OpConstant %int 1 + %uint_1 = OpConstant %uint 1 + %main = OpFunction %void None %func + %label = OpLabel + OpEmitMeshTasksEXT %int_1 %uint_1 %uint_1 + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Group Count X must be a 32-bit unsigned int scalar")); +} + +TEST_F(ValidateMeshShading, OpEmitMeshTasksBadGroupCountVector) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint TaskEXT %main "main" + %void = OpTypeVoid + %func = OpTypeFunction %void + %uint = OpTypeInt 32 0 + %v2uint = OpTypeVector %uint 2 +%_ptr_v2uint = OpTypePointer Function %v2uint + %uint_1 = OpConstant %uint 1 + %composite = OpConstantComposite %v2uint %uint_1 %uint_1 + %_ptr_uint = OpTypePointer Function %uint + %main = OpFunction %void None %func + %label = OpLabel + %x = OpVariable %_ptr_v2uint Function + OpStore %x %composite + %13 = OpAccessChain %_ptr_uint %x %uint_1 + %14 = OpLoad %uint %13 + OpEmitMeshTasksEXT %14 %composite %uint_1 + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Group Count Y must be a 32-bit unsigned int scalar")); +} + +TEST_F(ValidateMeshShading, OpEmitMeshTasksBadPayload) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint TaskEXT %main "main" %payload + %void = OpTypeVoid + %func = OpTypeFunction %void + %uint = OpTypeInt 32 0 + %task = OpTypeStruct %uint +%_ptr_Uniform = OpTypePointer Uniform %task + %payload = OpVariable %_ptr_Uniform Uniform + %uint_1 = OpConstant %uint 1 + %main = OpFunction %void None %func + %label = OpLabel + OpEmitMeshTasksEXT %uint_1 %uint_1 %uint_1 %payload + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Payload OpVariable must have a storage class of " + "TaskPayloadWorkgroupEXT")); +} + +TEST_F(ValidateMeshShading, TaskPayloadWorkgroupBadExecutionModel) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" %payload + %void = OpTypeVoid + %func = OpTypeFunction %void + %uint = OpTypeInt 32 0 +%_ptr_TaskPayloadWorkgroupEXT = OpTypePointer TaskPayloadWorkgroupEXT %uint + %payload = OpVariable %_ptr_TaskPayloadWorkgroupEXT TaskPayloadWorkgroupEXT + %main = OpFunction %void None %func + %label = OpLabel + %load = OpLoad %uint %payload + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("TaskPayloadWorkgroupEXT Storage Class is limited to " + "TaskEXT and MeshKHR execution model")); +} + +TEST_F(ValidateMeshShading, OpSetMeshOutputsBadVertexCount) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint MeshEXT %main "main" + OpExecutionMode %main OutputVertices 1 + OpExecutionMode %main OutputPrimitivesNV 1 + OpExecutionMode %main OutputTrianglesNV + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %uint = OpTypeInt 32 0 + %_ptr_int = OpTypePointer Function %int + %int_1 = OpConstant %int 1 + %uint_1 = OpConstant %uint 1 + %main = OpFunction %void None %3 + %5 = OpLabel + %x = OpVariable %_ptr_int Function + OpStore %x %int_1 + %load = OpLoad %int %x + OpSetMeshOutputsEXT %load %uint_1 + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Vertex Count must be a 32-bit unsigned int scalar")); +} + +TEST_F(ValidateMeshShading, OpSetMeshOutputsBadPrimitiveCount) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint MeshEXT %main "main" + OpExecutionMode %main OutputVertices 1 + OpExecutionMode %main OutputPrimitivesNV 1 + OpExecutionMode %main OutputTrianglesNV + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %uint = OpTypeInt 32 0 + %int_1 = OpConstant %int 1 + %uint_1 = OpConstant %uint 1 + %main = OpFunction %void None %3 + %5 = OpLabel + OpSetMeshOutputsEXT %uint_1 %int_1 + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Primitive Count must be a 32-bit unsigned int scalar")); +} + +TEST_F(ValidateMeshShading, OpSetMeshOutputsBadExecutionModel) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint TaskEXT %main "main" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %uint = OpTypeInt 32 0 + %uint_1 = OpConstant %uint 1 + %main = OpFunction %void None %3 + %5 = OpLabel + OpSetMeshOutputsEXT %uint_1 %uint_1 + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpSetMeshOutputsEXT requires MeshEXT execution model")); +} + +TEST_F(ValidateMeshShading, OpSetMeshOutputsZeroSuccess) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint MeshEXT %main "main" + OpExecutionMode %main OutputVertices 1 + OpExecutionMode %main OutputPrimitivesNV 1 + OpExecutionMode %main OutputTrianglesNV + %void = OpTypeVoid + %3 = OpTypeFunction %void + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %main = OpFunction %void None %3 + %5 = OpLabel + OpSetMeshOutputsEXT %uint_0 %uint_0 + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); +} + +TEST_F(ValidateMeshShading, OpEmitMeshTasksZeroSuccess) { + const std::string body = R"( + OpCapability MeshShadingEXT + OpExtension "SPV_EXT_mesh_shader" + OpMemoryModel Logical GLSL450 + OpEntryPoint TaskEXT %main "main" + %void = OpTypeVoid + %func = OpTypeFunction %void + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 1 + %main = OpFunction %void None %func + %label = OpLabel + OpEmitMeshTasksEXT %uint_0 %uint_0 %uint_0 + OpFunctionEnd +)"; + + CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5)); +} + } // namespace } // namespace val } // namespace spvtools |