mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-27 18:30:14 +00:00
Check BuiltIn Decoration rules.
When applied to a structure-type member, all members of that structure type must also be decorated with BuiltIn. (No allowed mixing of built-in variables and non-built-in variables within a single structure.) When applied to a structure-type member, that structure type cannot be contained as a member of another structure type. There is at most one object per Storage Class that can contain a structure type containing members decorated with BuiltIn, consumed per entry-point.
This commit is contained in:
parent
3a7df2cf1b
commit
9c71c572e5
@ -61,6 +61,7 @@ class Decoration {
|
||||
|
||||
void set_struct_member_index(uint32_t index) { struct_member_index_ = index; }
|
||||
int struct_member_index() { return struct_member_index_; }
|
||||
int struct_member_index() const { return struct_member_index_; }
|
||||
SpvDecoration dec_type() { return dec_type_; }
|
||||
SpvDecoration dec_type() const { return dec_type_; }
|
||||
std::vector<uint32_t>& params() { return params_; }
|
||||
|
@ -126,10 +126,28 @@ class ValidationState_t {
|
||||
/// instruction
|
||||
bool in_block() const;
|
||||
|
||||
/// Registers the given <id> as an Entry Point.
|
||||
void RegisterEntryPointId(const uint32_t id) {
|
||||
entry_points_.push_back(id);
|
||||
entry_point_interfaces_.insert(std::make_pair(id, std::vector<uint32_t>()));
|
||||
}
|
||||
|
||||
/// Returns a list of entry point function ids
|
||||
std::vector<uint32_t>& entry_points() { return entry_points_; }
|
||||
const std::vector<uint32_t>& entry_points() const { return entry_points_; }
|
||||
|
||||
/// Adds a new interface id to the interfaces of the given entry point.
|
||||
void RegisterInterfaceForEntryPoint(uint32_t entry_point,
|
||||
uint32_t interface) {
|
||||
entry_point_interfaces_[entry_point].push_back(interface);
|
||||
}
|
||||
|
||||
/// Returns the interfaces of a given entry point. If the given id is not a
|
||||
/// valid Entry Point id, std::out_of_range exception is thrown.
|
||||
const std::vector<uint32_t>& entry_point_interfaces(
|
||||
uint32_t entry_point) const {
|
||||
return entry_point_interfaces_.at(entry_point);
|
||||
}
|
||||
|
||||
/// Inserts an <id> to the set of functions that are target of OpFunctionCall.
|
||||
void AddFunctionCallTarget(const uint32_t id) {
|
||||
function_call_targets_.insert(id);
|
||||
@ -263,6 +281,15 @@ class ValidationState_t {
|
||||
return struct_nesting_depth_[id];
|
||||
}
|
||||
|
||||
/// Records that the structure type has a member decorated with a built-in.
|
||||
void RegisterStructTypeWithBuiltInMember(uint32_t id) {
|
||||
builtin_structs_.insert(id);
|
||||
}
|
||||
|
||||
/// Returns true if the struct type with the given Id has a BuiltIn member.
|
||||
bool IsStructTypeWithBuiltInMember(uint32_t id) const {
|
||||
return (builtin_structs_.find(id) != builtin_structs_.end());
|
||||
}
|
||||
private:
|
||||
ValidationState_t(const ValidationState_t&);
|
||||
|
||||
@ -303,6 +330,9 @@ class ValidationState_t {
|
||||
/// IDs that are entry points, ie, arguments to OpEntryPoint.
|
||||
std::vector<uint32_t> entry_points_;
|
||||
|
||||
/// Maps an entry point id to its interfaces.
|
||||
std::unordered_map<uint32_t, std::vector<uint32_t>> entry_point_interfaces_;
|
||||
|
||||
/// Functions IDs that are target of OpFunctionCall.
|
||||
std::unordered_set<uint32_t> function_call_targets_;
|
||||
|
||||
@ -315,6 +345,9 @@ class ValidationState_t {
|
||||
/// Set of Local Variable IDs ('Function' Storage Class)
|
||||
std::unordered_set<uint32_t> local_vars_;
|
||||
|
||||
/// Set of struct types that have members with a BuiltIn decoration.
|
||||
std::unordered_set<uint32_t> builtin_structs_;
|
||||
|
||||
/// Structure Nesting Depth
|
||||
std::unordered_map<uint32_t, uint32_t> struct_nesting_depth_;
|
||||
|
||||
|
@ -121,7 +121,13 @@ spv_result_t ProcessInstruction(void* user_data,
|
||||
ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data));
|
||||
_.increment_instruction_count();
|
||||
if (static_cast<SpvOp>(inst->opcode) == SpvOpEntryPoint) {
|
||||
_.entry_points().push_back(inst->words[2]);
|
||||
const auto entry_point = inst->words[2];
|
||||
_.RegisterEntryPointId(entry_point);
|
||||
// Operand 3 and later are the <id> of interfaces for the entry point.
|
||||
for (int i = 3; i < inst->num_operands; ++i) {
|
||||
_.RegisterInterfaceForEntryPoint(entry_point,
|
||||
inst->words[inst->operands[i].offset]);
|
||||
}
|
||||
}
|
||||
if (static_cast<SpvOp>(inst->opcode) == SpvOpFunctionCall) {
|
||||
_.AddFunctionCallTarget(inst->words[3]);
|
||||
|
@ -14,15 +14,32 @@
|
||||
|
||||
#include "validate.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include "diagnostic.h"
|
||||
#include "opcode.h"
|
||||
#include "val/validation_state.h"
|
||||
|
||||
using libspirv::Decoration;
|
||||
using libspirv::DiagnosticStream;
|
||||
using libspirv::ValidationState_t;
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns whether the given structure type has any members with BuiltIn
|
||||
// decoration.
|
||||
bool isBuiltInStruct(uint32_t struct_id, ValidationState_t& vstate) {
|
||||
const auto& decorations = vstate.id_decorations(struct_id);
|
||||
return std::any_of(
|
||||
decorations.begin(), decorations.end(), [](const Decoration& d) {
|
||||
return SpvDecorationBuiltIn == d.dec_type() &&
|
||||
Decoration::kInvalidMember != d.struct_member_index();
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace libspirv {
|
||||
|
||||
// Validates that decorations have been applied properly.
|
||||
@ -48,7 +65,49 @@ spv_result_t ValidateDecorations(ValidationState_t& vstate) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add more decoration validation code here.
|
||||
for (uint32_t entry_point : vstate.entry_points()) {
|
||||
const auto& interfaces = vstate.entry_point_interfaces(entry_point);
|
||||
int num_builtin_inputs = 0;
|
||||
int num_builtin_outputs = 0;
|
||||
for (auto interface : interfaces) {
|
||||
Instruction* var_instr = vstate.FindDef(interface);
|
||||
if (SpvOpVariable != var_instr->opcode()) {
|
||||
return vstate.diag(SPV_ERROR_INVALID_ID)
|
||||
<< "Interfaces passed to OpEntryPoint must be of type "
|
||||
"OpTypeVariable. Found Op"
|
||||
<< spvOpcodeString(static_cast<SpvOp>(var_instr->opcode()))
|
||||
<< ".";
|
||||
}
|
||||
const uint32_t ptr_id = var_instr->word(1);
|
||||
Instruction* ptr_instr = vstate.FindDef(ptr_id);
|
||||
// It is guaranteed (by validator ID checks) that ptr_instr is
|
||||
// OpTypePointer. Word 3 of this instruction is the type being pointed to.
|
||||
const uint32_t type_id = ptr_instr->word(3);
|
||||
Instruction* type_instr = vstate.FindDef(type_id);
|
||||
const auto storage_class =
|
||||
static_cast<SpvStorageClass>(var_instr->word(3));
|
||||
if (storage_class != SpvStorageClassInput &&
|
||||
storage_class != SpvStorageClassOutput) {
|
||||
return vstate.diag(SPV_ERROR_INVALID_ID)
|
||||
<< "OpEntryPoint interfaces must be OpVariables with "
|
||||
"Storage Class of Input(1) or Output(3). Found Storage Class "
|
||||
<< storage_class << " for Entry Point id " << entry_point << ".";
|
||||
}
|
||||
if (type_instr && SpvOpTypeStruct == type_instr->opcode() &&
|
||||
isBuiltInStruct(type_id, vstate)) {
|
||||
if (storage_class == SpvStorageClassInput) ++num_builtin_inputs;
|
||||
if (storage_class == SpvStorageClassOutput) ++num_builtin_outputs;
|
||||
if (num_builtin_inputs > 1 || num_builtin_outputs > 1) break;
|
||||
}
|
||||
}
|
||||
if (num_builtin_inputs > 1 || num_builtin_outputs > 1) {
|
||||
return vstate.diag(SPV_ERROR_INVALID_BINARY)
|
||||
<< "There must be at most one object per Storage Class that can "
|
||||
"contain a structure type containing members decorated with "
|
||||
"BuiltIn, consumed per entry-point. Entry Point id "
|
||||
<< entry_point << " does not meet this requirement.";
|
||||
}
|
||||
}
|
||||
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "val/validation_state.h"
|
||||
|
||||
using libspirv::ValidationState_t;
|
||||
using libspirv::Decoration;
|
||||
using std::function;
|
||||
using std::ignore;
|
||||
using std::make_pair;
|
||||
@ -386,6 +387,8 @@ bool idUsage::isValid<SpvOpTypeRuntimeArray>(const spv_instruction_t* inst,
|
||||
template <>
|
||||
bool idUsage::isValid<SpvOpTypeStruct>(const spv_instruction_t* inst,
|
||||
const spv_opcode_desc) {
|
||||
ValidationState_t& vstate = const_cast<ValidationState_t&>(module_);
|
||||
const uint32_t struct_id = inst->words[1];
|
||||
for (size_t memberTypeIndex = 2; memberTypeIndex < inst->words.size();
|
||||
++memberTypeIndex) {
|
||||
auto memberTypeId = inst->words[memberTypeIndex];
|
||||
@ -396,7 +399,17 @@ bool idUsage::isValid<SpvOpTypeStruct>(const spv_instruction_t* inst,
|
||||
<< "' is not a type.";
|
||||
return false;
|
||||
}
|
||||
if (memberType && module_.IsForwardPointer(memberTypeId)) {
|
||||
if (SpvOpTypeStruct == memberType->opcode() &&
|
||||
module_.IsStructTypeWithBuiltInMember(memberTypeId)) {
|
||||
DIAG(memberTypeIndex)
|
||||
<< "Structure <id> " << memberTypeId
|
||||
<< " contains members with BuiltIn decoration. Therefore this "
|
||||
"structure may not be contained as a member of another structure "
|
||||
"type. Structure <id> "
|
||||
<< struct_id << " contains structure <id> " << memberTypeId << ".";
|
||||
return false;
|
||||
}
|
||||
if (module_.IsForwardPointer(memberTypeId)) {
|
||||
if (memberType->opcode() != SpvOpTypePointer) {
|
||||
DIAG(memberTypeIndex) << "Found a forward reference to a non-pointer "
|
||||
"type in OpTypeStruct instruction.";
|
||||
@ -418,6 +431,27 @@ bool idUsage::isValid<SpvOpTypeStruct>(const spv_instruction_t* inst,
|
||||
}
|
||||
}
|
||||
}
|
||||
std::unordered_set<uint32_t> built_in_members;
|
||||
for (auto decoration : vstate.id_decorations(struct_id)) {
|
||||
if (decoration.dec_type() == SpvDecorationBuiltIn &&
|
||||
decoration.struct_member_index() != Decoration::kInvalidMember) {
|
||||
built_in_members.insert(decoration.struct_member_index());
|
||||
}
|
||||
}
|
||||
int num_struct_members = static_cast<int>(inst->words.size() - 2);
|
||||
int num_builtin_members = static_cast<int>(built_in_members.size());
|
||||
if (num_builtin_members > 0 && num_builtin_members != num_struct_members) {
|
||||
DIAG(0)
|
||||
<< "When BuiltIn decoration is applied to a structure-type member, "
|
||||
"all members of that structure type must also be decorated with "
|
||||
"BuiltIn (No allowed mixing of built-in variables and "
|
||||
"non-built-in variables within a single structure). Structure id "
|
||||
<< struct_id << " does not meet this requirement.";
|
||||
return false;
|
||||
}
|
||||
if (num_builtin_members > 0) {
|
||||
vstate.RegisterStructTypeWithBuiltInMember(struct_id);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -178,5 +178,164 @@ TEST_F(ValidateDecorations, LinkageExportUsedForInitializedVariableGood) {
|
||||
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations, StructAllMembersHaveBuiltInDecorationsGood) {
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpMemberDecorate %_struct_1 0 BuiltIn Position
|
||||
OpMemberDecorate %_struct_1 1 BuiltIn Position
|
||||
OpMemberDecorate %_struct_1 2 BuiltIn Position
|
||||
OpMemberDecorate %_struct_1 3 BuiltIn Position
|
||||
%float = OpTypeFloat 32
|
||||
%_runtimearr = OpTypeRuntimeArray %float
|
||||
%_struct_1 = OpTypeStruct %float %float %float %_runtimearr
|
||||
)";
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations, MixedBuiltInDecorationsBad) {
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpMemberDecorate %_struct_1 0 BuiltIn Position
|
||||
OpMemberDecorate %_struct_1 1 BuiltIn Position
|
||||
%float = OpTypeFloat 32
|
||||
%_runtimearr = OpTypeRuntimeArray %float
|
||||
%_struct_1 = OpTypeStruct %float %float %float %_runtimearr
|
||||
)";
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
|
||||
EXPECT_THAT(
|
||||
getDiagnosticString(),
|
||||
HasSubstr("When BuiltIn decoration is applied to a structure-type "
|
||||
"member, all members of that structure type must also be "
|
||||
"decorated with BuiltIn (No allowed mixing of built-in "
|
||||
"variables and non-built-in variables within a single "
|
||||
"structure). Structure id 1 does not meet this requirement."));
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations, StructContainsBuiltInStructBad) {
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpMemberDecorate %_struct_1 0 BuiltIn Position
|
||||
OpMemberDecorate %_struct_1 1 BuiltIn Position
|
||||
OpMemberDecorate %_struct_1 2 BuiltIn Position
|
||||
OpMemberDecorate %_struct_1 3 BuiltIn Position
|
||||
%float = OpTypeFloat 32
|
||||
%_runtimearr = OpTypeRuntimeArray %float
|
||||
%_struct_1 = OpTypeStruct %float %float %float %_runtimearr
|
||||
%_struct_2 = OpTypeStruct %_struct_1
|
||||
)";
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("Structure <id> 1 contains members with BuiltIn "
|
||||
"decoration. Therefore this structure may not be "
|
||||
"contained as a member of another structure type. "
|
||||
"Structure <id> 4 contains structure <id> 1."));
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations, StructContainsNonBuiltInStructGood) {
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%float = OpTypeFloat 32
|
||||
%_struct_1 = OpTypeStruct %float
|
||||
%_struct_2 = OpTypeStruct %_struct_1
|
||||
)";
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations, MultipleBuiltInObjectsConsumedByOpEntryPointBad) {
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Geometry
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Geometry %main "main" %in_1 %in_2
|
||||
OpMemberDecorate %struct_1 0 BuiltIn InvocationId
|
||||
OpMemberDecorate %struct_2 0 BuiltIn Position
|
||||
%int = OpTypeInt 32 1
|
||||
%void = OpTypeVoid
|
||||
%func = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%struct_1 = OpTypeStruct %int
|
||||
%struct_2 = OpTypeStruct %float
|
||||
%ptr_builtin_1 = OpTypePointer Input %struct_1
|
||||
%ptr_builtin_2 = OpTypePointer Input %struct_2
|
||||
%in_1 = OpVariable %ptr_builtin_1 Input
|
||||
%in_2 = OpVariable %ptr_builtin_2 Input
|
||||
%main = OpFunction %void None %func
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateAndRetrieveValidationState());
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("There must be at most one object per Storage Class "
|
||||
"that can contain a structure type containing members "
|
||||
"decorated with BuiltIn, consumed per entry-point."));
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations,
|
||||
OneBuiltInObjectPerStorageClassConsumedByOpEntryPointGood) {
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Geometry
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Geometry %main "main" %in_1 %out_1
|
||||
OpMemberDecorate %struct_1 0 BuiltIn InvocationId
|
||||
OpMemberDecorate %struct_2 0 BuiltIn Position
|
||||
%int = OpTypeInt 32 1
|
||||
%void = OpTypeVoid
|
||||
%func = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%struct_1 = OpTypeStruct %int
|
||||
%struct_2 = OpTypeStruct %float
|
||||
%ptr_builtin_1 = OpTypePointer Input %struct_1
|
||||
%ptr_builtin_2 = OpTypePointer Output %struct_2
|
||||
%in_1 = OpVariable %ptr_builtin_1 Input
|
||||
%out_1 = OpVariable %ptr_builtin_2 Output
|
||||
%main = OpFunction %void None %func
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
|
||||
}
|
||||
|
||||
TEST_F(ValidateDecorations, NoBuiltInObjectsConsumedByOpEntryPointGood) {
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Geometry
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Geometry %main "main" %in_1 %out_1
|
||||
%int = OpTypeInt 32 1
|
||||
%void = OpTypeVoid
|
||||
%func = OpTypeFunction %void
|
||||
%float = OpTypeFloat 32
|
||||
%struct_1 = OpTypeStruct %int
|
||||
%struct_2 = OpTypeStruct %float
|
||||
%ptr_builtin_1 = OpTypePointer Input %struct_1
|
||||
%ptr_builtin_2 = OpTypePointer Output %struct_2
|
||||
%in_1 = OpVariable %ptr_builtin_1 Input
|
||||
%out_1 = OpVariable %ptr_builtin_2 Output
|
||||
%main = OpFunction %void None %func
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
|
@ -326,6 +326,57 @@ TEST_F(ValidateIdWithMessage, OpEntryPointReturnTypeBad) {
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
|
||||
}
|
||||
|
||||
TEST_F(ValidateIdWithMessage, OpEntryPointInterfaceIsNotVariableTypeBad) {
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Geometry
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Geometry %main "main" %ptr_builtin_1
|
||||
OpMemberDecorate %struct_1 0 BuiltIn InvocationId
|
||||
%int = OpTypeInt 32 1
|
||||
%void = OpTypeVoid
|
||||
%func = OpTypeFunction %void
|
||||
%struct_1 = OpTypeStruct %int
|
||||
%ptr_builtin_1 = OpTypePointer Input %struct_1
|
||||
%main = OpFunction %void None %func
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("Interfaces passed to OpEntryPoint must be of type "
|
||||
"OpTypeVariable. Found OpTypePointer."));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(ValidateIdWithMessage, OpEntryPointInterfaceStorageClassBad) {
|
||||
string spirv = R"(
|
||||
OpCapability Shader
|
||||
OpCapability Geometry
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Geometry %main "main" %in_1
|
||||
OpMemberDecorate %struct_1 0 BuiltIn InvocationId
|
||||
%int = OpTypeInt 32 1
|
||||
%void = OpTypeVoid
|
||||
%func = OpTypeFunction %void
|
||||
%struct_1 = OpTypeStruct %int
|
||||
%ptr_builtin_1 = OpTypePointer Uniform %struct_1
|
||||
%in_1 = OpVariable %ptr_builtin_1 Uniform
|
||||
%main = OpFunction %void None %func
|
||||
%5 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
CompileSuccessfully(spirv);
|
||||
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
|
||||
EXPECT_THAT(getDiagnosticString(),
|
||||
HasSubstr("OpEntryPoint interfaces must be OpVariables with "
|
||||
"Storage Class of Input(1) or Output(3). Found Storage "
|
||||
"Class 2 for Entry Point id 1."));
|
||||
}
|
||||
|
||||
TEST_F(ValidateIdWithMessage, OpExecutionModeGood) {
|
||||
string spirv = kGLSL450MemoryModel + R"(
|
||||
OpEntryPoint GLCompute %3 ""
|
||||
|
Loading…
Reference in New Issue
Block a user