diff --git a/Android.mk b/Android.mk index c32732d5cd3..80c61b08010 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 \ diff --git a/BUILD.gn b/BUILD.gn index ac75cbaab13..4f1c43ee14b 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -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 ab4578b995a..668579ac4c0 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 9a685f22995..efb9225e6b7 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 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 85c32d38590..4b953ba3126 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_memory.cpp b/source/val/validate_memory.cpp index e4ad0e0276b..4a21090b016 100644 --- a/source/val/validate_memory.cpp +++ b/source/val/validate_memory.cpp @@ -639,6 +639,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, '" << _.getIdName(inst->id()) + << "', initializer are not allowed for TaskPayloadWorkgroupEXT"; + } + if (storage_class == SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "OpVariable, '" << _.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 00000000000..938434e91f0 --- /dev/null +++ b/source/val/validate_mesh_shading.cpp @@ -0,0 +1,110 @@ +// 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: { + 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(3)); + if (payload->opcode() != SpvOpVariable) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Payload must be the result of a OpVariable"; + } + if (SpvStorageClass(payload->word(3)) != + 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 models"; + } + 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 09a9d48e16c..023f8cb92c5 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,18 @@ 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; + })) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Execution mode can only be used with the MeshEXT execution " + "model."; + } + break; case SpvExecutionModePixelCenterInteger: case SpvExecutionModeOriginUpperLeft: case SpvExecutionModeOriginLowerLeft: diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp index 2e1fa31ae39..98aaa566506 100644 --- a/source/val/validation_state.cpp +++ b/source/val/validation_state.cpp @@ -763,6 +763,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 MissKHR execution model"; + } + return false; + } + return true; + }); } } @@ -2106,6 +2121,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); default: return ""; // unknown id } diff --git a/test/val/CMakeLists.txt b/test/val/CMakeLists.txt index e73e0f4af36..8d00560bcec 100644 --- a/test/val/CMakeLists.txt +++ b/test/val/CMakeLists.txt @@ -77,6 +77,7 @@ add_spvtools_unittest(TARGET val_fghijklmnop val_literals_test.cpp val_logicals_test.cpp val_memory_test.cpp + val_mesh_shading_test.cpp val_misc_test.cpp val_modes_test.cpp val_non_semantic_test.cpp diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp index ec1a0007769..565c4647388 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, '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 Uniform %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, '5[%5]', 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 new file mode 100644 index 00000000000..292c504e919 --- /dev/null +++ b/test/val/val_mesh_shading_test.cpp @@ -0,0 +1,505 @@ +// 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. + +// Tests instructions from SPV_EXT_mesh_shader + +#include +#include + +#include "gmock/gmock.h" +#include "test/val/val_fixtures.h" + +namespace spvtools { +namespace val { +namespace { + +using ::testing::HasSubstr; +using ::testing::Values; + +using ValidateMeshShading = spvtest::ValidateBase; + +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 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 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 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 + 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("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 + 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("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 + 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("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 MissKHR 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 models")); +} + +} // namespace +} // namespace val +} // namespace spvtools \ No newline at end of file