|  | // Copyright 2020 The Tint Authors. | 
|  | // | 
|  | // 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. | 
|  |  | 
|  | #include "src/inspector/inspector.h" | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "gtest/gtest.h" | 
|  | #include "src/ast/assignment_statement.h" | 
|  | #include "src/ast/bool_literal.h" | 
|  | #include "src/ast/call_expression.h" | 
|  | #include "src/ast/call_statement.h" | 
|  | #include "src/ast/constant_id_decoration.h" | 
|  | #include "src/ast/decorated_variable.h" | 
|  | #include "src/ast/float_literal.h" | 
|  | #include "src/ast/function.h" | 
|  | #include "src/ast/identifier_expression.h" | 
|  | #include "src/ast/member_accessor_expression.h" | 
|  | #include "src/ast/null_literal.h" | 
|  | #include "src/ast/pipeline_stage.h" | 
|  | #include "src/ast/return_statement.h" | 
|  | #include "src/ast/scalar_constructor_expression.h" | 
|  | #include "src/ast/sint_literal.h" | 
|  | #include "src/ast/stage_decoration.h" | 
|  | #include "src/ast/stride_decoration.h" | 
|  | #include "src/ast/struct_block_decoration.h" | 
|  | #include "src/ast/struct_decoration.h" | 
|  | #include "src/ast/struct_member.h" | 
|  | #include "src/ast/struct_member_decoration.h" | 
|  | #include "src/ast/struct_member_offset_decoration.h" | 
|  | #include "src/ast/type/access_control_type.h" | 
|  | #include "src/ast/type/array_type.h" | 
|  | #include "src/ast/type/bool_type.h" | 
|  | #include "src/ast/type/depth_texture_type.h" | 
|  | #include "src/ast/type/f32_type.h" | 
|  | #include "src/ast/type/i32_type.h" | 
|  | #include "src/ast/type/matrix_type.h" | 
|  | #include "src/ast/type/multisampled_texture_type.h" | 
|  | #include "src/ast/type/pointer_type.h" | 
|  | #include "src/ast/type/sampled_texture_type.h" | 
|  | #include "src/ast/type/sampler_type.h" | 
|  | #include "src/ast/type/struct_type.h" | 
|  | #include "src/ast/type/type.h" | 
|  | #include "src/ast/type/u32_type.h" | 
|  | #include "src/ast/type/vector_type.h" | 
|  | #include "src/ast/type/void_type.h" | 
|  | #include "src/ast/uint_literal.h" | 
|  | #include "src/ast/variable_decl_statement.h" | 
|  | #include "src/ast/variable_decoration.h" | 
|  | #include "src/ast/workgroup_decoration.h" | 
|  | #include "src/context.h" | 
|  | #include "src/type_determiner.h" | 
|  | #include "tint/tint.h" | 
|  |  | 
|  | namespace tint { | 
|  | namespace inspector { | 
|  | namespace { | 
|  |  | 
|  | class InspectorHelper { | 
|  | public: | 
|  | InspectorHelper() | 
|  | : td_(std::make_unique<TypeDeterminer>(&ctx_, &mod_)), | 
|  | inspector_(std::make_unique<Inspector>(mod_)), | 
|  | sampler_type_(ast::type::SamplerKind::kSampler), | 
|  | comparison_sampler_type_(ast::type::SamplerKind::kComparisonSampler) {} | 
|  |  | 
|  | /// Generates an empty function | 
|  | /// @param name name of the function created | 
|  | /// @returns a function object | 
|  | ast::Function* MakeEmptyBodyFunction(std::string name) { | 
|  | auto* body = create<ast::BlockStatement>(); | 
|  | body->append(create<ast::ReturnStatement>()); | 
|  | return create<ast::Function>(name, ast::VariableList(), void_type(), body); | 
|  | } | 
|  |  | 
|  | /// Generates a function that calls another | 
|  | /// @param caller name of the function created | 
|  | /// @param callee name of the function to be called | 
|  | /// @returns a function object | 
|  | ast::Function* MakeCallerBodyFunction(std::string caller, | 
|  | std::string callee) { | 
|  | auto* body = create<ast::BlockStatement>(); | 
|  | auto* ident_expr = create<ast::IdentifierExpression>(callee); | 
|  | auto* call_expr = | 
|  | create<ast::CallExpression>(ident_expr, ast::ExpressionList()); | 
|  | body->append(create<ast::CallStatement>(call_expr)); | 
|  | body->append(create<ast::ReturnStatement>()); | 
|  | return create<ast::Function>(caller, ast::VariableList(), void_type(), | 
|  | body); | 
|  | } | 
|  |  | 
|  | /// Add In/Out variables to the global variables | 
|  | /// @param inout_vars tuples of {in, out} that will be added as entries to the | 
|  | ///                   global variables | 
|  | void AddInOutVariables( | 
|  | std::vector<std::tuple<std::string, std::string>> inout_vars) { | 
|  | for (auto inout : inout_vars) { | 
|  | std::string in, out; | 
|  | std::tie(in, out) = inout; | 
|  | auto* in_var = | 
|  | create<ast::Variable>(in, ast::StorageClass::kInput, u32_type()); | 
|  | auto* out_var = | 
|  | create<ast::Variable>(out, ast::StorageClass::kOutput, u32_type()); | 
|  | mod()->AddGlobalVariable(in_var); | 
|  | mod()->AddGlobalVariable(out_var); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Generates a function that references in/out variables | 
|  | /// @param name name of the function created | 
|  | /// @param inout_vars tuples of {in, out} that will be converted into out = in | 
|  | ///                   calls in the function body | 
|  | /// @returns a function object | 
|  | ast::Function* MakeInOutVariableBodyFunction( | 
|  | std::string name, | 
|  | std::vector<std::tuple<std::string, std::string>> inout_vars) { | 
|  | auto* body = create<ast::BlockStatement>(); | 
|  | for (auto inout : inout_vars) { | 
|  | std::string in, out; | 
|  | std::tie(in, out) = inout; | 
|  | body->append(create<ast::AssignmentStatement>( | 
|  | create<ast::IdentifierExpression>(out), | 
|  | create<ast::IdentifierExpression>(in))); | 
|  | } | 
|  | body->append(create<ast::ReturnStatement>()); | 
|  | return create<ast::Function>(name, ast::VariableList(), void_type(), body); | 
|  | } | 
|  |  | 
|  | /// Generates a function that references in/out variables and calls another | 
|  | /// function. | 
|  | /// @param caller name of the function created | 
|  | /// @param callee name of the function to be called | 
|  | /// @param inout_vars tuples of {in, out} that will be converted into out = in | 
|  | ///                   calls in the function body | 
|  | /// @returns a function object | 
|  | ast::Function* MakeInOutVariableCallerBodyFunction( | 
|  | std::string caller, | 
|  | std::string callee, | 
|  | std::vector<std::tuple<std::string, std::string>> inout_vars) { | 
|  | auto* body = create<ast::BlockStatement>(); | 
|  | for (auto inout : inout_vars) { | 
|  | std::string in, out; | 
|  | std::tie(in, out) = inout; | 
|  | body->append(create<ast::AssignmentStatement>( | 
|  | create<ast::IdentifierExpression>(out), | 
|  | create<ast::IdentifierExpression>(in))); | 
|  | } | 
|  | auto* ident_expr = create<ast::IdentifierExpression>(callee); | 
|  | auto* call_expr = | 
|  | create<ast::CallExpression>(ident_expr, ast::ExpressionList()); | 
|  | body->append(create<ast::CallStatement>(call_expr)); | 
|  | body->append(create<ast::ReturnStatement>()); | 
|  | return create<ast::Function>(caller, ast::VariableList(), void_type(), | 
|  | body); | 
|  | } | 
|  |  | 
|  | /// Add a Constant ID to the global variables. | 
|  | /// @param name name of the variable to add | 
|  | /// @param id id number for the constant id | 
|  | /// @param type type of the variable | 
|  | /// @param val value to initialize the variable with, if NULL no initializer | 
|  | ///            will be added. | 
|  | template <class T> | 
|  | void AddConstantID(std::string name, | 
|  | uint32_t id, | 
|  | ast::type::Type* type, | 
|  | T* val) { | 
|  | auto* dvar = create<ast::DecoratedVariable>( | 
|  | create<ast::Variable>(name, ast::StorageClass::kNone, type)); | 
|  | dvar->set_is_const(true); | 
|  | ast::VariableDecorationList decos; | 
|  | decos.push_back(create<ast::ConstantIdDecoration>(id, Source{})); | 
|  | dvar->set_decorations(decos); | 
|  | if (val) { | 
|  | dvar->set_constructor( | 
|  | create<ast::ScalarConstructorExpression>(MakeLiteral(type, val))); | 
|  | } | 
|  | mod()->AddGlobalVariable(dvar); | 
|  | } | 
|  |  | 
|  | /// @param type AST type of the literal, must resolve to BoolLiteral | 
|  | /// @param val scalar value for the literal to contain | 
|  | /// @returns a Literal of the expected type and value | 
|  | ast::Literal* MakeLiteral(ast::type::Type* type, bool* val) { | 
|  | return create<ast::BoolLiteral>(type, *val); | 
|  | } | 
|  |  | 
|  | /// @param type AST type of the literal, must resolve to UIntLiteral | 
|  | /// @param val scalar value for the literal to contain | 
|  | /// @returns a Literal of the expected type and value | 
|  | ast::Literal* MakeLiteral(ast::type::Type* type, uint32_t* val) { | 
|  | return create<ast::UintLiteral>(type, *val); | 
|  | } | 
|  |  | 
|  | /// @param type AST type of the literal, must resolve to IntLiteral | 
|  | /// @param val scalar value for the literal to contain | 
|  | /// @returns a Literal of the expected type and value | 
|  | ast::Literal* MakeLiteral(ast::type::Type* type, int32_t* val) { | 
|  | return create<ast::SintLiteral>(type, *val); | 
|  | } | 
|  |  | 
|  | /// @param type AST type of the literal, must resolve to FloattLiteral | 
|  | /// @param val scalar value for the literal to contain | 
|  | /// @returns a Literal of the expected type and value | 
|  | ast::Literal* MakeLiteral(ast::type::Type* type, float* val) { | 
|  | return create<ast::FloatLiteral>(type, *val); | 
|  | } | 
|  |  | 
|  | /// @param vec Vector of strings to be searched | 
|  | /// @param str String to be searching for | 
|  | /// @returns true if str is in vec, otherwise false | 
|  | bool ContainsString(const std::vector<std::string>& vec, | 
|  | const std::string& str) { | 
|  | for (auto& s : vec) { | 
|  | if (s == str) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /// Builds a string for accessing a member in a generated struct | 
|  | /// @param idx index of member | 
|  | /// @param type type of member | 
|  | /// @returns a string for the member | 
|  | std::string StructMemberName(size_t idx, ast::type::Type* type) { | 
|  | return std::to_string(idx) + type->type_name(); | 
|  | } | 
|  |  | 
|  | /// Generates a struct type | 
|  | /// @param name name for the type | 
|  | /// @param members_info a vector of {type, offset} where each entry is the | 
|  | ///                     type and offset of a member of the struct | 
|  | /// @param is_block whether or not to decorate as a Block | 
|  | /// @returns a struct type | 
|  | std::unique_ptr<ast::type::StructType> MakeStructType( | 
|  | const std::string& name, | 
|  | std::vector<std::tuple<ast::type::Type*, uint32_t>> members_info, | 
|  | bool is_block) { | 
|  | ast::StructMemberList members; | 
|  | for (auto& member_info : members_info) { | 
|  | ast::type::Type* type; | 
|  | uint32_t offset; | 
|  | std::tie(type, offset) = member_info; | 
|  |  | 
|  | ast::StructMemberDecorationList deco; | 
|  | deco.push_back( | 
|  | create<ast::StructMemberOffsetDecoration>(offset, Source{})); | 
|  |  | 
|  | members.push_back(create<ast::StructMember>( | 
|  | StructMemberName(members.size(), type), type, deco)); | 
|  | } | 
|  |  | 
|  | ast::StructDecorationList decos; | 
|  | if (is_block) { | 
|  | decos.push_back(create<ast::StructBlockDecoration>(Source{})); | 
|  | } | 
|  |  | 
|  | auto* str = create<ast::Struct>(decos, members); | 
|  |  | 
|  | return std::make_unique<ast::type::StructType>(name, str); | 
|  | } | 
|  |  | 
|  | /// Generates types appropriate for using in an uniform buffer | 
|  | /// @param name name for the type | 
|  | /// @param members_info a vector of {type, offset} where each entry is the | 
|  | ///                     type and offset of a member of the struct | 
|  | /// @returns a tuple {struct type, access control type}, where the struct has | 
|  | ///          the layout for an uniform buffer, and the control type wraps the | 
|  | ///          struct. | 
|  | std::tuple<std::unique_ptr<ast::type::StructType>, | 
|  | std::unique_ptr<ast::type::AccessControlType>> | 
|  | MakeUniformBufferTypes( | 
|  | const std::string& name, | 
|  | std::vector<std::tuple<ast::type::Type*, uint32_t>> members_info) { | 
|  | auto struct_type = MakeStructType(name, members_info, true); | 
|  | auto access_type = std::make_unique<ast::type::AccessControlType>( | 
|  | ast::AccessControl::kReadOnly, struct_type.get()); | 
|  | return {std::move(struct_type), std::move(access_type)}; | 
|  | } | 
|  |  | 
|  | /// Generates types appropriate for using in a storage buffer | 
|  | /// @param name name for the type | 
|  | /// @param members_info a vector of {type, offset} where each entry is the | 
|  | ///                     type and offset of a member of the struct | 
|  | /// @returns a tuple {struct type, access control type}, where the struct has | 
|  | ///          the layout for a storage buffer, and the control type wraps the | 
|  | ///          struct. | 
|  | std::tuple<std::unique_ptr<ast::type::StructType>, | 
|  | std::unique_ptr<ast::type::AccessControlType>> | 
|  | MakeStorageBufferTypes( | 
|  | const std::string& name, | 
|  | std::vector<std::tuple<ast::type::Type*, uint32_t>> members_info) { | 
|  | auto struct_type = MakeStructType(name, members_info, false); | 
|  | auto access_type = std::make_unique<ast::type::AccessControlType>( | 
|  | ast::AccessControl::kReadWrite, struct_type.get()); | 
|  | return {std::move(struct_type), std::move(access_type)}; | 
|  | } | 
|  |  | 
|  | /// Generates types appropriate for using in a read-only storage buffer | 
|  | /// @param name name for the type | 
|  | /// @param members_info a vector of {type, offset} where each entry is the | 
|  | ///                     type and offset of a member of the struct | 
|  | /// @returns a tuple {struct type, access control type}, where the struct has | 
|  | ///          the layout for a read-only storage buffer, and the control type | 
|  | ///          wraps the struct. | 
|  | std::tuple<std::unique_ptr<ast::type::StructType>, | 
|  | std::unique_ptr<ast::type::AccessControlType>> | 
|  | MakeReadOnlyStorageBufferTypes( | 
|  | const std::string& name, | 
|  | std::vector<std::tuple<ast::type::Type*, uint32_t>> members_info) { | 
|  | auto struct_type = MakeStructType(name, members_info, false); | 
|  | auto access_type = std::make_unique<ast::type::AccessControlType>( | 
|  | ast::AccessControl::kReadOnly, struct_type.get()); | 
|  | return {std::move(struct_type), std::move(access_type)}; | 
|  | } | 
|  |  | 
|  | /// Adds a binding variable with a struct type to the module | 
|  | /// @param name the name of the variable | 
|  | /// @param type the type to use | 
|  | /// @param storage_class the storage class to use | 
|  | /// @param set the binding group/set to use for the uniform buffer | 
|  | /// @param binding the binding number to use for the uniform buffer | 
|  | void AddBinding(const std::string& name, | 
|  | ast::type::Type* type, | 
|  | ast::StorageClass storage_class, | 
|  | uint32_t set, | 
|  | uint32_t binding) { | 
|  | auto* var = create<ast::DecoratedVariable>( | 
|  | create<ast::Variable>(name, storage_class, type)); | 
|  | ast::VariableDecorationList decorations; | 
|  |  | 
|  | decorations.push_back(create<ast::BindingDecoration>(binding, Source{})); | 
|  | decorations.push_back(create<ast::SetDecoration>(set, Source{})); | 
|  | var->set_decorations(decorations); | 
|  |  | 
|  | mod()->AddGlobalVariable(var); | 
|  | } | 
|  |  | 
|  | /// Adds an uniform buffer variable to the module | 
|  | /// @param name the name of the variable | 
|  | /// @param type the type to use | 
|  | /// @param set the binding group/set to use for the uniform buffer | 
|  | /// @param binding the binding number to use for the uniform buffer | 
|  | void AddUniformBuffer(const std::string& name, | 
|  | ast::type::Type* type, | 
|  | uint32_t set, | 
|  | uint32_t binding) { | 
|  | AddBinding(name, type, ast::StorageClass::kUniform, set, binding); | 
|  | } | 
|  |  | 
|  | /// Adds a storage buffer variable to the module | 
|  | /// @param name the name of the variable | 
|  | /// @param type the type to use | 
|  | /// @param set the binding group/set to use for the storage buffer | 
|  | /// @param binding the binding number to use for the storage buffer | 
|  | void AddStorageBuffer(const std::string& name, | 
|  | ast::type::Type* type, | 
|  | uint32_t set, | 
|  | uint32_t binding) { | 
|  | AddBinding(name, type, ast::StorageClass::kStorageBuffer, set, binding); | 
|  | } | 
|  |  | 
|  | /// Generates a function that references a specific struct variable | 
|  | /// @param func_name name of the function created | 
|  | /// @param struct_name name of the struct variabler to be accessed | 
|  | /// @param members list of members to access, by index and type | 
|  | /// @returns a function that references all of the members specified | 
|  | ast::Function* MakeStructVariableReferenceBodyFunction( | 
|  | std::string func_name, | 
|  | std::string struct_name, | 
|  | std::vector<std::tuple<size_t, ast::type::Type*>> members) { | 
|  | auto* body = create<ast::BlockStatement>(); | 
|  |  | 
|  | for (auto member : members) { | 
|  | size_t member_idx; | 
|  | ast::type::Type* member_type; | 
|  | std::tie(member_idx, member_type) = member; | 
|  | std::string member_name = StructMemberName(member_idx, member_type); | 
|  | body->append(create<ast::VariableDeclStatement>(create<ast::Variable>( | 
|  | "local" + member_name, ast::StorageClass::kNone, member_type))); | 
|  | } | 
|  |  | 
|  | for (auto member : members) { | 
|  | size_t member_idx; | 
|  | ast::type::Type* member_type; | 
|  | std::tie(member_idx, member_type) = member; | 
|  | std::string member_name = StructMemberName(member_idx, member_type); | 
|  | body->append(create<ast::AssignmentStatement>( | 
|  | create<ast::IdentifierExpression>("local" + member_name), | 
|  | create<ast::MemberAccessorExpression>( | 
|  | create<ast::IdentifierExpression>(struct_name), | 
|  | create<ast::IdentifierExpression>(member_name)))); | 
|  | } | 
|  |  | 
|  | body->append(create<ast::ReturnStatement>()); | 
|  | return create<ast::Function>(func_name, ast::VariableList(), void_type(), | 
|  | body); | 
|  | } | 
|  |  | 
|  | /// Adds a regular sampler variable to the module | 
|  | /// @param name the name of the variable | 
|  | /// @param set the binding group/set to use for the storage buffer | 
|  | /// @param binding the binding number to use for the storage buffer | 
|  | void AddSampler(const std::string& name, uint32_t set, uint32_t binding) { | 
|  | AddBinding(name, sampler_type(), ast::StorageClass::kUniformConstant, set, | 
|  | binding); | 
|  | } | 
|  |  | 
|  | /// Adds a comparison sampler variable to the module | 
|  | /// @param name the name of the variable | 
|  | /// @param set the binding group/set to use for the storage buffer | 
|  | /// @param binding the binding number to use for the storage buffer | 
|  | void AddComparisonSampler(const std::string& name, | 
|  | uint32_t set, | 
|  | uint32_t binding) { | 
|  | AddBinding(name, comparison_sampler_type(), | 
|  | ast::StorageClass::kUniformConstant, set, binding); | 
|  | } | 
|  |  | 
|  | /// Generates a SampledTextureType appropriate for the params | 
|  | /// @param dim the dimensions of the texture | 
|  | /// @param type the data type of the sampled texture | 
|  | /// @returns the generated SampleTextureType | 
|  | std::unique_ptr<ast::type::SampledTextureType> MakeSampledTextureType( | 
|  | ast::type::TextureDimension dim, | 
|  | ast::type::Type* type) { | 
|  | return std::make_unique<ast::type::SampledTextureType>(dim, type); | 
|  | } | 
|  |  | 
|  | /// Generates a DepthTextureType appropriate for the params | 
|  | /// @param dim the dimensions of the texture | 
|  | /// @returns the generated DepthTextureType | 
|  | std::unique_ptr<ast::type::DepthTextureType> MakeDepthTextureType( | 
|  | ast::type::TextureDimension dim) { | 
|  | return std::make_unique<ast::type::DepthTextureType>(dim); | 
|  | } | 
|  |  | 
|  | /// Generates a MultisampledTextureType appropriate for the params | 
|  | /// @param dim the dimensions of the texture | 
|  | /// @param type the data type of the sampled texture | 
|  | /// @returns the generated SampleTextureType | 
|  | std::unique_ptr<ast::type::MultisampledTextureType> | 
|  | MakeMultisampledTextureType(ast::type::TextureDimension dim, | 
|  | ast::type::Type* type) { | 
|  | return std::make_unique<ast::type::MultisampledTextureType>(dim, type); | 
|  | } | 
|  |  | 
|  | /// Adds a sampled texture variable to the module | 
|  | /// @param name the name of the variable | 
|  | /// @param type the type to use | 
|  | /// @param set the binding group/set to use for the sampled texture | 
|  | /// @param binding the binding number to use for the sampled texture | 
|  | void AddSampledTexture(const std::string& name, | 
|  | ast::type::Type* type, | 
|  | uint32_t set, | 
|  | uint32_t binding) { | 
|  | AddBinding(name, type, ast::StorageClass::kUniformConstant, set, binding); | 
|  | } | 
|  |  | 
|  | /// Adds a multi-sampled texture variable to the module | 
|  | /// @param name the name of the variable | 
|  | /// @param type the type to use | 
|  | /// @param set the binding group/set to use for the multi-sampled texture | 
|  | /// @param binding the binding number to use for the multi-sampled texture | 
|  | void AddMultisampledTexture(const std::string& name, | 
|  | ast::type::Type* type, | 
|  | uint32_t set, | 
|  | uint32_t binding) { | 
|  | AddBinding(name, type, ast::StorageClass::kUniformConstant, set, binding); | 
|  | } | 
|  |  | 
|  | void AddGlobalVariable(const std::string& name, ast::type::Type* type) { | 
|  | mod()->AddGlobalVariable( | 
|  | create<ast::Variable>(name, ast::StorageClass::kUniformConstant, type)); | 
|  | } | 
|  |  | 
|  | /// Adds a depth texture variable to the module | 
|  | /// @param name the name of the variable | 
|  | /// @param type the type to use | 
|  | void AddDepthTexture(const std::string& name, ast::type::Type* type) { | 
|  | mod()->AddGlobalVariable( | 
|  | create<ast::Variable>(name, ast::StorageClass::kUniformConstant, type)); | 
|  | } | 
|  |  | 
|  | /// Generates a function that references a specific sampler variable | 
|  | /// @param func_name name of the function created | 
|  | /// @param texture_name name of the texture to be sampled | 
|  | /// @param sampler_name name of the sampler to use | 
|  | /// @param coords_name name of the coords variable to use | 
|  | /// @returns a function that references all of the values specified | 
|  | ast::Function* MakeSamplerReferenceBodyFunction( | 
|  | const std::string& func_name, | 
|  | const std::string& texture_name, | 
|  | const std::string& sampler_name, | 
|  | const std::string& coords_name, | 
|  | ast::type::Type* base_type) { | 
|  | std::string result_name = "sampler_result"; | 
|  |  | 
|  | auto* body = create<ast::BlockStatement>(); | 
|  |  | 
|  | auto* call_result = create<ast::Variable>( | 
|  | "sampler_result", ast::StorageClass::kFunction, vec_type(base_type, 4)); | 
|  | body->append(create<ast::VariableDeclStatement>(call_result)); | 
|  |  | 
|  | ast::ExpressionList call_params; | 
|  | call_params.push_back(create<ast::IdentifierExpression>(texture_name)); | 
|  | call_params.push_back(create<ast::IdentifierExpression>(sampler_name)); | 
|  | call_params.push_back(create<ast::IdentifierExpression>(coords_name)); | 
|  | auto* call_expr = create<ast::CallExpression>( | 
|  | create<ast::IdentifierExpression>("textureSample"), call_params); | 
|  |  | 
|  | body->append(create<ast::AssignmentStatement>( | 
|  | create<ast::IdentifierExpression>("sampler_result"), call_expr)); | 
|  | body->append(create<ast::ReturnStatement>()); | 
|  |  | 
|  | return create<ast::Function>(func_name, ast::VariableList(), void_type(), | 
|  | body); | 
|  | } | 
|  |  | 
|  | /// Generates a function that references a specific sampler variable | 
|  | /// @param func_name name of the function created | 
|  | /// @param texture_name name of the texture to be sampled | 
|  | /// @param sampler_name name of the sampler to use | 
|  | /// @param coords_name name of the coords variable to use | 
|  | /// @param array_index name of the array index variable to use | 
|  | /// @returns a function that references all of the values specified | 
|  | ast::Function* MakeSamplerReferenceBodyFunction( | 
|  | const std::string& func_name, | 
|  | const std::string& texture_name, | 
|  | const std::string& sampler_name, | 
|  | const std::string& coords_name, | 
|  | const std::string& array_index, | 
|  | ast::type::Type* base_type) { | 
|  | std::string result_name = "sampler_result"; | 
|  |  | 
|  | auto* body = create<ast::BlockStatement>(); | 
|  |  | 
|  | auto* call_result = create<ast::Variable>( | 
|  | "sampler_result", ast::StorageClass::kFunction, vec_type(base_type, 4)); | 
|  | body->append(create<ast::VariableDeclStatement>(call_result)); | 
|  |  | 
|  | ast::ExpressionList call_params; | 
|  | call_params.push_back(create<ast::IdentifierExpression>(texture_name)); | 
|  | call_params.push_back(create<ast::IdentifierExpression>(sampler_name)); | 
|  | call_params.push_back(create<ast::IdentifierExpression>(coords_name)); | 
|  | call_params.push_back(create<ast::IdentifierExpression>(array_index)); | 
|  | auto* call_expr = create<ast::CallExpression>( | 
|  | create<ast::IdentifierExpression>("textureSample"), call_params); | 
|  |  | 
|  | body->append(create<ast::AssignmentStatement>( | 
|  | create<ast::IdentifierExpression>("sampler_result"), call_expr)); | 
|  | body->append(create<ast::ReturnStatement>()); | 
|  |  | 
|  | return create<ast::Function>(func_name, ast::VariableList(), void_type(), | 
|  | body); | 
|  | } | 
|  |  | 
|  | /// Generates a function that references a specific comparison sampler | 
|  | /// variable. | 
|  | /// @param func_name name of the function created | 
|  | /// @param texture_name name of the depth texture to  use | 
|  | /// @param sampler_name name of the sampler to use | 
|  | /// @param coords_name name of the coords variable to use | 
|  | /// @param depth_name name of the depth reference to use | 
|  | /// @returns a function that references all of the values specified | 
|  | ast::Function* MakeComparisonSamplerReferenceBodyFunction( | 
|  | const std::string& func_name, | 
|  | const std::string& texture_name, | 
|  | const std::string& sampler_name, | 
|  | const std::string& coords_name, | 
|  | const std::string& depth_name, | 
|  | ast::type::Type* base_type) { | 
|  | std::string result_name = "sampler_result"; | 
|  |  | 
|  | auto* body = create<ast::BlockStatement>(); | 
|  |  | 
|  | auto* call_result = create<ast::Variable>( | 
|  | "sampler_result", ast::StorageClass::kFunction, base_type); | 
|  | body->append(create<ast::VariableDeclStatement>(call_result)); | 
|  |  | 
|  | ast::ExpressionList call_params; | 
|  | call_params.push_back(create<ast::IdentifierExpression>(texture_name)); | 
|  | call_params.push_back(create<ast::IdentifierExpression>(sampler_name)); | 
|  | call_params.push_back(create<ast::IdentifierExpression>(coords_name)); | 
|  | call_params.push_back(create<ast::IdentifierExpression>(depth_name)); | 
|  | auto* call_expr = create<ast::CallExpression>( | 
|  | create<ast::IdentifierExpression>("textureSampleCompare"), call_params); | 
|  |  | 
|  | body->append(create<ast::AssignmentStatement>( | 
|  | create<ast::IdentifierExpression>("sampler_result"), call_expr)); | 
|  | body->append(create<ast::ReturnStatement>()); | 
|  |  | 
|  | return create<ast::Function>(func_name, ast::VariableList(), void_type(), | 
|  | body); | 
|  | } | 
|  |  | 
|  | /// Gets an appropriate type for the data in a given texture type. | 
|  | /// @param sampled_kind type of in the texture | 
|  | /// @returns a pointer to a type appropriate for the coord param | 
|  | ast::type::Type* GetBaseType(ResourceBinding::SampledKind sampled_kind) { | 
|  | switch (sampled_kind) { | 
|  | case ResourceBinding::SampledKind::kFloat: | 
|  | return f32_type(); | 
|  | case ResourceBinding::SampledKind::kSInt: | 
|  | return i32_type(); | 
|  | case ResourceBinding::SampledKind::kUInt: | 
|  | return u32_type(); | 
|  | default: | 
|  | return nullptr; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Gets an appropriate type for the coords parameter depending the the | 
|  | /// dimensionality of the texture being sampled. | 
|  | /// @param dim dimensionality of the texture being sampled | 
|  | /// @param sampled_kind type of data in the texture | 
|  | /// @returns a pointer to a type appropriate for the coord param | 
|  | ast::type::Type* GetCoordsType(ast::type::TextureDimension dim, | 
|  | ResourceBinding::SampledKind sampled_kind) { | 
|  | ast::type::Type* base_type = GetBaseType(sampled_kind); | 
|  | if (dim == ast::type::TextureDimension::k1d) { | 
|  | return base_type; | 
|  | } else if (dim == ast::type::TextureDimension::k1dArray || | 
|  | dim == ast::type::TextureDimension::k2d) { | 
|  | return vec_type(base_type, 2); | 
|  | } else if (dim == ast::type::TextureDimension::kCubeArray) { | 
|  | return vec_type(base_type, 4); | 
|  | } | 
|  | return vec_type(base_type, 3); | 
|  | } | 
|  |  | 
|  | ast::Module* mod() { return &mod_; } | 
|  | TypeDeterminer* td() { return td_.get(); } | 
|  | Inspector* inspector() { return inspector_.get(); } | 
|  |  | 
|  | ast::type::BoolType* bool_type() { return &bool_type_; } | 
|  | ast::type::F32Type* f32_type() { return &f32_type_; } | 
|  | ast::type::I32Type* i32_type() { return &i32_type_; } | 
|  | ast::type::U32Type* u32_type() { return &u32_type_; } | 
|  | ast::type::ArrayType* u32_array_type(uint32_t count) { | 
|  | if (array_type_memo_.find(count) == array_type_memo_.end()) { | 
|  | array_type_memo_[count] = | 
|  | std::make_unique<ast::type::ArrayType>(u32_type(), count); | 
|  | ast::ArrayDecorationList decos; | 
|  | decos.push_back(create<ast::StrideDecoration>(4, Source{})); | 
|  | array_type_memo_[count]->set_decorations(decos); | 
|  | } | 
|  | return array_type_memo_[count].get(); | 
|  | } | 
|  | ast::type::VectorType* vec_type(ast::type::Type* type, uint32_t count) { | 
|  | if (vector_type_memo_.find(std::tie(type, count)) == | 
|  | vector_type_memo_.end()) { | 
|  | vector_type_memo_[std::tie(type, count)] = | 
|  | std::make_unique<ast::type::VectorType>(u32_type(), count); | 
|  | } | 
|  | return vector_type_memo_[std::tie(type, count)].get(); | 
|  | } | 
|  | ast::type::VoidType* void_type() { return &void_type_; } | 
|  | ast::type::SamplerType* sampler_type() { return &sampler_type_; } | 
|  | ast::type::SamplerType* comparison_sampler_type() { | 
|  | return &comparison_sampler_type_; | 
|  | } | 
|  |  | 
|  | /// Creates a new `ast::Node` owned by the Module. When the Module is | 
|  | /// destructed, the `ast::Node` will also be destructed. | 
|  | /// @param args the arguments to pass to the type constructor | 
|  | /// @returns the node pointer | 
|  | template <typename T, typename... ARGS> | 
|  | T* create(ARGS&&... args) { | 
|  | return mod_.create<T>(std::forward<ARGS>(args)...); | 
|  | } | 
|  |  | 
|  | private: | 
|  | Context ctx_; | 
|  | ast::Module mod_; | 
|  | std::unique_ptr<TypeDeterminer> td_; | 
|  | std::unique_ptr<Inspector> inspector_; | 
|  |  | 
|  | ast::type::BoolType bool_type_; | 
|  | ast::type::F32Type f32_type_; | 
|  | ast::type::I32Type i32_type_; | 
|  | ast::type::U32Type u32_type_; | 
|  | ast::type::VoidType void_type_; | 
|  | ast::type::SamplerType sampler_type_; | 
|  | ast::type::SamplerType comparison_sampler_type_; | 
|  | std::map<uint32_t, std::unique_ptr<ast::type::ArrayType>> array_type_memo_; | 
|  | std::map<std::tuple<ast::type::Type*, uint32_t>, | 
|  | std::unique_ptr<ast::type::VectorType>> | 
|  | vector_type_memo_; | 
|  | }; | 
|  |  | 
|  | class InspectorGetEntryPointTest : public InspectorHelper, | 
|  | public testing::Test {}; | 
|  | class InspectorGetRemappedNameForEntryPointTest : public InspectorHelper, | 
|  | public testing::Test {}; | 
|  | class InspectorGetConstantIDsTest : public InspectorHelper, | 
|  | public testing::Test {}; | 
|  | class InspectorGetUniformBufferResourceBindingsTest : public InspectorHelper, | 
|  | public testing::Test {}; | 
|  | class InspectorGetStorageBufferResourceBindingsTest : public InspectorHelper, | 
|  | public testing::Test {}; | 
|  | class InspectorGetReadOnlyStorageBufferResourceBindingsTest | 
|  | : public InspectorHelper, | 
|  | public testing::Test {}; | 
|  | class InspectorGetSamplerResourceBindingsTest : public InspectorHelper, | 
|  | public testing::Test {}; | 
|  | class InspectorGetComparisonSamplerResourceBindingsTest | 
|  | : public InspectorHelper, | 
|  | public testing::Test {}; | 
|  | class InspectorGetSampledTextureResourceBindingsTest : public InspectorHelper, | 
|  | public testing::Test {}; | 
|  | class InspectorGetSampledArrayTextureResourceBindingsTest | 
|  | : public InspectorHelper, | 
|  | public testing::Test {}; | 
|  | struct GetSampledTextureTestParams { | 
|  | ast::type::TextureDimension type_dim; | 
|  | inspector::ResourceBinding::TextureDimension inspector_dim; | 
|  | inspector::ResourceBinding::SampledKind sampled_kind; | 
|  | }; | 
|  | class InspectorGetSampledTextureResourceBindingsTestWithParam | 
|  | : public InspectorHelper, | 
|  | public testing::TestWithParam<GetSampledTextureTestParams> {}; | 
|  | class InspectorGetSampledArrayTextureResourceBindingsTestWithParam | 
|  | : public InspectorHelper, | 
|  | public testing::TestWithParam<GetSampledTextureTestParams> {}; | 
|  | class InspectorGetMultisampledTextureResourceBindingsTest | 
|  | : public InspectorHelper, | 
|  | public testing::Test {}; | 
|  | class InspectorGetMultisampledArrayTextureResourceBindingsTest | 
|  | : public InspectorHelper, | 
|  | public testing::Test {}; | 
|  | typedef GetSampledTextureTestParams GetMultisampledTextureTestParams; | 
|  | class InspectorGetMultisampledArrayTextureResourceBindingsTestWithParam | 
|  | : public InspectorHelper, | 
|  | public testing::TestWithParam<GetMultisampledTextureTestParams> {}; | 
|  | class InspectorGetMultisampledTextureResourceBindingsTestWithParam | 
|  | : public InspectorHelper, | 
|  | public testing::TestWithParam<GetMultisampledTextureTestParams> {}; | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, NoFunctions) { | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | EXPECT_EQ(0u, result.size()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, NoEntryPoints) { | 
|  | mod()->AddFunction(MakeEmptyBodyFunction("foo")); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | EXPECT_EQ(0u, result.size()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, OneEntryPoint) { | 
|  | auto* foo = MakeEmptyBodyFunction("foo"); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  | EXPECT_EQ("foo", result[0].name); | 
|  | EXPECT_EQ("tint_666f6f", result[0].remapped_name); | 
|  | EXPECT_EQ(ast::PipelineStage::kVertex, result[0].stage); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, MultipleEntryPoints) { | 
|  | auto* foo = MakeEmptyBodyFunction("foo"); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | auto* bar = MakeEmptyBodyFunction("bar"); | 
|  | bar->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kCompute, Source{})); | 
|  | mod()->AddFunction(bar); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(2u, result.size()); | 
|  | EXPECT_EQ("foo", result[0].name); | 
|  | EXPECT_EQ("tint_666f6f", result[0].remapped_name); | 
|  | EXPECT_EQ(ast::PipelineStage::kVertex, result[0].stage); | 
|  | EXPECT_EQ("bar", result[1].name); | 
|  | EXPECT_EQ("tint_626172", result[1].remapped_name); | 
|  | EXPECT_EQ(ast::PipelineStage::kCompute, result[1].stage); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, MixFunctionsAndEntryPoints) { | 
|  | auto* func = MakeEmptyBodyFunction("func"); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | auto* foo = MakeCallerBodyFunction("foo", "func"); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | auto* bar = MakeCallerBodyFunction("bar", "func"); | 
|  | bar->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kFragment, Source{})); | 
|  | mod()->AddFunction(bar); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | EXPECT_FALSE(inspector()->has_error()); | 
|  |  | 
|  | ASSERT_EQ(2u, result.size()); | 
|  | EXPECT_EQ("foo", result[0].name); | 
|  | EXPECT_EQ("tint_666f6f", result[0].remapped_name); | 
|  | EXPECT_EQ(ast::PipelineStage::kVertex, result[0].stage); | 
|  | EXPECT_EQ("bar", result[1].name); | 
|  | EXPECT_EQ("tint_626172", result[1].remapped_name); | 
|  | EXPECT_EQ(ast::PipelineStage::kFragment, result[1].stage); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, DefaultWorkgroupSize) { | 
|  | auto* foo = MakeCallerBodyFunction("foo", "func"); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  | uint32_t x, y, z; | 
|  | std::tie(x, y, z) = result[0].workgroup_size(); | 
|  | EXPECT_EQ(1u, x); | 
|  | EXPECT_EQ(1u, y); | 
|  | EXPECT_EQ(1u, z); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, NonDefaultWorkgroupSize) { | 
|  | auto* foo = MakeEmptyBodyFunction("foo"); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kCompute, Source{})); | 
|  | foo->add_decoration(create<ast::WorkgroupDecoration>(8u, 2u, 1u, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  | uint32_t x, y, z; | 
|  | std::tie(x, y, z) = result[0].workgroup_size(); | 
|  | EXPECT_EQ(8u, x); | 
|  | EXPECT_EQ(2u, y); | 
|  | EXPECT_EQ(1u, z); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, NoInOutVariables) { | 
|  | auto* func = MakeEmptyBodyFunction("func"); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | auto* foo = MakeCallerBodyFunction("foo", "func"); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  | EXPECT_EQ(0u, result[0].input_variables.size()); | 
|  | EXPECT_EQ(0u, result[0].output_variables.size()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, EntryPointInOutVariables) { | 
|  | AddInOutVariables({{"in_var", "out_var"}}); | 
|  |  | 
|  | auto* foo = MakeInOutVariableBodyFunction("foo", {{"in_var", "out_var"}}); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | ASSERT_EQ(1u, result[0].input_variables.size()); | 
|  | EXPECT_EQ("in_var", result[0].input_variables[0]); | 
|  | ASSERT_EQ(1u, result[0].output_variables.size()); | 
|  | EXPECT_EQ("out_var", result[0].output_variables[0]); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, FunctionInOutVariables) { | 
|  | AddInOutVariables({{"in_var", "out_var"}}); | 
|  |  | 
|  | auto* func = MakeInOutVariableBodyFunction("func", {{"in_var", "out_var"}}); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | auto* foo = MakeCallerBodyFunction("foo", "func"); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | ASSERT_EQ(1u, result[0].input_variables.size()); | 
|  | EXPECT_EQ("in_var", result[0].input_variables[0]); | 
|  | ASSERT_EQ(1u, result[0].output_variables.size()); | 
|  | EXPECT_EQ("out_var", result[0].output_variables[0]); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, RepeatedInOutVariables) { | 
|  | AddInOutVariables({{"in_var", "out_var"}}); | 
|  |  | 
|  | auto* func = MakeInOutVariableBodyFunction("func", {{"in_var", "out_var"}}); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | auto* foo = MakeInOutVariableCallerBodyFunction("foo", "func", | 
|  | {{"in_var", "out_var"}}); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | ASSERT_EQ(1u, result[0].input_variables.size()); | 
|  | EXPECT_EQ("in_var", result[0].input_variables[0]); | 
|  | ASSERT_EQ(1u, result[0].output_variables.size()); | 
|  | EXPECT_EQ("out_var", result[0].output_variables[0]); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, EntryPointMultipleInOutVariables) { | 
|  | AddInOutVariables({{"in_var", "out_var"}, {"in2_var", "out2_var"}}); | 
|  |  | 
|  | auto* foo = MakeInOutVariableBodyFunction( | 
|  | "foo", {{"in_var", "out_var"}, {"in2_var", "out2_var"}}); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | ASSERT_EQ(2u, result[0].input_variables.size()); | 
|  | EXPECT_TRUE(ContainsString(result[0].input_variables, "in_var")); | 
|  | EXPECT_TRUE(ContainsString(result[0].input_variables, "in2_var")); | 
|  | ASSERT_EQ(2u, result[0].output_variables.size()); | 
|  | EXPECT_TRUE(ContainsString(result[0].output_variables, "out_var")); | 
|  | EXPECT_TRUE(ContainsString(result[0].output_variables, "out2_var")); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, FunctionMultipleInOutVariables) { | 
|  | AddInOutVariables({{"in_var", "out_var"}, {"in2_var", "out2_var"}}); | 
|  |  | 
|  | auto* func = MakeInOutVariableBodyFunction( | 
|  | "func", {{"in_var", "out_var"}, {"in2_var", "out2_var"}}); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | auto* foo = MakeCallerBodyFunction("foo", "func"); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | ASSERT_EQ(2u, result[0].input_variables.size()); | 
|  | EXPECT_TRUE(ContainsString(result[0].input_variables, "in_var")); | 
|  | EXPECT_TRUE(ContainsString(result[0].input_variables, "in2_var")); | 
|  | ASSERT_EQ(2u, result[0].output_variables.size()); | 
|  | EXPECT_TRUE(ContainsString(result[0].output_variables, "out_var")); | 
|  | EXPECT_TRUE(ContainsString(result[0].output_variables, "out2_var")); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutVariables) { | 
|  | AddInOutVariables({{"in_var", "out_var"}, {"in2_var", "out2_var"}}); | 
|  |  | 
|  | auto* foo = MakeInOutVariableBodyFunction("foo", {{"in_var", "out2_var"}}); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | auto* bar = MakeInOutVariableBodyFunction("bar", {{"in2_var", "out_var"}}); | 
|  | bar->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kCompute, Source{})); | 
|  | mod()->AddFunction(bar); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(2u, result.size()); | 
|  |  | 
|  | ASSERT_EQ("foo", result[0].name); | 
|  | ASSERT_EQ("tint_666f6f", result[0].remapped_name); | 
|  | ASSERT_EQ(1u, result[0].input_variables.size()); | 
|  | EXPECT_EQ("in_var", result[0].input_variables[0]); | 
|  | ASSERT_EQ(1u, result[0].output_variables.size()); | 
|  | EXPECT_EQ("out2_var", result[0].output_variables[0]); | 
|  |  | 
|  | ASSERT_EQ("bar", result[1].name); | 
|  | ASSERT_EQ("tint_626172", result[1].remapped_name); | 
|  | ASSERT_EQ(1u, result[1].input_variables.size()); | 
|  | EXPECT_EQ("in2_var", result[1].input_variables[0]); | 
|  | ASSERT_EQ(1u, result[1].output_variables.size()); | 
|  | EXPECT_EQ("out_var", result[1].output_variables[0]); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsSharedInOutVariables) { | 
|  | AddInOutVariables({{"in_var", "out_var"}, {"in2_var", "out2_var"}}); | 
|  |  | 
|  | auto* func = MakeInOutVariableBodyFunction("func", {{"in2_var", "out2_var"}}); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | auto* foo = MakeInOutVariableCallerBodyFunction("foo", "func", | 
|  | {{"in_var", "out_var"}}); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | auto* bar = MakeCallerBodyFunction("bar", "func"); | 
|  | bar->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kCompute, Source{})); | 
|  | mod()->AddFunction(bar); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetEntryPoints(); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(2u, result.size()); | 
|  |  | 
|  | ASSERT_EQ("foo", result[0].name); | 
|  | ASSERT_EQ("tint_666f6f", result[0].remapped_name); | 
|  | EXPECT_EQ(2u, result[0].input_variables.size()); | 
|  | EXPECT_TRUE(ContainsString(result[0].input_variables, "in_var")); | 
|  | EXPECT_TRUE(ContainsString(result[0].input_variables, "in2_var")); | 
|  | EXPECT_EQ(2u, result[0].output_variables.size()); | 
|  | EXPECT_TRUE(ContainsString(result[0].output_variables, "out_var")); | 
|  | EXPECT_TRUE(ContainsString(result[0].output_variables, "out2_var")); | 
|  |  | 
|  | ASSERT_EQ("bar", result[1].name); | 
|  | ASSERT_EQ("tint_626172", result[1].remapped_name); | 
|  | EXPECT_EQ(1u, result[1].input_variables.size()); | 
|  | EXPECT_EQ("in2_var", result[1].input_variables[0]); | 
|  | EXPECT_EQ(1u, result[1].output_variables.size()); | 
|  | EXPECT_EQ("out2_var", result[1].output_variables[0]); | 
|  | } | 
|  |  | 
|  | // TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass | 
|  | // through | 
|  | TEST_F(InspectorGetRemappedNameForEntryPointTest, DISABLED_NoFunctions) { | 
|  | auto result = inspector()->GetRemappedNameForEntryPoint("foo"); | 
|  | ASSERT_TRUE(inspector()->has_error()); | 
|  |  | 
|  | EXPECT_EQ("", result); | 
|  | } | 
|  |  | 
|  | // TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass | 
|  | // through | 
|  | TEST_F(InspectorGetRemappedNameForEntryPointTest, DISABLED_NoEntryPoints) { | 
|  | mod()->AddFunction(MakeEmptyBodyFunction("foo")); | 
|  |  | 
|  | auto result = inspector()->GetRemappedNameForEntryPoint("foo"); | 
|  | ASSERT_TRUE(inspector()->has_error()); | 
|  |  | 
|  | EXPECT_EQ("", result); | 
|  | } | 
|  |  | 
|  | // TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass | 
|  | // through | 
|  | TEST_F(InspectorGetRemappedNameForEntryPointTest, DISABLED_OneEntryPoint) { | 
|  | auto* foo = MakeEmptyBodyFunction("foo"); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | auto result = inspector()->GetRemappedNameForEntryPoint("foo"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | EXPECT_EQ("tint_666f6f", result); | 
|  | } | 
|  |  | 
|  | // TODO(rharrison): Reenable once GetRemappedNameForEntryPoint isn't a pass | 
|  | // through | 
|  | TEST_F(InspectorGetRemappedNameForEntryPointTest, | 
|  | DISABLED_MultipleEntryPoints) { | 
|  | auto* foo = MakeEmptyBodyFunction("foo"); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | auto* bar = MakeEmptyBodyFunction("bar"); | 
|  | bar->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kCompute, Source{})); | 
|  | mod()->AddFunction(bar); | 
|  |  | 
|  | { | 
|  | auto result = inspector()->GetRemappedNameForEntryPoint("foo"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | EXPECT_EQ("tint_666f6f", result); | 
|  | } | 
|  | { | 
|  | auto result = inspector()->GetRemappedNameForEntryPoint("bar"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | EXPECT_EQ("tint_626172", result); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetConstantIDsTest, Bool) { | 
|  | bool val_true = true; | 
|  | bool val_false = false; | 
|  | AddConstantID<bool>("foo", 1, bool_type(), nullptr); | 
|  | AddConstantID<bool>("bar", 20, bool_type(), &val_true); | 
|  | AddConstantID<bool>("baz", 300, bool_type(), &val_false); | 
|  |  | 
|  | auto result = inspector()->GetConstantIDs(); | 
|  | ASSERT_EQ(3u, result.size()); | 
|  |  | 
|  | ASSERT_TRUE(result.find(1) != result.end()); | 
|  | EXPECT_TRUE(result[1].IsNull()); | 
|  |  | 
|  | ASSERT_TRUE(result.find(20) != result.end()); | 
|  | EXPECT_TRUE(result[20].IsBool()); | 
|  | EXPECT_TRUE(result[20].AsBool()); | 
|  |  | 
|  | ASSERT_TRUE(result.find(300) != result.end()); | 
|  | EXPECT_TRUE(result[300].IsBool()); | 
|  | EXPECT_FALSE(result[300].AsBool()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetConstantIDsTest, U32) { | 
|  | uint32_t val = 42; | 
|  | AddConstantID<uint32_t>("foo", 1, u32_type(), nullptr); | 
|  | AddConstantID<uint32_t>("bar", 20, u32_type(), &val); | 
|  |  | 
|  | auto result = inspector()->GetConstantIDs(); | 
|  | ASSERT_EQ(2u, result.size()); | 
|  |  | 
|  | ASSERT_TRUE(result.find(1) != result.end()); | 
|  | EXPECT_TRUE(result[1].IsNull()); | 
|  |  | 
|  | ASSERT_TRUE(result.find(20) != result.end()); | 
|  | EXPECT_TRUE(result[20].IsU32()); | 
|  | EXPECT_EQ(42u, result[20].AsU32()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetConstantIDsTest, I32) { | 
|  | int32_t val_neg = -42; | 
|  | int32_t val_pos = 42; | 
|  | AddConstantID<int32_t>("foo", 1, i32_type(), nullptr); | 
|  | AddConstantID<int32_t>("bar", 20, i32_type(), &val_neg); | 
|  | AddConstantID<int32_t>("baz", 300, i32_type(), &val_pos); | 
|  |  | 
|  | auto result = inspector()->GetConstantIDs(); | 
|  | ASSERT_EQ(3u, result.size()); | 
|  |  | 
|  | ASSERT_TRUE(result.find(1) != result.end()); | 
|  | EXPECT_TRUE(result[1].IsNull()); | 
|  |  | 
|  | ASSERT_TRUE(result.find(20) != result.end()); | 
|  | EXPECT_TRUE(result[20].IsI32()); | 
|  | EXPECT_EQ(-42, result[20].AsI32()); | 
|  |  | 
|  | ASSERT_TRUE(result.find(300) != result.end()); | 
|  | EXPECT_TRUE(result[300].IsI32()); | 
|  | EXPECT_EQ(42, result[300].AsI32()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetConstantIDsTest, Float) { | 
|  | float val_zero = 0.0f; | 
|  | float val_neg = -10.0f; | 
|  | float val_pos = 15.0f; | 
|  | AddConstantID<float>("foo", 1, f32_type(), nullptr); | 
|  | AddConstantID<float>("bar", 20, f32_type(), &val_zero); | 
|  | AddConstantID<float>("baz", 300, f32_type(), &val_neg); | 
|  | AddConstantID<float>("x", 4000, f32_type(), &val_pos); | 
|  |  | 
|  | auto result = inspector()->GetConstantIDs(); | 
|  | ASSERT_EQ(4u, result.size()); | 
|  |  | 
|  | ASSERT_TRUE(result.find(1) != result.end()); | 
|  | EXPECT_TRUE(result[1].IsNull()); | 
|  |  | 
|  | ASSERT_TRUE(result.find(20) != result.end()); | 
|  | EXPECT_TRUE(result[20].IsFloat()); | 
|  | EXPECT_FLOAT_EQ(0.0, result[20].AsFloat()); | 
|  |  | 
|  | ASSERT_TRUE(result.find(300) != result.end()); | 
|  | EXPECT_TRUE(result[300].IsFloat()); | 
|  | EXPECT_FLOAT_EQ(-10.0, result[300].AsFloat()); | 
|  |  | 
|  | ASSERT_TRUE(result.find(4000) != result.end()); | 
|  | EXPECT_TRUE(result[4000].IsFloat()); | 
|  | EXPECT_FLOAT_EQ(15.0, result[4000].AsFloat()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetUniformBufferResourceBindingsTest, MissingEntryPoint) { | 
|  | auto result = inspector()->GetUniformBufferResourceBindings("ep_func"); | 
|  | ASSERT_TRUE(inspector()->has_error()); | 
|  | std::string error = inspector()->error(); | 
|  | EXPECT_TRUE(error.find("not found") != std::string::npos); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetUniformBufferResourceBindingsTest, NonEntryPointFunc) { | 
|  | std::unique_ptr<ast::type::StructType> foo_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> foo_control_type; | 
|  | std::tie(foo_struct_type, foo_control_type) = | 
|  | MakeUniformBufferTypes("foo_type", {{i32_type(), 0}}); | 
|  | AddUniformBuffer("foo_ub", foo_control_type.get(), 0, 0); | 
|  |  | 
|  | auto* ub_func = MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", | 
|  | {{0, i32_type()}}); | 
|  | mod()->AddFunction(ub_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "ub_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetUniformBufferResourceBindings("ub_func"); | 
|  | std::string error = inspector()->error(); | 
|  | EXPECT_TRUE(error.find("not an entry point") != std::string::npos); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetUniformBufferResourceBindingsTest, MissingBlockDeco) { | 
|  | ast::StructMemberList members; | 
|  | ast::StructMemberDecorationList deco; | 
|  | deco.push_back(create<ast::StructMemberOffsetDecoration>(0, Source{})); | 
|  |  | 
|  | members.push_back(create<ast::StructMember>( | 
|  | StructMemberName(members.size(), i32_type()), i32_type(), deco)); | 
|  |  | 
|  | ast::StructDecorationList decos; | 
|  |  | 
|  | auto* str = create<ast::Struct>(decos, members); | 
|  | auto foo_type = std::make_unique<ast::type::StructType>("foo_type", str); | 
|  |  | 
|  | AddUniformBuffer("foo_ub", foo_type.get(), 0, 0); | 
|  |  | 
|  | auto* ub_func = MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", | 
|  | {{0, i32_type()}}); | 
|  | mod()->AddFunction(ub_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "ub_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetUniformBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | EXPECT_EQ(0u, result.size()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetUniformBufferResourceBindingsTest, Simple) { | 
|  | std::unique_ptr<ast::type::StructType> foo_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> foo_control_type; | 
|  | std::tie(foo_struct_type, foo_control_type) = | 
|  | MakeUniformBufferTypes("foo_type", {{i32_type(), 0}}); | 
|  | AddUniformBuffer("foo_ub", foo_control_type.get(), 0, 0); | 
|  |  | 
|  | auto* ub_func = MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", | 
|  | {{0, i32_type()}}); | 
|  | mod()->AddFunction(ub_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "ub_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetUniformBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(16u, result[0].min_buffer_binding_size); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleMembers) { | 
|  | std::unique_ptr<ast::type::StructType> foo_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> foo_control_type; | 
|  | std::tie(foo_struct_type, foo_control_type) = MakeUniformBufferTypes( | 
|  | "foo_type", {{i32_type(), 0}, {u32_type(), 4}, {f32_type(), 8}}); | 
|  | AddUniformBuffer("foo_ub", foo_control_type.get(), 0, 0); | 
|  |  | 
|  | auto* ub_func = MakeStructVariableReferenceBodyFunction( | 
|  | "ub_func", "foo_ub", {{0, i32_type()}, {1, u32_type()}, {2, f32_type()}}); | 
|  | mod()->AddFunction(ub_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "ub_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetUniformBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(16u, result[0].min_buffer_binding_size); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetUniformBufferResourceBindingsTest, MultipleUniformBuffers) { | 
|  | std::unique_ptr<ast::type::StructType> ub_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> ub_control_type; | 
|  | std::tie(ub_struct_type, ub_control_type) = MakeUniformBufferTypes( | 
|  | "ub_type", {{i32_type(), 0}, {u32_type(), 4}, {f32_type(), 8}}); | 
|  | AddUniformBuffer("ub_foo", ub_control_type.get(), 0, 0); | 
|  | AddUniformBuffer("ub_bar", ub_control_type.get(), 0, 1); | 
|  | AddUniformBuffer("ub_baz", ub_control_type.get(), 2, 0); | 
|  |  | 
|  | auto AddReferenceFunc = [this](const std::string& func_name, | 
|  | const std::string& var_name) { | 
|  | auto* ub_func = MakeStructVariableReferenceBodyFunction( | 
|  | func_name, var_name, | 
|  | {{0, i32_type()}, {1, u32_type()}, {2, f32_type()}}); | 
|  | mod()->AddFunction(ub_func); | 
|  | }; | 
|  | AddReferenceFunc("ub_foo_func", "ub_foo"); | 
|  | AddReferenceFunc("ub_bar_func", "ub_bar"); | 
|  | AddReferenceFunc("ub_baz_func", "ub_baz"); | 
|  |  | 
|  | auto AddFuncCall = [&](ast::BlockStatement* body, const std::string& callee) { | 
|  | auto* ident_expr = create<ast::IdentifierExpression>(callee); | 
|  | auto* call_expr = | 
|  | create<ast::CallExpression>(ident_expr, ast::ExpressionList()); | 
|  | body->append(create<ast::CallStatement>(call_expr)); | 
|  | }; | 
|  | auto* body = create<ast::BlockStatement>(); | 
|  |  | 
|  | AddFuncCall(body, "ub_foo_func"); | 
|  | AddFuncCall(body, "ub_bar_func"); | 
|  | AddFuncCall(body, "ub_baz_func"); | 
|  |  | 
|  | body->append(create<ast::ReturnStatement>()); | 
|  | ast::Function* func = | 
|  | create<ast::Function>("ep_func", ast::VariableList(), void_type(), body); | 
|  |  | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetUniformBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(3u, result.size()); | 
|  |  | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(16u, result[0].min_buffer_binding_size); | 
|  |  | 
|  | EXPECT_EQ(0u, result[1].bind_group); | 
|  | EXPECT_EQ(1u, result[1].binding); | 
|  | EXPECT_EQ(16u, result[1].min_buffer_binding_size); | 
|  |  | 
|  | EXPECT_EQ(2u, result[2].bind_group); | 
|  | EXPECT_EQ(0u, result[2].binding); | 
|  | EXPECT_EQ(16u, result[2].min_buffer_binding_size); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetUniformBufferResourceBindingsTest, ContainingArray) { | 
|  | std::unique_ptr<ast::type::StructType> foo_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> foo_control_type; | 
|  | std::tie(foo_struct_type, foo_control_type) = MakeUniformBufferTypes( | 
|  | "foo_type", {{i32_type(), 0}, {u32_array_type(4), 4}}); | 
|  | AddUniformBuffer("foo_ub", foo_control_type.get(), 0, 0); | 
|  |  | 
|  | auto* ub_func = MakeStructVariableReferenceBodyFunction("ub_func", "foo_ub", | 
|  | {{0, i32_type()}}); | 
|  | mod()->AddFunction(ub_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "ub_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetUniformBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(32u, result[0].min_buffer_binding_size); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetStorageBufferResourceBindingsTest, Simple) { | 
|  | std::unique_ptr<ast::type::StructType> foo_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> foo_control_type; | 
|  | std::tie(foo_struct_type, foo_control_type) = | 
|  | MakeStorageBufferTypes("foo_type", {{i32_type(), 0}}); | 
|  | AddStorageBuffer("foo_sb", foo_control_type.get(), 0, 0); | 
|  |  | 
|  | auto* sb_func = MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", | 
|  | {{0, i32_type()}}); | 
|  | mod()->AddFunction(sb_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "sb_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetStorageBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(4u, result[0].min_buffer_binding_size); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleMembers) { | 
|  | std::unique_ptr<ast::type::StructType> foo_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> foo_control_type; | 
|  | std::tie(foo_struct_type, foo_control_type) = MakeStorageBufferTypes( | 
|  | "foo_type", {{i32_type(), 0}, {u32_type(), 4}, {f32_type(), 8}}); | 
|  | AddStorageBuffer("foo_sb", foo_control_type.get(), 0, 0); | 
|  |  | 
|  | auto* sb_func = MakeStructVariableReferenceBodyFunction( | 
|  | "sb_func", "foo_sb", {{0, i32_type()}, {1, u32_type()}, {2, f32_type()}}); | 
|  | mod()->AddFunction(sb_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "sb_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetStorageBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(12u, result[0].min_buffer_binding_size); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetStorageBufferResourceBindingsTest, MultipleStorageBuffers) { | 
|  | std::unique_ptr<ast::type::StructType> sb_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> sb_control_type; | 
|  | std::tie(sb_struct_type, sb_control_type) = MakeStorageBufferTypes( | 
|  | "sb_type", {{i32_type(), 0}, {u32_type(), 4}, {f32_type(), 8}}); | 
|  | AddStorageBuffer("sb_foo", sb_control_type.get(), 0, 0); | 
|  | AddStorageBuffer("sb_bar", sb_control_type.get(), 0, 1); | 
|  | AddStorageBuffer("sb_baz", sb_control_type.get(), 2, 0); | 
|  |  | 
|  | auto AddReferenceFunc = [this](const std::string& func_name, | 
|  | const std::string& var_name) { | 
|  | auto* sb_func = MakeStructVariableReferenceBodyFunction( | 
|  | func_name, var_name, | 
|  | {{0, i32_type()}, {1, u32_type()}, {2, f32_type()}}); | 
|  | mod()->AddFunction(sb_func); | 
|  | }; | 
|  | AddReferenceFunc("sb_foo_func", "sb_foo"); | 
|  | AddReferenceFunc("sb_bar_func", "sb_bar"); | 
|  | AddReferenceFunc("sb_baz_func", "sb_baz"); | 
|  |  | 
|  | auto AddFuncCall = [&](ast::BlockStatement* body, const std::string& callee) { | 
|  | auto* ident_expr = create<ast::IdentifierExpression>(callee); | 
|  | auto* call_expr = | 
|  | create<ast::CallExpression>(ident_expr, ast::ExpressionList()); | 
|  | body->append(create<ast::CallStatement>(call_expr)); | 
|  | }; | 
|  | auto* body = create<ast::BlockStatement>(); | 
|  |  | 
|  | AddFuncCall(body, "sb_foo_func"); | 
|  | AddFuncCall(body, "sb_bar_func"); | 
|  | AddFuncCall(body, "sb_baz_func"); | 
|  |  | 
|  | body->append(create<ast::ReturnStatement>()); | 
|  | ast::Function* func = | 
|  | create<ast::Function>("ep_func", ast::VariableList(), void_type(), body); | 
|  |  | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetStorageBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(3u, result.size()); | 
|  |  | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(12u, result[0].min_buffer_binding_size); | 
|  |  | 
|  | EXPECT_EQ(0u, result[1].bind_group); | 
|  | EXPECT_EQ(1u, result[1].binding); | 
|  | EXPECT_EQ(12u, result[1].min_buffer_binding_size); | 
|  |  | 
|  | EXPECT_EQ(2u, result[2].bind_group); | 
|  | EXPECT_EQ(0u, result[2].binding); | 
|  | EXPECT_EQ(12u, result[2].min_buffer_binding_size); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingArray) { | 
|  | std::unique_ptr<ast::type::StructType> foo_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> foo_control_type; | 
|  | std::tie(foo_struct_type, foo_control_type) = MakeStorageBufferTypes( | 
|  | "foo_type", {{i32_type(), 0}, {u32_array_type(4), 4}}); | 
|  | AddStorageBuffer("foo_sb", foo_control_type.get(), 0, 0); | 
|  |  | 
|  | auto* sb_func = MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", | 
|  | {{0, i32_type()}}); | 
|  | mod()->AddFunction(sb_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "sb_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetStorageBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(20u, result[0].min_buffer_binding_size); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetStorageBufferResourceBindingsTest, ContainingRuntimeArray) { | 
|  | std::unique_ptr<ast::type::StructType> foo_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> foo_control_type; | 
|  | std::tie(foo_struct_type, foo_control_type) = MakeStorageBufferTypes( | 
|  | "foo_type", {{i32_type(), 0}, {u32_array_type(0), 4}}); | 
|  | AddStorageBuffer("foo_sb", foo_control_type.get(), 0, 0); | 
|  |  | 
|  | auto* sb_func = MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", | 
|  | {{0, i32_type()}}); | 
|  | mod()->AddFunction(sb_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "sb_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetStorageBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(8u, result[0].min_buffer_binding_size); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetStorageBufferResourceBindingsTest, SkipReadOnly) { | 
|  | std::unique_ptr<ast::type::StructType> foo_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> foo_control_type; | 
|  | std::tie(foo_struct_type, foo_control_type) = | 
|  | MakeReadOnlyStorageBufferTypes("foo_type", {{i32_type(), 0}}); | 
|  | AddStorageBuffer("foo_sb", foo_control_type.get(), 0, 0); | 
|  |  | 
|  | auto* sb_func = MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", | 
|  | {{0, i32_type()}}); | 
|  | mod()->AddFunction(sb_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "sb_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetStorageBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(0u, result.size()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, Simple) { | 
|  | std::unique_ptr<ast::type::StructType> foo_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> foo_control_type; | 
|  | std::tie(foo_struct_type, foo_control_type) = | 
|  | MakeReadOnlyStorageBufferTypes("foo_type", {{i32_type(), 0}}); | 
|  | AddStorageBuffer("foo_sb", foo_control_type.get(), 0, 0); | 
|  |  | 
|  | auto* sb_func = MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", | 
|  | {{0, i32_type()}}); | 
|  | mod()->AddFunction(sb_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "sb_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = | 
|  | inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(4u, result[0].min_buffer_binding_size); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, | 
|  | MultipleStorageBuffers) { | 
|  | std::unique_ptr<ast::type::StructType> sb_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> sb_control_type; | 
|  | std::tie(sb_struct_type, sb_control_type) = MakeReadOnlyStorageBufferTypes( | 
|  | "sb_type", {{i32_type(), 0}, {u32_type(), 4}, {f32_type(), 8}}); | 
|  | AddStorageBuffer("sb_foo", sb_control_type.get(), 0, 0); | 
|  | AddStorageBuffer("sb_bar", sb_control_type.get(), 0, 1); | 
|  | AddStorageBuffer("sb_baz", sb_control_type.get(), 2, 0); | 
|  |  | 
|  | auto AddReferenceFunc = [this](const std::string& func_name, | 
|  | const std::string& var_name) { | 
|  | auto* sb_func = MakeStructVariableReferenceBodyFunction( | 
|  | func_name, var_name, | 
|  | {{0, i32_type()}, {1, u32_type()}, {2, f32_type()}}); | 
|  | mod()->AddFunction(sb_func); | 
|  | }; | 
|  | AddReferenceFunc("sb_foo_func", "sb_foo"); | 
|  | AddReferenceFunc("sb_bar_func", "sb_bar"); | 
|  | AddReferenceFunc("sb_baz_func", "sb_baz"); | 
|  |  | 
|  | auto AddFuncCall = [&](ast::BlockStatement* body, const std::string& callee) { | 
|  | auto* ident_expr = create<ast::IdentifierExpression>(callee); | 
|  | auto* call_expr = | 
|  | create<ast::CallExpression>(ident_expr, ast::ExpressionList()); | 
|  | body->append(create<ast::CallStatement>(call_expr)); | 
|  | }; | 
|  | auto* body = create<ast::BlockStatement>(); | 
|  |  | 
|  | AddFuncCall(body, "sb_foo_func"); | 
|  | AddFuncCall(body, "sb_bar_func"); | 
|  | AddFuncCall(body, "sb_baz_func"); | 
|  |  | 
|  | body->append(create<ast::ReturnStatement>()); | 
|  | ast::Function* func = | 
|  | create<ast::Function>("ep_func", ast::VariableList(), void_type(), body); | 
|  |  | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = | 
|  | inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(3u, result.size()); | 
|  |  | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(12u, result[0].min_buffer_binding_size); | 
|  |  | 
|  | EXPECT_EQ(0u, result[1].bind_group); | 
|  | EXPECT_EQ(1u, result[1].binding); | 
|  | EXPECT_EQ(12u, result[1].min_buffer_binding_size); | 
|  |  | 
|  | EXPECT_EQ(2u, result[2].bind_group); | 
|  | EXPECT_EQ(0u, result[2].binding); | 
|  | EXPECT_EQ(12u, result[2].min_buffer_binding_size); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, ContainingArray) { | 
|  | std::unique_ptr<ast::type::StructType> foo_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> foo_control_type; | 
|  | std::tie(foo_struct_type, foo_control_type) = MakeReadOnlyStorageBufferTypes( | 
|  | "foo_type", {{i32_type(), 0}, {u32_array_type(4), 4}}); | 
|  | AddStorageBuffer("foo_sb", foo_control_type.get(), 0, 0); | 
|  |  | 
|  | auto* sb_func = MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", | 
|  | {{0, i32_type()}}); | 
|  | mod()->AddFunction(sb_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "sb_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = | 
|  | inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(20u, result[0].min_buffer_binding_size); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, | 
|  | ContainingRuntimeArray) { | 
|  | std::unique_ptr<ast::type::StructType> foo_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> foo_control_type; | 
|  | std::tie(foo_struct_type, foo_control_type) = MakeReadOnlyStorageBufferTypes( | 
|  | "foo_type", {{i32_type(), 0}, {u32_array_type(0), 4}}); | 
|  | AddStorageBuffer("foo_sb", foo_control_type.get(), 0, 0); | 
|  |  | 
|  | auto* sb_func = MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", | 
|  | {{0, i32_type()}}); | 
|  | mod()->AddFunction(sb_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "sb_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = | 
|  | inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(1u, result.size()); | 
|  |  | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(8u, result[0].min_buffer_binding_size); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetReadOnlyStorageBufferResourceBindingsTest, SkipNonReadOnly) { | 
|  | std::unique_ptr<ast::type::StructType> foo_struct_type; | 
|  | std::unique_ptr<ast::type::AccessControlType> foo_control_type; | 
|  | std::tie(foo_struct_type, foo_control_type) = | 
|  | MakeStorageBufferTypes("foo_type", {{i32_type(), 0}}); | 
|  | AddStorageBuffer("foo_sb", foo_control_type.get(), 0, 0); | 
|  |  | 
|  | auto* sb_func = MakeStructVariableReferenceBodyFunction("sb_func", "foo_sb", | 
|  | {{0, i32_type()}}); | 
|  | mod()->AddFunction(sb_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "sb_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = | 
|  | inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  | ASSERT_EQ(0u, result.size()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetSamplerResourceBindingsTest, Simple) { | 
|  | auto sampled_texture_type = | 
|  | MakeSampledTextureType(ast::type::TextureDimension::k1d, f32_type()); | 
|  | AddSampledTexture("foo_texture", sampled_texture_type.get(), 0, 0); | 
|  | AddSampler("foo_sampler", 0, 1); | 
|  | AddGlobalVariable("foo_coords", f32_type()); | 
|  |  | 
|  | auto* func = MakeSamplerReferenceBodyFunction( | 
|  | "ep", "foo_texture", "foo_sampler", "foo_coords", f32_type()); | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetSamplerResourceBindings("ep"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(1u, result[0].binding); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetSamplerResourceBindingsTest, NoSampler) { | 
|  | auto* func = MakeEmptyBodyFunction("ep_func"); | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetSamplerResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(0u, result.size()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetSamplerResourceBindingsTest, InFunction) { | 
|  | auto sampled_texture_type = | 
|  | MakeSampledTextureType(ast::type::TextureDimension::k1d, f32_type()); | 
|  | AddSampledTexture("foo_texture", sampled_texture_type.get(), 0, 0); | 
|  | AddSampler("foo_sampler", 0, 1); | 
|  | AddGlobalVariable("foo_coords", f32_type()); | 
|  |  | 
|  | auto* foo_func = MakeSamplerReferenceBodyFunction( | 
|  | "foo_func", "foo_texture", "foo_sampler", "foo_coords", f32_type()); | 
|  | mod()->AddFunction(foo_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "foo_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetSamplerResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(1u, result[0].binding); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetSamplerResourceBindingsTest, UnknownEntryPoint) { | 
|  | auto sampled_texture_type = | 
|  | MakeSampledTextureType(ast::type::TextureDimension::k1d, f32_type()); | 
|  | AddSampledTexture("foo_texture", sampled_texture_type.get(), 0, 0); | 
|  | AddSampler("foo_sampler", 0, 1); | 
|  | AddGlobalVariable("foo_coords", f32_type()); | 
|  |  | 
|  | auto* func = MakeSamplerReferenceBodyFunction( | 
|  | "ep", "foo_texture", "foo_sampler", "foo_coords", f32_type()); | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetSamplerResourceBindings("foo"); | 
|  | ASSERT_TRUE(inspector()->has_error()) << inspector()->error(); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetSamplerResourceBindingsTest, SkipsComparisonSamplers) { | 
|  | auto depth_texture_type = | 
|  | MakeDepthTextureType(ast::type::TextureDimension::k2d); | 
|  | AddDepthTexture("foo_texture", depth_texture_type.get()); | 
|  | AddComparisonSampler("foo_sampler", 0, 1); | 
|  | AddGlobalVariable("foo_coords", f32_type()); | 
|  | AddGlobalVariable("foo_depth", f32_type()); | 
|  |  | 
|  | auto* func = MakeComparisonSamplerReferenceBodyFunction( | 
|  | "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", | 
|  | f32_type()); | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetSamplerResourceBindings("ep"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(0u, result.size()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, Simple) { | 
|  | auto depth_texture_type = | 
|  | MakeDepthTextureType(ast::type::TextureDimension::k2d); | 
|  | AddDepthTexture("foo_texture", depth_texture_type.get()); | 
|  | AddComparisonSampler("foo_sampler", 0, 1); | 
|  | AddGlobalVariable("foo_coords", f32_type()); | 
|  | AddGlobalVariable("foo_depth", f32_type()); | 
|  |  | 
|  | auto* func = MakeComparisonSamplerReferenceBodyFunction( | 
|  | "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", | 
|  | f32_type()); | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetComparisonSamplerResourceBindings("ep"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(1u, result[0].binding); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, NoSampler) { | 
|  | auto* func = MakeEmptyBodyFunction("ep_func"); | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetComparisonSamplerResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(0u, result.size()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, InFunction) { | 
|  | auto depth_texture_type = | 
|  | MakeDepthTextureType(ast::type::TextureDimension::k2d); | 
|  | AddDepthTexture("foo_texture", depth_texture_type.get()); | 
|  | AddComparisonSampler("foo_sampler", 0, 1); | 
|  | AddGlobalVariable("foo_coords", f32_type()); | 
|  | AddGlobalVariable("foo_depth", f32_type()); | 
|  |  | 
|  | auto* foo_func = MakeComparisonSamplerReferenceBodyFunction( | 
|  | "foo_func", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", | 
|  | f32_type()); | 
|  | mod()->AddFunction(foo_func); | 
|  |  | 
|  | auto* ep_func = MakeCallerBodyFunction("ep_func", "foo_func"); | 
|  | ep_func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(ep_func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetComparisonSamplerResourceBindings("ep_func"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(1u, result[0].binding); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, UnknownEntryPoint) { | 
|  | auto depth_texture_type = | 
|  | MakeDepthTextureType(ast::type::TextureDimension::k2d); | 
|  | AddDepthTexture("foo_texture", depth_texture_type.get()); | 
|  | AddComparisonSampler("foo_sampler", 0, 1); | 
|  | AddGlobalVariable("foo_coords", f32_type()); | 
|  | AddGlobalVariable("foo_depth", f32_type()); | 
|  |  | 
|  | auto* func = MakeComparisonSamplerReferenceBodyFunction( | 
|  | "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_depth", | 
|  | f32_type()); | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetSamplerResourceBindings("foo"); | 
|  | ASSERT_TRUE(inspector()->has_error()) << inspector()->error(); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetComparisonSamplerResourceBindingsTest, SkipsSamplers) { | 
|  | auto sampled_texture_type = | 
|  | MakeSampledTextureType(ast::type::TextureDimension::k1d, f32_type()); | 
|  | AddSampledTexture("foo_texture", sampled_texture_type.get(), 0, 0); | 
|  | AddSampler("foo_sampler", 0, 1); | 
|  | AddGlobalVariable("foo_coords", f32_type()); | 
|  |  | 
|  | auto* func = MakeSamplerReferenceBodyFunction( | 
|  | "ep", "foo_texture", "foo_sampler", "foo_coords", f32_type()); | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetComparisonSamplerResourceBindings("ep"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(0u, result.size()); | 
|  | } | 
|  |  | 
|  | TEST_F(InspectorGetSampledTextureResourceBindingsTest, Empty) { | 
|  | auto* foo = MakeEmptyBodyFunction("foo"); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | auto result = inspector()->GetSampledTextureResourceBindings("foo"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | EXPECT_EQ(0u, result.size()); | 
|  | } | 
|  |  | 
|  | TEST_P(InspectorGetSampledTextureResourceBindingsTestWithParam, textureSample) { | 
|  | auto sampled_texture_type = MakeSampledTextureType( | 
|  | GetParam().type_dim, GetBaseType(GetParam().sampled_kind)); | 
|  | AddSampledTexture("foo_texture", sampled_texture_type.get(), 0, 0); | 
|  | AddSampler("foo_sampler", 0, 1); | 
|  | auto* coord_type = | 
|  | GetCoordsType(GetParam().type_dim, GetParam().sampled_kind); | 
|  | AddGlobalVariable("foo_coords", coord_type); | 
|  |  | 
|  | auto* func = MakeSamplerReferenceBodyFunction( | 
|  | "ep", "foo_texture", "foo_sampler", "foo_coords", | 
|  | GetBaseType(GetParam().sampled_kind)); | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetSampledTextureResourceBindings("ep"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(GetParam().inspector_dim, result[0].dim); | 
|  | EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P( | 
|  | InspectorGetSampledTextureResourceBindingsTest, | 
|  | InspectorGetSampledTextureResourceBindingsTestWithParam, | 
|  | testing::Values( | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k1d, | 
|  | inspector::ResourceBinding::TextureDimension::k1d, | 
|  | inspector::ResourceBinding::SampledKind::kFloat}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k1d, | 
|  | inspector::ResourceBinding::TextureDimension::k1d, | 
|  | inspector::ResourceBinding::SampledKind::kSInt}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k1d, | 
|  | inspector::ResourceBinding::TextureDimension::k1d, | 
|  | inspector::ResourceBinding::SampledKind::kUInt}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k2d, | 
|  | inspector::ResourceBinding::TextureDimension::k2d, | 
|  | inspector::ResourceBinding::SampledKind::kFloat}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k2d, | 
|  | inspector::ResourceBinding::TextureDimension::k2d, | 
|  | inspector::ResourceBinding::SampledKind::kSInt}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k2d, | 
|  | inspector::ResourceBinding::TextureDimension::k2d, | 
|  | inspector::ResourceBinding::SampledKind::kUInt}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k3d, | 
|  | inspector::ResourceBinding::TextureDimension::k3d, | 
|  | inspector::ResourceBinding::SampledKind::kFloat}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k3d, | 
|  | inspector::ResourceBinding::TextureDimension::k3d, | 
|  | inspector::ResourceBinding::SampledKind::kSInt}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k3d, | 
|  | inspector::ResourceBinding::TextureDimension::k3d, | 
|  | inspector::ResourceBinding::SampledKind::kUInt}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::kCube, | 
|  | inspector::ResourceBinding::TextureDimension::kCube, | 
|  | inspector::ResourceBinding::SampledKind::kFloat}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::kCube, | 
|  | inspector::ResourceBinding::TextureDimension::kCube, | 
|  | inspector::ResourceBinding::SampledKind::kSInt}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::kCube, | 
|  | inspector::ResourceBinding::TextureDimension::kCube, | 
|  | inspector::ResourceBinding::SampledKind::kUInt})); | 
|  |  | 
|  | TEST_P(InspectorGetSampledArrayTextureResourceBindingsTestWithParam, | 
|  | textureSample) { | 
|  | auto sampled_texture_type = MakeSampledTextureType( | 
|  | GetParam().type_dim, GetBaseType(GetParam().sampled_kind)); | 
|  | AddSampledTexture("foo_texture", sampled_texture_type.get(), 0, 0); | 
|  | AddSampler("foo_sampler", 0, 1); | 
|  | auto* coord_type = | 
|  | GetCoordsType(GetParam().type_dim, GetParam().sampled_kind); | 
|  | AddGlobalVariable("foo_coords", coord_type); | 
|  | AddGlobalVariable("foo_array_index", u32_type()); | 
|  |  | 
|  | auto* func = MakeSamplerReferenceBodyFunction( | 
|  | "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_array_index", | 
|  | GetBaseType(GetParam().sampled_kind)); | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetSampledTextureResourceBindings("ep"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(GetParam().inspector_dim, result[0].dim); | 
|  | EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P( | 
|  | InspectorGetSampledArrayTextureResourceBindingsTest, | 
|  | InspectorGetSampledArrayTextureResourceBindingsTestWithParam, | 
|  | testing::Values( | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k1dArray, | 
|  | inspector::ResourceBinding::TextureDimension::k1dArray, | 
|  | inspector::ResourceBinding::SampledKind::kFloat}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k1dArray, | 
|  | inspector::ResourceBinding::TextureDimension::k1dArray, | 
|  | inspector::ResourceBinding::SampledKind::kSInt}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k1dArray, | 
|  | inspector::ResourceBinding::TextureDimension::k1dArray, | 
|  | inspector::ResourceBinding::SampledKind::kUInt}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k2dArray, | 
|  | inspector::ResourceBinding::TextureDimension::k2dArray, | 
|  | inspector::ResourceBinding::SampledKind::kFloat}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k2dArray, | 
|  | inspector::ResourceBinding::TextureDimension::k2dArray, | 
|  | inspector::ResourceBinding::SampledKind::kSInt}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k2dArray, | 
|  | inspector::ResourceBinding::TextureDimension::k2dArray, | 
|  | inspector::ResourceBinding::SampledKind::kUInt}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::kCubeArray, | 
|  | inspector::ResourceBinding::TextureDimension::kCubeArray, | 
|  | inspector::ResourceBinding::SampledKind::kFloat}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::kCubeArray, | 
|  | inspector::ResourceBinding::TextureDimension::kCubeArray, | 
|  | inspector::ResourceBinding::SampledKind::kSInt}, | 
|  | GetSampledTextureTestParams{ | 
|  | ast::type::TextureDimension::kCubeArray, | 
|  | inspector::ResourceBinding::TextureDimension::kCubeArray, | 
|  | inspector::ResourceBinding::SampledKind::kUInt})); | 
|  |  | 
|  | TEST_P(InspectorGetMultisampledTextureResourceBindingsTestWithParam, | 
|  | textureSample) { | 
|  | auto multisampled_texture_type = MakeMultisampledTextureType( | 
|  | GetParam().type_dim, GetBaseType(GetParam().sampled_kind)); | 
|  | AddMultisampledTexture("foo_texture", multisampled_texture_type.get(), 0, 0); | 
|  | AddSampler("foo_sampler", 0, 1); | 
|  | auto* coord_type = | 
|  | GetCoordsType(GetParam().type_dim, GetParam().sampled_kind); | 
|  | AddGlobalVariable("foo_coords", coord_type); | 
|  |  | 
|  | auto* func = MakeSamplerReferenceBodyFunction( | 
|  | "ep", "foo_texture", "foo_sampler", "foo_coords", | 
|  | GetBaseType(GetParam().sampled_kind)); | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetMultisampledTextureResourceBindings("ep"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(GetParam().inspector_dim, result[0].dim); | 
|  | EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P( | 
|  | InspectorGetMultisampledTextureResourceBindingsTest, | 
|  | InspectorGetMultisampledTextureResourceBindingsTestWithParam, | 
|  | testing::Values( | 
|  | GetMultisampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k1d, | 
|  | inspector::ResourceBinding::TextureDimension::k1d, | 
|  | inspector::ResourceBinding::SampledKind::kFloat}, | 
|  | GetMultisampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k1d, | 
|  | inspector::ResourceBinding::TextureDimension::k1d, | 
|  | inspector::ResourceBinding::SampledKind::kSInt}, | 
|  | GetMultisampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k1d, | 
|  | inspector::ResourceBinding::TextureDimension::k1d, | 
|  | inspector::ResourceBinding::SampledKind::kUInt}, | 
|  | GetMultisampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k2d, | 
|  | inspector::ResourceBinding::TextureDimension::k2d, | 
|  | inspector::ResourceBinding::SampledKind::kFloat}, | 
|  | GetMultisampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k2d, | 
|  | inspector::ResourceBinding::TextureDimension::k2d, | 
|  | inspector::ResourceBinding::SampledKind::kSInt}, | 
|  | GetMultisampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k2d, | 
|  | inspector::ResourceBinding::TextureDimension::k2d, | 
|  | inspector::ResourceBinding::SampledKind::kUInt})); | 
|  |  | 
|  | TEST_F(InspectorGetMultisampledArrayTextureResourceBindingsTest, Empty) { | 
|  | auto* foo = MakeEmptyBodyFunction("foo"); | 
|  | foo->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(foo); | 
|  |  | 
|  | auto result = inspector()->GetSampledTextureResourceBindings("foo"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | EXPECT_EQ(0u, result.size()); | 
|  | } | 
|  |  | 
|  | TEST_P(InspectorGetMultisampledArrayTextureResourceBindingsTestWithParam, | 
|  | textureSample) { | 
|  | auto multisampled_texture_type = MakeMultisampledTextureType( | 
|  | GetParam().type_dim, GetBaseType(GetParam().sampled_kind)); | 
|  | AddMultisampledTexture("foo_texture", multisampled_texture_type.get(), 0, 0); | 
|  | AddSampler("foo_sampler", 0, 1); | 
|  | auto* coord_type = | 
|  | GetCoordsType(GetParam().type_dim, GetParam().sampled_kind); | 
|  | AddGlobalVariable("foo_coords", coord_type); | 
|  | AddGlobalVariable("foo_array_index", u32_type()); | 
|  |  | 
|  | auto* func = MakeSamplerReferenceBodyFunction( | 
|  | "ep", "foo_texture", "foo_sampler", "foo_coords", "foo_array_index", | 
|  | GetBaseType(GetParam().sampled_kind)); | 
|  | func->add_decoration( | 
|  | create<ast::StageDecoration>(ast::PipelineStage::kVertex, Source{})); | 
|  | mod()->AddFunction(func); | 
|  |  | 
|  | ASSERT_TRUE(td()->Determine()) << td()->error(); | 
|  |  | 
|  | auto result = inspector()->GetMultisampledTextureResourceBindings("ep"); | 
|  | ASSERT_FALSE(inspector()->has_error()) << inspector()->error(); | 
|  |  | 
|  | ASSERT_EQ(1u, result.size()); | 
|  | EXPECT_EQ(0u, result[0].bind_group); | 
|  | EXPECT_EQ(0u, result[0].binding); | 
|  | EXPECT_EQ(GetParam().inspector_dim, result[0].dim); | 
|  | EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind); | 
|  | } | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P( | 
|  | InspectorGetMultisampledArrayTextureResourceBindingsTest, | 
|  | InspectorGetMultisampledArrayTextureResourceBindingsTestWithParam, | 
|  | testing::Values( | 
|  | GetMultisampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k1dArray, | 
|  | inspector::ResourceBinding::TextureDimension::k1dArray, | 
|  | inspector::ResourceBinding::SampledKind::kFloat}, | 
|  | GetMultisampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k1dArray, | 
|  | inspector::ResourceBinding::TextureDimension::k1dArray, | 
|  | inspector::ResourceBinding::SampledKind::kSInt}, | 
|  | GetMultisampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k1dArray, | 
|  | inspector::ResourceBinding::TextureDimension::k1dArray, | 
|  | inspector::ResourceBinding::SampledKind::kUInt}, | 
|  | GetMultisampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k2dArray, | 
|  | inspector::ResourceBinding::TextureDimension::k2dArray, | 
|  | inspector::ResourceBinding::SampledKind::kFloat}, | 
|  | GetMultisampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k2dArray, | 
|  | inspector::ResourceBinding::TextureDimension::k2dArray, | 
|  | inspector::ResourceBinding::SampledKind::kSInt}, | 
|  | GetMultisampledTextureTestParams{ | 
|  | ast::type::TextureDimension::k2dArray, | 
|  | inspector::ResourceBinding::TextureDimension::k2dArray, | 
|  | inspector::ResourceBinding::SampledKind::kUInt})); | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace inspector | 
|  | }  // namespace tint |