Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/KhronosGroup/SPIRV-Tools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSpencer Fricke <spencerfricke@gmail.com>2022-09-23 18:06:46 +0300
committerGitHub <noreply@github.com>2022-09-23 18:06:46 +0300
commitb53d7a8affabb230a47fb4123b040573a3062d4a (patch)
tree17ad86d1bfe312ac8a0d4b8fc4b6f4185287b3ae
parent265b455c996f7314ff995ecc3f5e3548365dbe87 (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.mk1
-rw-r--r--BUILD.gn1
-rw-r--r--source/CMakeLists.txt1
-rw-r--r--source/val/validate.cpp14
-rw-r--r--source/val/validate.h3
-rw-r--r--source/val/validate_cfg.cpp6
-rw-r--r--source/val/validate_memory.cpp13
-rw-r--r--source/val/validate_mesh_shading.cpp123
-rw-r--r--source/val/validate_mode_setting.cpp47
-rw-r--r--source/val/validate_ray_tracing.cpp11
-rw-r--r--source/val/validation_state.cpp17
-rw-r--r--test/val/val_id_test.cpp4
-rw-r--r--test/val/val_memory_test.cpp49
-rw-r--r--test/val/val_mesh_shading_test.cpp509
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 \
diff --git a/BUILD.gn b/BUILD.gn
index ac75cbaab..4f1c43ee1 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 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