tint/resolver: Formatting
Change-Id: I9580a9bad5b1f281d14ee1c1fba38c18d9cff610 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/105402 Commit-Queue: Ben Clayton <bclayton@google.com> Reviewed-by: David Neto <dneto@google.com>
This commit is contained in:
parent
f9ed9d3a63
commit
feb447d9dc
|
@ -344,8 +344,8 @@ bool Validator::VariableInitializer(const ast::Variable* v,
|
||||||
break; // Allowed an initializer
|
break; // Allowed an initializer
|
||||||
default:
|
default:
|
||||||
// https://gpuweb.github.io/gpuweb/wgsl/#var-and-let
|
// https://gpuweb.github.io/gpuweb/wgsl/#var-and-let
|
||||||
// Optionally has an initializer expression, if the variable is in the
|
// Optionally has an initializer expression, if the variable is in the private or
|
||||||
// private or function address spacees.
|
// function address spacees.
|
||||||
AddError("var of address space '" + utils::ToString(address_space) +
|
AddError("var of address space '" + utils::ToString(address_space) +
|
||||||
"' cannot have an initializer. var initializers are only "
|
"' cannot have an initializer. var initializers are only "
|
||||||
"supported for the address spacees "
|
"supported for the address spacees "
|
||||||
|
@ -440,9 +440,8 @@ bool Validator::AddressSpaceLayout(const sem::Type* store_ty,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For uniform buffers, validate that the number of bytes between the
|
// For uniform buffers, validate that the number of bytes between the previous member of
|
||||||
// previous member of type struct and the current is a multiple of 16
|
// type struct and the current is a multiple of 16 bytes.
|
||||||
// bytes.
|
|
||||||
auto* const prev_member = (i == 0) ? nullptr : str->Members()[i - 1];
|
auto* const prev_member = (i == 0) ? nullptr : str->Members()[i - 1];
|
||||||
if (prev_member && is_uniform_struct(prev_member->Type())) {
|
if (prev_member && is_uniform_struct(prev_member->Type())) {
|
||||||
const uint32_t prev_to_curr_offset = m->Offset() - prev_member->Offset();
|
const uint32_t prev_to_curr_offset = m->Offset() - prev_member->Offset();
|
||||||
|
@ -469,24 +468,22 @@ bool Validator::AddressSpaceLayout(const sem::Type* store_ty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For uniform buffer array members, validate that array elements are
|
// For uniform buffer array members, validate that array elements are aligned to 16 bytes
|
||||||
// aligned to 16 bytes
|
|
||||||
if (auto* arr = store_ty->As<sem::Array>()) {
|
if (auto* arr = store_ty->As<sem::Array>()) {
|
||||||
// Recurse into the element type.
|
// Recurse into the element type.
|
||||||
// TODO(crbug.com/tint/1388): Ideally we'd pass the source for nested
|
// TODO(crbug.com/tint/1388): Ideally we'd pass the source for nested element type here, but
|
||||||
// element type here, but we can't easily get that from the semantic node.
|
// we can't easily get that from the semantic node. We should consider recursing through the
|
||||||
// We should consider recursing through the AST type nodes instead.
|
// AST type nodes instead.
|
||||||
if (!AddressSpaceLayout(arr->ElemType(), address_space, source, layouts)) {
|
if (!AddressSpaceLayout(arr->ElemType(), address_space, source, layouts)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (address_space == ast::AddressSpace::kUniform) {
|
if (address_space == ast::AddressSpace::kUniform) {
|
||||||
// We already validated that this array member is itself aligned to 16
|
// We already validated that this array member is itself aligned to 16 bytes above, so
|
||||||
// bytes above, so we only need to validate that stride is a multiple
|
// we only need to validate that stride is a multiple of 16 bytes.
|
||||||
// of 16 bytes.
|
|
||||||
if (arr->Stride() % 16 != 0) {
|
if (arr->Stride() % 16 != 0) {
|
||||||
// Since WGSL has no stride attribute, try to provide a useful hint
|
// Since WGSL has no stride attribute, try to provide a useful hint for how the
|
||||||
// for how the shader author can resolve the issue.
|
// shader author can resolve the issue.
|
||||||
std::string hint;
|
std::string hint;
|
||||||
if (arr->ElemType()->is_scalar()) {
|
if (arr->ElemType()->is_scalar()) {
|
||||||
hint =
|
hint =
|
||||||
|
@ -622,8 +619,8 @@ bool Validator::GlobalVariable(
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://gpuweb.github.io/gpuweb/wgsl/#variable-declaration
|
// https://gpuweb.github.io/gpuweb/wgsl/#variable-declaration
|
||||||
// The access mode always has a default, and except for variables in the
|
// The access mode always has a default, and except for variables in the storage address
|
||||||
// storage address space, must not be written.
|
// space, must not be written.
|
||||||
if (var->declared_access != ast::Access::kUndefined) {
|
if (var->declared_access != ast::Access::kUndefined) {
|
||||||
if (global->AddressSpace() == ast::AddressSpace::kStorage) {
|
if (global->AddressSpace() == ast::AddressSpace::kStorage) {
|
||||||
// The access mode for the storage address space can only be 'read' or
|
// The access mode for the storage address space can only be 'read' or
|
||||||
|
@ -684,8 +681,7 @@ bool Validator::GlobalVariable(
|
||||||
case ast::AddressSpace::kStorage:
|
case ast::AddressSpace::kStorage:
|
||||||
case ast::AddressSpace::kHandle: {
|
case ast::AddressSpace::kHandle: {
|
||||||
// https://gpuweb.github.io/gpuweb/wgsl/#resource-interface
|
// https://gpuweb.github.io/gpuweb/wgsl/#resource-interface
|
||||||
// Each resource variable must be declared with both group and binding
|
// Each resource variable must be declared with both group and binding attributes.
|
||||||
// attributes.
|
|
||||||
if (!decl->HasBindingPoint()) {
|
if (!decl->HasBindingPoint()) {
|
||||||
AddError("resource variables require @group and @binding attributes", decl->source);
|
AddError("resource variables require @group and @binding attributes", decl->source);
|
||||||
return false;
|
return false;
|
||||||
|
@ -709,8 +705,8 @@ bool Validator::GlobalVariable(
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://gpuweb.github.io/gpuweb/wgsl/#atomic-types
|
// https://gpuweb.github.io/gpuweb/wgsl/#atomic-types
|
||||||
// Atomic types may only be instantiated by variables in the workgroup storage
|
// Atomic types may only be instantiated by variables in the workgroup storage class or by storage
|
||||||
// class or by storage buffer variables with a read_write access mode.
|
// buffer variables with a read_write access mode.
|
||||||
bool Validator::AtomicVariable(
|
bool Validator::AtomicVariable(
|
||||||
const sem::Variable* var,
|
const sem::Variable* var,
|
||||||
std::unordered_map<const sem::Type*, const Source&> atomic_composite_info) const {
|
std::unordered_map<const sem::Type*, const Source&> atomic_composite_info) const {
|
||||||
|
@ -763,9 +759,8 @@ bool Validator::Var(const sem::Variable* v) const {
|
||||||
|
|
||||||
if (storage_ty->is_handle() && var->declared_address_space != ast::AddressSpace::kNone) {
|
if (storage_ty->is_handle() && var->declared_address_space != ast::AddressSpace::kNone) {
|
||||||
// https://gpuweb.github.io/gpuweb/wgsl/#module-scope-variables
|
// https://gpuweb.github.io/gpuweb/wgsl/#module-scope-variables
|
||||||
// If the store type is a texture type or a sampler type, then the
|
// If the store type is a texture type or a sampler type, then the variable declaration must
|
||||||
// variable declaration must not have a address space attribute. The
|
// not have a address space attribute. The address space will always be handle.
|
||||||
// address space will always be handle.
|
|
||||||
AddError(
|
AddError(
|
||||||
"variables of type '" + sem_.TypeNameOf(storage_ty) + "' must not have a address space",
|
"variables of type '" + sem_.TypeNameOf(storage_ty) + "' must not have a address space",
|
||||||
var->source);
|
var->source);
|
||||||
|
@ -1127,10 +1122,10 @@ bool Validator::EntryPoint(const sem::Function* func, ast::PipelineStage stage)
|
||||||
auto* decl = func->Declaration();
|
auto* decl = func->Declaration();
|
||||||
|
|
||||||
// Use a lambda to validate the entry point attributes for a type.
|
// Use a lambda to validate the entry point attributes for a type.
|
||||||
// Persistent state is used to track which builtins and locations have
|
// Persistent state is used to track which builtins and locations have already been seen, in
|
||||||
// already been seen, in order to catch conflicts.
|
// order to catch conflicts.
|
||||||
// TODO(jrprice): This state could be stored in sem::Function instead, and
|
// TODO(jrprice): This state could be stored in sem::Function instead, and then passed to
|
||||||
// then passed to sem::Function since it would be useful there too.
|
// sem::Function since it would be useful there too.
|
||||||
std::unordered_set<ast::BuiltinValue> builtins;
|
std::unordered_set<ast::BuiltinValue> builtins;
|
||||||
std::unordered_set<uint32_t> locations;
|
std::unordered_set<uint32_t> locations;
|
||||||
enum class ParamOrRetType {
|
enum class ParamOrRetType {
|
||||||
|
@ -1145,8 +1140,7 @@ bool Validator::EntryPoint(const sem::Function* func, ast::PipelineStage stage)
|
||||||
bool is_struct_member,
|
bool is_struct_member,
|
||||||
std::optional<uint32_t> location) {
|
std::optional<uint32_t> location) {
|
||||||
// Temporally forbid using f16 types in entry point IO.
|
// Temporally forbid using f16 types in entry point IO.
|
||||||
// TODO(tint:1473, tint:1502): Remove this error after f16 is supported in entry point
|
// TODO(tint:1473, tint:1502): Remove this error after f16 is supported in entry point IO.
|
||||||
// IO.
|
|
||||||
if (Is<sem::F16>(sem::Type::DeepestElementOf(ty))) {
|
if (Is<sem::F16>(sem::Type::DeepestElementOf(ty))) {
|
||||||
AddError("entry point IO of f16 types is not implemented yet", source);
|
AddError("entry point IO of f16 types is not implemented yet", source);
|
||||||
return false;
|
return false;
|
||||||
|
@ -1324,9 +1318,8 @@ bool Validator::EntryPoint(const sem::Function* func, ast::PipelineStage stage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear IO sets after parameter validation. Builtin and location attributes
|
// Clear IO sets after parameter validation. Builtin and location attributes in return types
|
||||||
// in return types should be validated independently from those used in
|
// should be validated independently from those used in parameters.
|
||||||
// parameters.
|
|
||||||
builtins.clear();
|
builtins.clear();
|
||||||
locations.clear();
|
locations.clear();
|
||||||
|
|
||||||
|
@ -1352,9 +1345,7 @@ bool Validator::EntryPoint(const sem::Function* func, ast::PipelineStage stage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
AddError(
|
AddError("a vertex shader must include the 'position' builtin in its return type",
|
||||||
"a vertex shader must include the 'position' builtin in its return "
|
|
||||||
"type",
|
|
||||||
decl->source);
|
decl->source);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1362,9 +1353,7 @@ bool Validator::EntryPoint(const sem::Function* func, ast::PipelineStage stage)
|
||||||
|
|
||||||
if (decl->PipelineStage() == ast::PipelineStage::kCompute) {
|
if (decl->PipelineStage() == ast::PipelineStage::kCompute) {
|
||||||
if (!ast::HasAttribute<ast::WorkgroupAttribute>(decl->attributes)) {
|
if (!ast::HasAttribute<ast::WorkgroupAttribute>(decl->attributes)) {
|
||||||
AddError(
|
AddError("a compute shader must include 'workgroup_size' in its attributes",
|
||||||
"a compute shader must include 'workgroup_size' in its "
|
|
||||||
"attributes",
|
|
||||||
decl->source);
|
decl->source);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1385,16 +1374,14 @@ bool Validator::EntryPoint(const sem::Function* func, ast::PipelineStage stage)
|
||||||
IsValidationEnabled(res.first->second->attributes,
|
IsValidationEnabled(res.first->second->attributes,
|
||||||
ast::DisabledValidation::kBindingPointCollision)) {
|
ast::DisabledValidation::kBindingPointCollision)) {
|
||||||
// https://gpuweb.github.io/gpuweb/wgsl/#resource-interface
|
// https://gpuweb.github.io/gpuweb/wgsl/#resource-interface
|
||||||
// Bindings must not alias within a shader stage: two different
|
// Bindings must not alias within a shader stage: two different variables in the
|
||||||
// variables in the resource interface of a given shader must not have
|
// resource interface of a given shader must not have the same group and binding values,
|
||||||
// the same group and binding values, when considered as a pair of
|
// when considered as a pair of values.
|
||||||
// values.
|
|
||||||
auto func_name = symbols_.NameFor(decl->symbol);
|
auto func_name = symbols_.NameFor(decl->symbol);
|
||||||
AddError("entry point '" + func_name +
|
AddError(
|
||||||
"' references multiple variables that use the "
|
"entry point '" + func_name +
|
||||||
"same resource binding @group(" +
|
"' references multiple variables that use the same resource binding @group(" +
|
||||||
std::to_string(bp.group) + "), @binding(" + std::to_string(bp.binding) +
|
std::to_string(bp.group) + "), @binding(" + std::to_string(bp.binding) + ")",
|
||||||
")",
|
|
||||||
var_decl->source);
|
var_decl->source);
|
||||||
AddNote("first resource binding usage declared here", res.first->second->source);
|
AddNote("first resource binding usage declared here", res.first->second->source);
|
||||||
return false;
|
return false;
|
||||||
|
@ -1453,9 +1440,9 @@ bool Validator::BreakStatement(const sem::Statement* stmt,
|
||||||
if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ true, current_statement)) {
|
if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ true, current_statement)) {
|
||||||
auto fail = [&](const char* note_msg, const Source& note_src) {
|
auto fail = [&](const char* note_msg, const Source& note_src) {
|
||||||
constexpr const char* kErrorMsg =
|
constexpr const char* kErrorMsg =
|
||||||
"break statement in a continuing block must be the single statement "
|
"break statement in a continuing block must be the single statement of an if "
|
||||||
"of an if statement's true or false block, and that if statement "
|
"statement's true or false block, and that if statement must be the last statement "
|
||||||
"must be the last statement of the continuing block";
|
"of the continuing block";
|
||||||
AddError(kErrorMsg, stmt->Declaration()->source);
|
AddError(kErrorMsg, stmt->Declaration()->source);
|
||||||
AddNote(note_msg, note_src);
|
AddNote(note_msg, note_src);
|
||||||
return false;
|
return false;
|
||||||
|
@ -1491,15 +1478,14 @@ bool Validator::BreakStatement(const sem::Statement* stmt,
|
||||||
|
|
||||||
if (if_stmt->Parent()->Declaration() != continuing) {
|
if (if_stmt->Parent()->Declaration() != continuing) {
|
||||||
return fail(
|
return fail(
|
||||||
"if statement containing break statement is not directly in "
|
"if statement containing break statement is not directly in continuing block",
|
||||||
"continuing block",
|
|
||||||
if_stmt->Declaration()->source);
|
if_stmt->Declaration()->source);
|
||||||
}
|
}
|
||||||
if (auto* cont_block = continuing->As<ast::BlockStatement>()) {
|
if (auto* cont_block = continuing->As<ast::BlockStatement>()) {
|
||||||
if (if_stmt->Declaration() != cont_block->Last()) {
|
if (if_stmt->Declaration() != cont_block->Last()) {
|
||||||
return fail(
|
return fail(
|
||||||
"if statement containing break statement is not the last "
|
"if statement containing break statement is not the last statement of the "
|
||||||
"statement of the continuing block",
|
"continuing block",
|
||||||
if_stmt->Declaration()->source);
|
if_stmt->Declaration()->source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1570,9 +1556,7 @@ bool Validator::FallthroughStatement(const sem::Statement* stmt) const {
|
||||||
if (c->Declaration() != s->Declaration()->body.Back()) {
|
if (c->Declaration() != s->Declaration()->body.Back()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
AddError(
|
AddError("a fallthrough statement must not be used in the last switch case",
|
||||||
"a fallthrough statement must not be used in the last switch "
|
|
||||||
"case",
|
|
||||||
stmt->Declaration()->source);
|
stmt->Declaration()->source);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1826,8 +1810,8 @@ bool Validator::FunctionCall(const sem::Call* call, sem::Statement* current_stat
|
||||||
IsValidationEnabled(param->Declaration()->attributes,
|
IsValidationEnabled(param->Declaration()->attributes,
|
||||||
ast::DisabledValidation::kIgnoreInvalidPointerArgument)) {
|
ast::DisabledValidation::kIgnoreInvalidPointerArgument)) {
|
||||||
AddError(
|
AddError(
|
||||||
"expected an address-of expression of a variable identifier "
|
"expected an address-of expression of a variable identifier expression or a "
|
||||||
"expression or a function parameter",
|
"function parameter",
|
||||||
arg_expr->source);
|
arg_expr->source);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1886,8 +1870,7 @@ bool Validator::StructureConstructor(const ast::CallExpression* ctor,
|
||||||
auto* value_ty = sem_.TypeOf(value);
|
auto* value_ty = sem_.TypeOf(value);
|
||||||
if (member->Type() != value_ty->UnwrapRef()) {
|
if (member->Type() != value_ty->UnwrapRef()) {
|
||||||
AddError(
|
AddError(
|
||||||
"type in struct constructor does not match struct member type: "
|
"type in struct constructor does not match struct member type: expected '" +
|
||||||
"expected '" +
|
|
||||||
sem_.TypeNameOf(member->Type()) + "', found '" + sem_.TypeNameOf(value_ty) +
|
sem_.TypeNameOf(member->Type()) + "', found '" + sem_.TypeNameOf(value_ty) +
|
||||||
"'",
|
"'",
|
||||||
value->source);
|
value->source);
|
||||||
|
@ -2140,9 +2123,8 @@ bool Validator::ArrayStrideAttribute(const ast::StrideAttribute* attr,
|
||||||
// at least the size of the element type, and be a multiple of the
|
// at least the size of the element type, and be a multiple of the
|
||||||
// element type's alignment value.
|
// element type's alignment value.
|
||||||
AddError(
|
AddError(
|
||||||
"arrays decorated with the stride attribute must have a stride "
|
"arrays decorated with the stride attribute must have a stride that is at least the "
|
||||||
"that is at least the size of the element type, and be a multiple "
|
"size of the element type, and be a multiple of the element type's alignment value",
|
||||||
"of the element type's alignment value",
|
|
||||||
attr->source);
|
attr->source);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2189,8 +2171,7 @@ bool Validator::Structure(const sem::Struct* str, ast::PipelineStage stage) cons
|
||||||
}
|
}
|
||||||
} else if (!IsFixedFootprint(member->Type())) {
|
} else if (!IsFixedFootprint(member->Type())) {
|
||||||
AddError(
|
AddError(
|
||||||
"a struct that contains a runtime array cannot be nested inside "
|
"a struct that contains a runtime array cannot be nested inside another struct",
|
||||||
"another struct",
|
|
||||||
member->Declaration()->source);
|
member->Declaration()->source);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2283,8 +2264,8 @@ bool Validator::LocationAttribute(const ast::LocationAttribute* loc_attr,
|
||||||
AddError("cannot apply 'location' attribute to declaration of type '" + invalid_type + "'",
|
AddError("cannot apply 'location' attribute to declaration of type '" + invalid_type + "'",
|
||||||
source);
|
source);
|
||||||
AddNote(
|
AddNote(
|
||||||
"'location' attribute must only be applied to declarations of "
|
"'location' attribute must only be applied to declarations of numeric scalar or "
|
||||||
"numeric scalar or numeric vector type",
|
"numeric vector type",
|
||||||
loc_attr->source);
|
loc_attr->source);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2304,9 +2285,7 @@ bool Validator::Return(const ast::ReturnStatement* ret,
|
||||||
const sem::Type* ret_type,
|
const sem::Type* ret_type,
|
||||||
sem::Statement* current_statement) const {
|
sem::Statement* current_statement) const {
|
||||||
if (func_type->UnwrapRef() != ret_type) {
|
if (func_type->UnwrapRef() != ret_type) {
|
||||||
AddError(
|
AddError("return statement type must match its function return type, returned '" +
|
||||||
"return statement type must match its function "
|
|
||||||
"return type, returned '" +
|
|
||||||
sem_.TypeNameOf(ret_type) + "', expected '" + sem_.TypeNameOf(func_type) + "'",
|
sem_.TypeNameOf(ret_type) + "', expected '" + sem_.TypeNameOf(func_type) + "'",
|
||||||
ret->source);
|
ret->source);
|
||||||
return false;
|
return false;
|
||||||
|
@ -2327,9 +2306,7 @@ bool Validator::Return(const ast::ReturnStatement* ret,
|
||||||
bool Validator::SwitchStatement(const ast::SwitchStatement* s) {
|
bool Validator::SwitchStatement(const ast::SwitchStatement* s) {
|
||||||
auto* cond_ty = sem_.TypeOf(s->condition)->UnwrapRef();
|
auto* cond_ty = sem_.TypeOf(s->condition)->UnwrapRef();
|
||||||
if (!cond_ty->is_integer_scalar()) {
|
if (!cond_ty->is_integer_scalar()) {
|
||||||
AddError(
|
AddError("switch statement selector expression must be of a scalar integer type",
|
||||||
"switch statement selector expression must be of a "
|
|
||||||
"scalar integer type",
|
|
||||||
s->condition->source);
|
s->condition->source);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2351,8 +2328,7 @@ bool Validator::SwitchStatement(const ast::SwitchStatement* s) {
|
||||||
for (auto* selector : case_stmt->selectors) {
|
for (auto* selector : case_stmt->selectors) {
|
||||||
if (cond_ty != sem_.TypeOf(selector)) {
|
if (cond_ty != sem_.TypeOf(selector)) {
|
||||||
AddError(
|
AddError(
|
||||||
"the case selector values must have the same "
|
"the case selector values must have the same type as the selector expression.",
|
||||||
"type as the selector expression.",
|
|
||||||
case_stmt->source);
|
case_stmt->source);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2397,8 +2373,8 @@ bool Validator::Assignment(const ast::Statement* a, const sem::Type* rhs_ty) con
|
||||||
if (!ty->IsConstructible() &&
|
if (!ty->IsConstructible() &&
|
||||||
!ty->IsAnyOf<sem::Pointer, sem::Texture, sem::Sampler, sem::AbstractNumeric>()) {
|
!ty->IsAnyOf<sem::Pointer, sem::Texture, sem::Sampler, sem::AbstractNumeric>()) {
|
||||||
AddError("cannot assign '" + sem_.TypeNameOf(rhs_ty) +
|
AddError("cannot assign '" + sem_.TypeNameOf(rhs_ty) +
|
||||||
"' to '_'. '_' can only be assigned a constructible, pointer, "
|
"' to '_'. '_' can only be assigned a constructible, pointer, texture or "
|
||||||
"texture or sampler type",
|
"sampler type",
|
||||||
rhs->source);
|
rhs->source);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue