| // 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 "src/ast/bool_literal.h" | 
 | #include "src/ast/constant_id_decoration.h" | 
 | #include "src/ast/float_literal.h" | 
 | #include "src/ast/module.h" | 
 | #include "src/ast/scalar_constructor_expression.h" | 
 | #include "src/ast/sint_literal.h" | 
 | #include "src/ast/uint_literal.h" | 
 | #include "src/sem/access_control_type.h" | 
 | #include "src/sem/array_type.h" | 
 | #include "src/sem/f32_type.h" | 
 | #include "src/sem/function.h" | 
 | #include "src/sem/i32_type.h" | 
 | #include "src/sem/matrix_type.h" | 
 | #include "src/sem/multisampled_texture_type.h" | 
 | #include "src/sem/sampled_texture_type.h" | 
 | #include "src/sem/storage_texture_type.h" | 
 | #include "src/sem/struct.h" | 
 | #include "src/sem/struct_type.h" | 
 | #include "src/sem/u32_type.h" | 
 | #include "src/sem/variable.h" | 
 | #include "src/sem/vector_type.h" | 
 | #include "src/sem/void_type.h" | 
 |  | 
 | namespace tint { | 
 | namespace inspector { | 
 |  | 
 | namespace { | 
 |  | 
 | void AppendResourceBindings(std::vector<ResourceBinding>* dest, | 
 |                             const std::vector<ResourceBinding>& orig) { | 
 |   TINT_ASSERT(dest); | 
 |   if (!dest) { | 
 |     return; | 
 |   } | 
 |  | 
 |   dest->reserve(dest->size() + orig.size()); | 
 |   dest->insert(dest->end(), orig.begin(), orig.end()); | 
 | } | 
 |  | 
 | ResourceBinding::TextureDimension | 
 | TypeTextureDimensionToResourceBindingTextureDimension( | 
 |     const ast::TextureDimension& type_dim) { | 
 |   switch (type_dim) { | 
 |     case ast::TextureDimension::k1d: | 
 |       return ResourceBinding::TextureDimension::k1d; | 
 |     case ast::TextureDimension::k2d: | 
 |       return ResourceBinding::TextureDimension::k2d; | 
 |     case ast::TextureDimension::k2dArray: | 
 |       return ResourceBinding::TextureDimension::k2dArray; | 
 |     case ast::TextureDimension::k3d: | 
 |       return ResourceBinding::TextureDimension::k3d; | 
 |     case ast::TextureDimension::kCube: | 
 |       return ResourceBinding::TextureDimension::kCube; | 
 |     case ast::TextureDimension::kCubeArray: | 
 |       return ResourceBinding::TextureDimension::kCubeArray; | 
 |     case ast::TextureDimension::kNone: | 
 |       return ResourceBinding::TextureDimension::kNone; | 
 |   } | 
 |   return ResourceBinding::TextureDimension::kNone; | 
 | } | 
 |  | 
 | ResourceBinding::SampledKind BaseTypeToSampledKind(sem::Type* base_type) { | 
 |   if (!base_type) { | 
 |     return ResourceBinding::SampledKind::kUnknown; | 
 |   } | 
 |  | 
 |   if (auto* at = base_type->As<sem::ArrayType>()) { | 
 |     base_type = at->type(); | 
 |   } else if (auto* mt = base_type->As<sem::Matrix>()) { | 
 |     base_type = mt->type(); | 
 |   } else if (auto* vt = base_type->As<sem::Vector>()) { | 
 |     base_type = vt->type(); | 
 |   } | 
 |  | 
 |   if (base_type->Is<sem::F32>()) { | 
 |     return ResourceBinding::SampledKind::kFloat; | 
 |   } else if (base_type->Is<sem::U32>()) { | 
 |     return ResourceBinding::SampledKind::kUInt; | 
 |   } else if (base_type->Is<sem::I32>()) { | 
 |     return ResourceBinding::SampledKind::kSInt; | 
 |   } else { | 
 |     return ResourceBinding::SampledKind::kUnknown; | 
 |   } | 
 | } | 
 |  | 
 | ResourceBinding::ImageFormat TypeImageFormatToResourceBindingImageFormat( | 
 |     const ast::ImageFormat& image_format) { | 
 |   switch (image_format) { | 
 |     case ast::ImageFormat::kR8Unorm: | 
 |       return ResourceBinding::ImageFormat::kR8Unorm; | 
 |     case ast::ImageFormat::kR8Snorm: | 
 |       return ResourceBinding::ImageFormat::kR8Snorm; | 
 |     case ast::ImageFormat::kR8Uint: | 
 |       return ResourceBinding::ImageFormat::kR8Uint; | 
 |     case ast::ImageFormat::kR8Sint: | 
 |       return ResourceBinding::ImageFormat::kR8Sint; | 
 |     case ast::ImageFormat::kR16Uint: | 
 |       return ResourceBinding::ImageFormat::kR16Uint; | 
 |     case ast::ImageFormat::kR16Sint: | 
 |       return ResourceBinding::ImageFormat::kR16Sint; | 
 |     case ast::ImageFormat::kR16Float: | 
 |       return ResourceBinding::ImageFormat::kR16Float; | 
 |     case ast::ImageFormat::kRg8Unorm: | 
 |       return ResourceBinding::ImageFormat::kRg8Unorm; | 
 |     case ast::ImageFormat::kRg8Snorm: | 
 |       return ResourceBinding::ImageFormat::kRg8Snorm; | 
 |     case ast::ImageFormat::kRg8Uint: | 
 |       return ResourceBinding::ImageFormat::kRg8Uint; | 
 |     case ast::ImageFormat::kRg8Sint: | 
 |       return ResourceBinding::ImageFormat::kRg8Sint; | 
 |     case ast::ImageFormat::kR32Uint: | 
 |       return ResourceBinding::ImageFormat::kR32Uint; | 
 |     case ast::ImageFormat::kR32Sint: | 
 |       return ResourceBinding::ImageFormat::kR32Sint; | 
 |     case ast::ImageFormat::kR32Float: | 
 |       return ResourceBinding::ImageFormat::kR32Float; | 
 |     case ast::ImageFormat::kRg16Uint: | 
 |       return ResourceBinding::ImageFormat::kRg16Uint; | 
 |     case ast::ImageFormat::kRg16Sint: | 
 |       return ResourceBinding::ImageFormat::kRg16Sint; | 
 |     case ast::ImageFormat::kRg16Float: | 
 |       return ResourceBinding::ImageFormat::kRg16Float; | 
 |     case ast::ImageFormat::kRgba8Unorm: | 
 |       return ResourceBinding::ImageFormat::kRgba8Unorm; | 
 |     case ast::ImageFormat::kRgba8UnormSrgb: | 
 |       return ResourceBinding::ImageFormat::kRgba8UnormSrgb; | 
 |     case ast::ImageFormat::kRgba8Snorm: | 
 |       return ResourceBinding::ImageFormat::kRgba8Snorm; | 
 |     case ast::ImageFormat::kRgba8Uint: | 
 |       return ResourceBinding::ImageFormat::kRgba8Uint; | 
 |     case ast::ImageFormat::kRgba8Sint: | 
 |       return ResourceBinding::ImageFormat::kRgba8Sint; | 
 |     case ast::ImageFormat::kBgra8Unorm: | 
 |       return ResourceBinding::ImageFormat::kBgra8Unorm; | 
 |     case ast::ImageFormat::kBgra8UnormSrgb: | 
 |       return ResourceBinding::ImageFormat::kBgra8UnormSrgb; | 
 |     case ast::ImageFormat::kRgb10A2Unorm: | 
 |       return ResourceBinding::ImageFormat::kRgb10A2Unorm; | 
 |     case ast::ImageFormat::kRg11B10Float: | 
 |       return ResourceBinding::ImageFormat::kRg11B10Float; | 
 |     case ast::ImageFormat::kRg32Uint: | 
 |       return ResourceBinding::ImageFormat::kRg32Uint; | 
 |     case ast::ImageFormat::kRg32Sint: | 
 |       return ResourceBinding::ImageFormat::kRg32Sint; | 
 |     case ast::ImageFormat::kRg32Float: | 
 |       return ResourceBinding::ImageFormat::kRg32Float; | 
 |     case ast::ImageFormat::kRgba16Uint: | 
 |       return ResourceBinding::ImageFormat::kRgba16Uint; | 
 |     case ast::ImageFormat::kRgba16Sint: | 
 |       return ResourceBinding::ImageFormat::kRgba16Sint; | 
 |     case ast::ImageFormat::kRgba16Float: | 
 |       return ResourceBinding::ImageFormat::kRgba16Float; | 
 |     case ast::ImageFormat::kRgba32Uint: | 
 |       return ResourceBinding::ImageFormat::kRgba32Uint; | 
 |     case ast::ImageFormat::kRgba32Sint: | 
 |       return ResourceBinding::ImageFormat::kRgba32Sint; | 
 |     case ast::ImageFormat::kRgba32Float: | 
 |       return ResourceBinding::ImageFormat::kRgba32Float; | 
 |     case ast::ImageFormat::kNone: | 
 |       return ResourceBinding::ImageFormat::kNone; | 
 |   } | 
 |   return ResourceBinding::ImageFormat::kNone; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | Inspector::Inspector(const Program* program) : program_(program) {} | 
 |  | 
 | Inspector::~Inspector() = default; | 
 |  | 
 | std::vector<EntryPoint> Inspector::GetEntryPoints() { | 
 |   std::vector<EntryPoint> result; | 
 |  | 
 |   for (auto* func : program_->AST().Functions()) { | 
 |     if (!func->IsEntryPoint()) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     EntryPoint entry_point; | 
 |     entry_point.name = program_->Symbols().NameFor(func->symbol()); | 
 |     entry_point.remapped_name = program_->Symbols().NameFor(func->symbol()); | 
 |     entry_point.stage = func->pipeline_stage(); | 
 |     std::tie(entry_point.workgroup_size_x, entry_point.workgroup_size_y, | 
 |              entry_point.workgroup_size_z) = func->workgroup_size(); | 
 |  | 
 |     for (auto* param : func->params()) { | 
 |       AddEntryPointInOutVariables(program_->Symbols().NameFor(param->symbol()), | 
 |                                   param->declared_type(), param->decorations(), | 
 |                                   entry_point.input_variables); | 
 |     } | 
 |  | 
 |     if (!func->return_type()->Is<sem::Void>()) { | 
 |       AddEntryPointInOutVariables("<retval>", func->return_type(), | 
 |                                   func->return_type_decorations(), | 
 |                                   entry_point.output_variables); | 
 |     } | 
 |  | 
 |     // TODO(crbug.com/tint/697): Remove this. | 
 |     for (auto* var : program_->Sem().Get(func)->ReferencedModuleVariables()) { | 
 |       auto* decl = var->Declaration(); | 
 |  | 
 |       auto name = program_->Symbols().NameFor(decl->symbol()); | 
 |       if (ast::HasDecoration<ast::BuiltinDecoration>(decl->decorations())) { | 
 |         continue; | 
 |       } | 
 |  | 
 |       StageVariable stage_variable; | 
 |       stage_variable.name = name; | 
 |  | 
 |       stage_variable.component_type = ComponentType::kUnknown; | 
 |       auto* type = var->Type()->UnwrapAll(); | 
 |       if (type->is_float_scalar_or_vector() || type->is_float_matrix()) { | 
 |         stage_variable.component_type = ComponentType::kFloat; | 
 |       } else if (type->is_unsigned_scalar_or_vector()) { | 
 |         stage_variable.component_type = ComponentType::kUInt; | 
 |       } else if (type->is_signed_scalar_or_vector()) { | 
 |         stage_variable.component_type = ComponentType::kSInt; | 
 |       } | 
 |  | 
 |       auto* location_decoration = | 
 |           ast::GetDecoration<ast::LocationDecoration>(decl->decorations()); | 
 |       if (location_decoration) { | 
 |         stage_variable.has_location_decoration = true; | 
 |         stage_variable.location_decoration = location_decoration->value(); | 
 |       } else { | 
 |         stage_variable.has_location_decoration = false; | 
 |       } | 
 |  | 
 |       if (var->StorageClass() == ast::StorageClass::kInput) { | 
 |         entry_point.input_variables.push_back(stage_variable); | 
 |       } else if (var->StorageClass() == ast::StorageClass::kOutput) { | 
 |         entry_point.output_variables.push_back(stage_variable); | 
 |       } | 
 |     } | 
 |  | 
 |     result.push_back(std::move(entry_point)); | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | std::string Inspector::GetRemappedNameForEntryPoint( | 
 |     const std::string& entry_point) { | 
 |   // TODO(rharrison): Reenable once all of the backends are using the renamed | 
 |   //                  entry points. | 
 |  | 
 |   //  auto* func = FindEntryPointByName(entry_point); | 
 |   //  if (!func) { | 
 |   //    return {}; | 
 |   //  } | 
 |   //  return func->name(); | 
 |   return entry_point; | 
 | } | 
 |  | 
 | std::map<uint32_t, Scalar> Inspector::GetConstantIDs() { | 
 |   std::map<uint32_t, Scalar> result; | 
 |   for (auto* var : program_->AST().GlobalVariables()) { | 
 |     if (!ast::HasDecoration<ast::ConstantIdDecoration>(var->decorations())) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     // If there are conflicting defintions for a constant id, that is invalid | 
 |     // WGSL, so the resolver should catch it. Thus here the inspector just | 
 |     // assumes all definitions of the constant id are the same, so only needs | 
 |     // to find the first reference to constant id. | 
 |     uint32_t constant_id = var->constant_id(); | 
 |     if (result.find(constant_id) != result.end()) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     if (!var->has_constructor()) { | 
 |       result[constant_id] = Scalar(); | 
 |       continue; | 
 |     } | 
 |  | 
 |     auto* expression = var->constructor(); | 
 |     auto* constructor = expression->As<ast::ConstructorExpression>(); | 
 |     if (constructor == nullptr) { | 
 |       // This is invalid WGSL, but handling gracefully. | 
 |       result[constant_id] = Scalar(); | 
 |       continue; | 
 |     } | 
 |  | 
 |     auto* scalar_constructor = | 
 |         constructor->As<ast::ScalarConstructorExpression>(); | 
 |     if (scalar_constructor == nullptr) { | 
 |       // This is invalid WGSL, but handling gracefully. | 
 |       result[constant_id] = Scalar(); | 
 |       continue; | 
 |     } | 
 |  | 
 |     auto* literal = scalar_constructor->literal(); | 
 |     if (!literal) { | 
 |       // This is invalid WGSL, but handling gracefully. | 
 |       result[constant_id] = Scalar(); | 
 |       continue; | 
 |     } | 
 |  | 
 |     if (auto* l = literal->As<ast::BoolLiteral>()) { | 
 |       result[constant_id] = Scalar(l->IsTrue()); | 
 |       continue; | 
 |     } | 
 |  | 
 |     if (auto* l = literal->As<ast::UintLiteral>()) { | 
 |       result[constant_id] = Scalar(l->value()); | 
 |       continue; | 
 |     } | 
 |  | 
 |     if (auto* l = literal->As<ast::SintLiteral>()) { | 
 |       result[constant_id] = Scalar(l->value()); | 
 |       continue; | 
 |     } | 
 |  | 
 |     if (auto* l = literal->As<ast::FloatLiteral>()) { | 
 |       result[constant_id] = Scalar(l->value()); | 
 |       continue; | 
 |     } | 
 |  | 
 |     result[constant_id] = Scalar(); | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> Inspector::GetResourceBindings( | 
 |     const std::string& entry_point) { | 
 |   auto* func = FindEntryPointByName(entry_point); | 
 |   if (!func) { | 
 |     return {}; | 
 |   } | 
 |  | 
 |   std::vector<ResourceBinding> result; | 
 |  | 
 |   AppendResourceBindings(&result, | 
 |                          GetUniformBufferResourceBindings(entry_point)); | 
 |   AppendResourceBindings(&result, | 
 |                          GetStorageBufferResourceBindings(entry_point)); | 
 |   AppendResourceBindings(&result, | 
 |                          GetReadOnlyStorageBufferResourceBindings(entry_point)); | 
 |   AppendResourceBindings(&result, GetSamplerResourceBindings(entry_point)); | 
 |   AppendResourceBindings(&result, | 
 |                          GetComparisonSamplerResourceBindings(entry_point)); | 
 |   AppendResourceBindings(&result, | 
 |                          GetSampledTextureResourceBindings(entry_point)); | 
 |   AppendResourceBindings(&result, | 
 |                          GetMultisampledTextureResourceBindings(entry_point)); | 
 |   AppendResourceBindings( | 
 |       &result, GetReadOnlyStorageTextureResourceBindings(entry_point)); | 
 |   AppendResourceBindings( | 
 |       &result, GetWriteOnlyStorageTextureResourceBindings(entry_point)); | 
 |   AppendResourceBindings(&result, GetDepthTextureResourceBindings(entry_point)); | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> Inspector::GetUniformBufferResourceBindings( | 
 |     const std::string& entry_point) { | 
 |   auto* func = FindEntryPointByName(entry_point); | 
 |   if (!func) { | 
 |     return {}; | 
 |   } | 
 |  | 
 |   std::vector<ResourceBinding> result; | 
 |  | 
 |   auto* func_sem = program_->Sem().Get(func); | 
 |   for (auto& ruv : func_sem->ReferencedUniformVariables()) { | 
 |     auto* var = ruv.first; | 
 |     auto binding_info = ruv.second; | 
 |  | 
 |     auto* unwrapped_type = var->Type()->UnwrapIfNeeded(); | 
 |     auto* str = unwrapped_type->As<sem::StructType>(); | 
 |     if (str == nullptr) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     if (!str->IsBlockDecorated()) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     auto* sem = program_->Sem().Get(str); | 
 |     if (!sem) { | 
 |       error_ = "Missing semantic information for structure " + | 
 |                program_->Symbols().NameFor(str->impl()->name()); | 
 |       continue; | 
 |     } | 
 |  | 
 |     ResourceBinding entry; | 
 |     entry.resource_type = ResourceBinding::ResourceType::kUniformBuffer; | 
 |     entry.bind_group = binding_info.group->value(); | 
 |     entry.binding = binding_info.binding->value(); | 
 |     entry.size = sem->Size(); | 
 |     entry.size_no_padding = sem->SizeNoPadding(); | 
 |  | 
 |     result.push_back(entry); | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> Inspector::GetStorageBufferResourceBindings( | 
 |     const std::string& entry_point) { | 
 |   return GetStorageBufferResourceBindingsImpl(entry_point, false); | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> | 
 | Inspector::GetReadOnlyStorageBufferResourceBindings( | 
 |     const std::string& entry_point) { | 
 |   return GetStorageBufferResourceBindingsImpl(entry_point, true); | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> Inspector::GetSamplerResourceBindings( | 
 |     const std::string& entry_point) { | 
 |   auto* func = FindEntryPointByName(entry_point); | 
 |   if (!func) { | 
 |     return {}; | 
 |   } | 
 |  | 
 |   std::vector<ResourceBinding> result; | 
 |  | 
 |   auto* func_sem = program_->Sem().Get(func); | 
 |   for (auto& rs : func_sem->ReferencedSamplerVariables()) { | 
 |     auto binding_info = rs.second; | 
 |  | 
 |     ResourceBinding entry; | 
 |     entry.resource_type = ResourceBinding::ResourceType::kSampler; | 
 |     entry.bind_group = binding_info.group->value(); | 
 |     entry.binding = binding_info.binding->value(); | 
 |  | 
 |     result.push_back(entry); | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> Inspector::GetComparisonSamplerResourceBindings( | 
 |     const std::string& entry_point) { | 
 |   auto* func = FindEntryPointByName(entry_point); | 
 |   if (!func) { | 
 |     return {}; | 
 |   } | 
 |  | 
 |   std::vector<ResourceBinding> result; | 
 |  | 
 |   auto* func_sem = program_->Sem().Get(func); | 
 |   for (auto& rcs : func_sem->ReferencedComparisonSamplerVariables()) { | 
 |     auto binding_info = rcs.second; | 
 |  | 
 |     ResourceBinding entry; | 
 |     entry.resource_type = ResourceBinding::ResourceType::kComparisonSampler; | 
 |     entry.bind_group = binding_info.group->value(); | 
 |     entry.binding = binding_info.binding->value(); | 
 |  | 
 |     result.push_back(entry); | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> Inspector::GetSampledTextureResourceBindings( | 
 |     const std::string& entry_point) { | 
 |   return GetSampledTextureResourceBindingsImpl(entry_point, false); | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> Inspector::GetMultisampledTextureResourceBindings( | 
 |     const std::string& entry_point) { | 
 |   return GetSampledTextureResourceBindingsImpl(entry_point, true); | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> | 
 | Inspector::GetReadOnlyStorageTextureResourceBindings( | 
 |     const std::string& entry_point) { | 
 |   return GetStorageTextureResourceBindingsImpl(entry_point, true); | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> | 
 | Inspector::GetWriteOnlyStorageTextureResourceBindings( | 
 |     const std::string& entry_point) { | 
 |   return GetStorageTextureResourceBindingsImpl(entry_point, false); | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> Inspector::GetDepthTextureResourceBindings( | 
 |     const std::string& entry_point) { | 
 |   auto* func = FindEntryPointByName(entry_point); | 
 |   if (!func) { | 
 |     return {}; | 
 |   } | 
 |  | 
 |   std::vector<ResourceBinding> result; | 
 |   auto* func_sem = program_->Sem().Get(func); | 
 |   for (auto& ref : func_sem->ReferencedDepthTextureVariables()) { | 
 |     auto* var = ref.first; | 
 |     auto binding_info = ref.second; | 
 |  | 
 |     ResourceBinding entry; | 
 |     entry.resource_type = ResourceBinding::ResourceType::kDepthTexture; | 
 |     entry.bind_group = binding_info.group->value(); | 
 |     entry.binding = binding_info.binding->value(); | 
 |  | 
 |     auto* texture_type = var->Type()->UnwrapIfNeeded()->As<sem::Texture>(); | 
 |     entry.dim = TypeTextureDimensionToResourceBindingTextureDimension( | 
 |         texture_type->dim()); | 
 |  | 
 |     result.push_back(entry); | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | ast::Function* Inspector::FindEntryPointByName(const std::string& name) { | 
 |   auto* func = program_->AST().Functions().Find(program_->Symbols().Get(name)); | 
 |   if (!func) { | 
 |     error_ += name + " was not found!"; | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   if (!func->IsEntryPoint()) { | 
 |     error_ += name + " is not an entry point!"; | 
 |     return nullptr; | 
 |   } | 
 |  | 
 |   return func; | 
 | } | 
 |  | 
 | void Inspector::AddEntryPointInOutVariables( | 
 |     std::string name, | 
 |     sem::Type* type, | 
 |     const ast::DecorationList& decorations, | 
 |     std::vector<StageVariable>& variables) const { | 
 |   // Skip builtins. | 
 |   if (ast::HasDecoration<ast::BuiltinDecoration>(decorations)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   auto* unwrapped_type = type->UnwrapAll(); | 
 |  | 
 |   if (auto* struct_ty = unwrapped_type->As<sem::StructType>()) { | 
 |     // Recurse into members. | 
 |     for (auto* member : struct_ty->impl()->members()) { | 
 |       AddEntryPointInOutVariables( | 
 |           name + "." + program_->Symbols().NameFor(member->symbol()), | 
 |           member->type(), member->decorations(), variables); | 
 |     } | 
 |     return; | 
 |   } | 
 |  | 
 |   // Base case: add the variable. | 
 |  | 
 |   StageVariable stage_variable; | 
 |   stage_variable.name = name; | 
 |   stage_variable.component_type = ComponentType::kUnknown; | 
 |   if (unwrapped_type->is_float_scalar_or_vector() || | 
 |       unwrapped_type->is_float_matrix()) { | 
 |     stage_variable.component_type = ComponentType::kFloat; | 
 |   } else if (unwrapped_type->is_unsigned_scalar_or_vector()) { | 
 |     stage_variable.component_type = ComponentType::kUInt; | 
 |   } else if (unwrapped_type->is_signed_scalar_or_vector()) { | 
 |     stage_variable.component_type = ComponentType::kSInt; | 
 |   } | 
 |  | 
 |   auto* location = ast::GetDecoration<ast::LocationDecoration>(decorations); | 
 |   TINT_ASSERT(location != nullptr); | 
 |   stage_variable.has_location_decoration = true; | 
 |   stage_variable.location_decoration = location->value(); | 
 |  | 
 |   variables.push_back(stage_variable); | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> Inspector::GetStorageBufferResourceBindingsImpl( | 
 |     const std::string& entry_point, | 
 |     bool read_only) { | 
 |   auto* func = FindEntryPointByName(entry_point); | 
 |   if (!func) { | 
 |     return {}; | 
 |   } | 
 |  | 
 |   auto* func_sem = program_->Sem().Get(func); | 
 |   std::vector<ResourceBinding> result; | 
 |   for (auto& rsv : func_sem->ReferencedStorageBufferVariables()) { | 
 |     auto* var = rsv.first; | 
 |     auto binding_info = rsv.second; | 
 |  | 
 |     auto* ac_type = var->Type()->As<sem::AccessControl>(); | 
 |     if (ac_type == nullptr) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     if (read_only != ac_type->IsReadOnly()) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     auto* str = var->Type()->UnwrapIfNeeded()->As<sem::StructType>(); | 
 |     if (!str) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     auto* sem = program_->Sem().Get(str); | 
 |     if (!sem) { | 
 |       error_ = "Missing semantic information for structure " + | 
 |                program_->Symbols().NameFor(str->impl()->name()); | 
 |       continue; | 
 |     } | 
 |  | 
 |     ResourceBinding entry; | 
 |     entry.resource_type = | 
 |         read_only ? ResourceBinding::ResourceType::kReadOnlyStorageBuffer | 
 |                   : ResourceBinding::ResourceType::kStorageBuffer; | 
 |     entry.bind_group = binding_info.group->value(); | 
 |     entry.binding = binding_info.binding->value(); | 
 |     entry.size = sem->Size(); | 
 |     entry.size_no_padding = sem->SizeNoPadding(); | 
 |  | 
 |     result.push_back(entry); | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> Inspector::GetSampledTextureResourceBindingsImpl( | 
 |     const std::string& entry_point, | 
 |     bool multisampled_only) { | 
 |   auto* func = FindEntryPointByName(entry_point); | 
 |   if (!func) { | 
 |     return {}; | 
 |   } | 
 |  | 
 |   std::vector<ResourceBinding> result; | 
 |   auto* func_sem = program_->Sem().Get(func); | 
 |   auto referenced_variables = | 
 |       multisampled_only ? func_sem->ReferencedMultisampledTextureVariables() | 
 |                         : func_sem->ReferencedSampledTextureVariables(); | 
 |   for (auto& ref : referenced_variables) { | 
 |     auto* var = ref.first; | 
 |     auto binding_info = ref.second; | 
 |  | 
 |     ResourceBinding entry; | 
 |     entry.resource_type = | 
 |         multisampled_only ? ResourceBinding::ResourceType::kMultisampledTexture | 
 |                           : ResourceBinding::ResourceType::kSampledTexture; | 
 |     entry.bind_group = binding_info.group->value(); | 
 |     entry.binding = binding_info.binding->value(); | 
 |  | 
 |     auto* texture_type = var->Type()->UnwrapIfNeeded()->As<sem::Texture>(); | 
 |     entry.dim = TypeTextureDimensionToResourceBindingTextureDimension( | 
 |         texture_type->dim()); | 
 |  | 
 |     sem::Type* base_type = nullptr; | 
 |     if (multisampled_only) { | 
 |       base_type = texture_type->As<sem::MultisampledTexture>() | 
 |                       ->type() | 
 |                       ->UnwrapIfNeeded(); | 
 |     } else { | 
 |       base_type = | 
 |           texture_type->As<sem::SampledTexture>()->type()->UnwrapIfNeeded(); | 
 |     } | 
 |     entry.sampled_kind = BaseTypeToSampledKind(base_type); | 
 |  | 
 |     result.push_back(entry); | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | std::vector<ResourceBinding> Inspector::GetStorageTextureResourceBindingsImpl( | 
 |     const std::string& entry_point, | 
 |     bool read_only) { | 
 |   auto* func = FindEntryPointByName(entry_point); | 
 |   if (!func) { | 
 |     return {}; | 
 |   } | 
 |  | 
 |   auto* func_sem = program_->Sem().Get(func); | 
 |   std::vector<ResourceBinding> result; | 
 |   for (auto& ref : func_sem->ReferencedStorageTextureVariables()) { | 
 |     auto* var = ref.first; | 
 |     auto binding_info = ref.second; | 
 |  | 
 |     auto* ac_type = var->Type()->As<sem::AccessControl>(); | 
 |     if (ac_type == nullptr) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     if (read_only != ac_type->IsReadOnly()) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     ResourceBinding entry; | 
 |     entry.resource_type = | 
 |         read_only ? ResourceBinding::ResourceType::kReadOnlyStorageTexture | 
 |                   : ResourceBinding::ResourceType::kWriteOnlyStorageTexture; | 
 |     entry.bind_group = binding_info.group->value(); | 
 |     entry.binding = binding_info.binding->value(); | 
 |  | 
 |     auto* texture_type = | 
 |         var->Type()->UnwrapIfNeeded()->As<sem::StorageTexture>(); | 
 |     entry.dim = TypeTextureDimensionToResourceBindingTextureDimension( | 
 |         texture_type->dim()); | 
 |  | 
 |     sem::Type* base_type = texture_type->type()->UnwrapIfNeeded(); | 
 |     entry.sampled_kind = BaseTypeToSampledKind(base_type); | 
 |     entry.image_format = TypeImageFormatToResourceBindingImageFormat( | 
 |         texture_type->image_format()); | 
 |  | 
 |     result.push_back(entry); | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | }  // namespace inspector | 
 | }  // namespace tint |