spirv-val: Add initial SPV_EXT_mesh_shader validation (#4924)

* Move TaskEXT check to OpEmitMeshTasksEXT

* Add MeshNV for Execution Model alias
This commit is contained in:
Spencer Fricke 2022-09-24 00:06:46 +09:00 коммит произвёл GitHub
Родитель 265b455c99
Коммит b53d7a8aff
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 788 добавлений и 11 удалений

Просмотреть файл

@ -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",

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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& _);

Просмотреть файл

@ -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:

Просмотреть файл

@ -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.";

Просмотреть файл

@ -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

Просмотреть файл

@ -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:

Просмотреть файл

@ -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 "

Просмотреть файл

@ -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:

Просмотреть файл

@ -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());
}

Просмотреть файл

@ -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

Просмотреть файл

@ -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