Add TextureBuiltinsFromUniform transform
TextureNumLevels/TextureNumSamples counterpart builtin functions
are not available in GLSL for OpenGLES.
This transform replace those builtin calls by creating an internal
uniform buffer to store these builtin values. A bindpoint to
byte offset and data type is included in the result of the transform
so that caller will know how to upload the data.
But: dawn:1299, tint:2006
Change-Id: Idc1b6e699443b8b656208d49e73ec6001f1e77ea
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/145621
Commit-Queue: Shrek Shao <shrekshao@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/include/tint/tint.h b/include/tint/tint.h
index c8f6581..899de9a 100644
--- a/include/tint/tint.h
+++ b/include/tint/tint.h
@@ -25,6 +25,7 @@
#include "src/tint/api/options/array_length_from_uniform.h"
#include "src/tint/api/options/binding_remapper.h"
#include "src/tint/api/options/external_texture.h"
+#include "src/tint/api/options/texture_builtins_from_uniform.h"
#include "src/tint/api/tint.h"
#include "src/tint/lang/core/type/manager.h"
#include "src/tint/lang/wgsl/ast/transform/first_index_offset.h"
diff --git a/src/tint/api/options/BUILD.cmake b/src/tint/api/options/BUILD.cmake
index 2edad38..8117549 100644
--- a/src/tint/api/options/BUILD.cmake
+++ b/src/tint/api/options/BUILD.cmake
@@ -29,6 +29,7 @@
api/options/binding_remapper.h
api/options/external_texture.h
api/options/options.cc
+ api/options/texture_builtins_from_uniform.h
)
tint_target_add_dependencies(tint_api_options lib
diff --git a/src/tint/api/options/BUILD.gn b/src/tint/api/options/BUILD.gn
index e24f6f1..9608ec4 100644
--- a/src/tint/api/options/BUILD.gn
+++ b/src/tint/api/options/BUILD.gn
@@ -30,6 +30,7 @@
"binding_remapper.h",
"external_texture.h",
"options.cc",
+ "texture_builtins_from_uniform.h",
]
deps = [
"${tint_src_dir}/api/common",
diff --git a/src/tint/api/options/texture_builtins_from_uniform.h b/src/tint/api/options/texture_builtins_from_uniform.h
new file mode 100644
index 0000000..4dc2eff
--- /dev/null
+++ b/src/tint/api/options/texture_builtins_from_uniform.h
@@ -0,0 +1,52 @@
+// Copyright 2023 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.
+
+#ifndef SRC_TINT_API_OPTIONS_TEXTURE_BUILTINS_FROM_UNIFORM_H_
+#define SRC_TINT_API_OPTIONS_TEXTURE_BUILTINS_FROM_UNIFORM_H_
+
+#include <unordered_map>
+#include <utility>
+
+#include "src/tint/api/common/binding_point.h"
+#include "src/tint/utils/reflection/reflection.h"
+
+namespace tint {
+
+/// Options used to specify a mapping of binding points to indices into a UBO
+/// from which to load buffer sizes.
+struct TextureBuiltinsFromUniformOptions {
+ /// Indicate the type of field for each entry to push.
+ enum class Field {
+ /// The number of mip levels of the bonnd texture view.
+ TextureNumLevels,
+ /// The number of samples per texel of the bound multipsampled texture.
+ TextureNumSamples,
+ };
+
+ /// Records the field and the byte offset of the data to push in the internal uniform buffer.
+ using FieldAndOffset = std::pair<Field, uint32_t>;
+ /// Maps from binding point to data entry with the information to populate the data.
+ using BindingPointToFieldAndOffset = std::unordered_map<BindingPoint, FieldAndOffset>;
+
+ /// The binding point to use to generate a uniform buffer from which to read
+ /// buffer sizes.
+ BindingPoint ubo_binding = {};
+
+ /// Reflect the fields of this class so that it can be used by tint::ForeachField()
+ TINT_REFLECT(ubo_binding);
+};
+
+} // namespace tint
+
+#endif // SRC_TINT_API_OPTIONS_TEXTURE_BUILTINS_FROM_UNIFORM_H_
diff --git a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
index 127a2da..895fd56 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
@@ -62,6 +62,7 @@
#include "src/tint/lang/wgsl/ast/transform/single_entry_point.h"
#include "src/tint/lang/wgsl/ast/transform/std140.h"
#include "src/tint/lang/wgsl/ast/transform/texture_1d_to_2d.h"
+#include "src/tint/lang/wgsl/ast/transform/texture_builtins_from_uniform.h"
#include "src/tint/lang/wgsl/ast/transform/unshadow.h"
#include "src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.h"
#include "src/tint/lang/wgsl/ast/variable_decl_statement.h"
@@ -229,6 +230,15 @@
manager.Add<ast::transform::RemovePhonies>();
+ // TextureBuiltinsFromUniform must come before CombineSamplers to preserve texture binding point
+ // info, instead of combined sampler binding point. As a result, TextureBuiltinsFromUniform also
+ // comes before BindingRemapper so the binding point info it reflects is before remapping.
+ if (options.texture_builtins_from_uniform) {
+ manager.Add<ast::transform::TextureBuiltinsFromUniform>();
+ data.Add<ast::transform::TextureBuiltinsFromUniform::Config>(
+ options.texture_builtins_from_uniform->ubo_binding);
+ }
+
data.Add<ast::transform::CombineSamplers::BindingInfo>(options.binding_map,
options.placeholder_binding_point);
manager.Add<ast::transform::CombineSamplers>();
@@ -254,6 +264,10 @@
SanitizedResult result;
ast::transform::DataMap outputs;
result.program = manager.Run(in, data, outputs);
+ if (auto* res = outputs.Get<ast::transform::TextureBuiltinsFromUniform::Result>()) {
+ result.needs_internal_uniform_buffer = true;
+ result.bindpoint_to_data = std::move(res->bindpoint_to_data);
+ }
return result;
}
diff --git a/src/tint/lang/glsl/writer/ast_printer/ast_printer.h b/src/tint/lang/glsl/writer/ast_printer/ast_printer.h
index 6c8f5fb..ad44120 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer.h
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer.h
@@ -21,6 +21,7 @@
#include <unordered_set>
#include <utility>
+#include "src/tint/api/options/texture_builtins_from_uniform.h"
#include "src/tint/lang/core/builtin_value.h"
#include "src/tint/lang/glsl/writer/common/version.h"
#include "src/tint/lang/wgsl/program/program_builder.h"
@@ -53,6 +54,13 @@
/// The sanitized program.
Program program;
+
+ /// True if the shader needs a UBO.
+ bool needs_internal_uniform_buffer = false;
+
+ /// Store a map of global texture variable binding point to the byte offset and data type to
+ /// push into the internal uniform buffer.
+ TextureBuiltinsFromUniformOptions::BindingPointToFieldAndOffset bindpoint_to_data;
};
/// Sanitize a program in preparation for generating GLSL.
diff --git a/src/tint/lang/glsl/writer/common/options.h b/src/tint/lang/glsl/writer/common/options.h
index e2aadd7..2f55c4b 100644
--- a/src/tint/lang/glsl/writer/common/options.h
+++ b/src/tint/lang/glsl/writer/common/options.h
@@ -15,10 +15,12 @@
#ifndef SRC_TINT_LANG_GLSL_WRITER_COMMON_OPTIONS_H_
#define SRC_TINT_LANG_GLSL_WRITER_COMMON_OPTIONS_H_
+#include <optional>
#include <string>
#include <unordered_map>
#include "src/tint/api/options/external_texture.h"
+#include "src/tint/api/options/texture_builtins_from_uniform.h"
#include "src/tint/lang/core/access.h"
#include "src/tint/lang/glsl/writer/common/version.h"
#include "src/tint/lang/wgsl/sem/sampler_texture_pair.h"
@@ -66,6 +68,11 @@
/// Options used in the binding mappings for external textures
ExternalTextureOptions external_texture_options = {};
+ /// Options used to map WGSL textureNumLevels/textureNumSamples builtins to internal uniform
+ /// buffer values. If not specified, emits corresponding GLSL builtins
+ /// textureQueryLevels/textureSamples directly.
+ std::optional<TextureBuiltinsFromUniformOptions> texture_builtins_from_uniform = std::nullopt;
+
/// The GLSL version to emit
Version version;
@@ -74,6 +81,7 @@
allow_collisions,
disable_workgroup_init,
external_texture_options,
+ texture_builtins_from_uniform,
version);
};
diff --git a/src/tint/lang/glsl/writer/output.h b/src/tint/lang/glsl/writer/output.h
index 084b0ef..286af3f 100644
--- a/src/tint/lang/glsl/writer/output.h
+++ b/src/tint/lang/glsl/writer/output.h
@@ -19,6 +19,7 @@
#include <utility>
#include <vector>
+#include "src/tint/api/options/texture_builtins_from_uniform.h"
#include "src/tint/lang/wgsl/ast/pipeline_stage.h"
namespace tint::glsl::writer {
@@ -39,6 +40,13 @@
/// The list of entry points in the generated GLSL.
std::vector<std::pair<std::string, ast::PipelineStage>> entry_points;
+
+ /// True if the shader needs a UBO.
+ bool needs_internal_uniform_buffer = false;
+
+ /// Store a map of global texture variable binding points to the byte offset and data type to
+ /// push into the internal uniform buffer.
+ TextureBuiltinsFromUniformOptions::BindingPointToFieldAndOffset bindpoint_to_data;
};
} // namespace tint::glsl::writer
diff --git a/src/tint/lang/glsl/writer/writer.cc b/src/tint/lang/glsl/writer/writer.cc
index 4124346..e02c6bb 100644
--- a/src/tint/lang/glsl/writer/writer.cc
+++ b/src/tint/lang/glsl/writer/writer.cc
@@ -15,6 +15,7 @@
#include "src/tint/lang/glsl/writer/writer.h"
#include <memory>
+#include <utility>
#include "src/tint/lang/glsl/writer/ast_printer/ast_printer.h"
#include "src/tint/lang/wgsl/ast/transform/binding_remapper.h"
@@ -43,6 +44,8 @@
Output output;
output.glsl = impl->Result();
+ output.needs_internal_uniform_buffer = sanitized_result.needs_internal_uniform_buffer;
+ output.bindpoint_to_data = std::move(sanitized_result.bindpoint_to_data);
// Collect the list of entry points in the sanitized program.
for (auto* func : sanitized_result.program.AST().Functions()) {
diff --git a/src/tint/lang/wgsl/ast/transform/BUILD.cmake b/src/tint/lang/wgsl/ast/transform/BUILD.cmake
index e56ea18..d91332e 100644
--- a/src/tint/lang/wgsl/ast/transform/BUILD.cmake
+++ b/src/tint/lang/wgsl/ast/transform/BUILD.cmake
@@ -115,6 +115,8 @@
lang/wgsl/ast/transform/substitute_override.h
lang/wgsl/ast/transform/texture_1d_to_2d.cc
lang/wgsl/ast/transform/texture_1d_to_2d.h
+ lang/wgsl/ast/transform/texture_builtins_from_uniform.cc
+ lang/wgsl/ast/transform/texture_builtins_from_uniform.h
lang/wgsl/ast/transform/transform.cc
lang/wgsl/ast/transform/transform.h
lang/wgsl/ast/transform/truncate_interstage_variables.cc
@@ -213,6 +215,7 @@
lang/wgsl/ast/transform/std140_test.cc
lang/wgsl/ast/transform/substitute_override_test.cc
lang/wgsl/ast/transform/texture_1d_to_2d_test.cc
+ lang/wgsl/ast/transform/texture_builtins_from_uniform_test.cc
lang/wgsl/ast/transform/transform_test.cc
lang/wgsl/ast/transform/truncate_interstage_variables_test.cc
lang/wgsl/ast/transform/unshadow_test.cc
diff --git a/src/tint/lang/wgsl/ast/transform/BUILD.gn b/src/tint/lang/wgsl/ast/transform/BUILD.gn
index 8df2f94..c55205d 100644
--- a/src/tint/lang/wgsl/ast/transform/BUILD.gn
+++ b/src/tint/lang/wgsl/ast/transform/BUILD.gn
@@ -120,6 +120,8 @@
"substitute_override.h",
"texture_1d_to_2d.cc",
"texture_1d_to_2d.h",
+ "texture_builtins_from_uniform.cc",
+ "texture_builtins_from_uniform.h",
"transform.cc",
"transform.h",
"truncate_interstage_variables.cc",
@@ -216,6 +218,7 @@
"std140_test.cc",
"substitute_override_test.cc",
"texture_1d_to_2d_test.cc",
+ "texture_builtins_from_uniform_test.cc",
"transform_test.cc",
"truncate_interstage_variables_test.cc",
"unshadow_test.cc",
diff --git a/src/tint/lang/wgsl/ast/transform/texture_builtins_from_uniform.cc b/src/tint/lang/wgsl/ast/transform/texture_builtins_from_uniform.cc
new file mode 100644
index 0000000..4c4baee
--- /dev/null
+++ b/src/tint/lang/wgsl/ast/transform/texture_builtins_from_uniform.cc
@@ -0,0 +1,491 @@
+// Copyright 2023 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/tint/lang/wgsl/ast/transform/texture_builtins_from_uniform.h"
+
+#include <memory>
+#include <queue>
+#include <string>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include "src/tint/lang/wgsl/program/clone_context.h"
+#include "src/tint/lang/wgsl/program/program_builder.h"
+#include "src/tint/lang/wgsl/resolver/resolve.h"
+#include "src/tint/lang/wgsl/sem/call.h"
+#include "src/tint/lang/wgsl/sem/function.h"
+#include "src/tint/lang/wgsl/sem/module.h"
+#include "src/tint/lang/wgsl/sem/statement.h"
+#include "src/tint/lang/wgsl/sem/variable.h"
+
+#include "src/tint/utils/containers/hashmap.h"
+#include "src/tint/utils/containers/vector.h"
+#include "src/tint/utils/rtti/switch.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::transform::TextureBuiltinsFromUniform);
+TINT_INSTANTIATE_TYPEINFO(tint::ast::transform::TextureBuiltinsFromUniform::Config);
+TINT_INSTANTIATE_TYPEINFO(tint::ast::transform::TextureBuiltinsFromUniform::Result);
+
+namespace tint::ast::transform {
+
+namespace {
+
+/// The member name of the texture builtin values.
+constexpr std::string_view kTextureBuiltinValuesMemberNamePrefix = "texture_builtin_value_";
+
+bool ShouldRun(const Program* program) {
+ for (auto* fn : program->AST().Functions()) {
+ if (auto* sem_fn = program->Sem().Get(fn)) {
+ for (auto* builtin : sem_fn->DirectlyCalledBuiltins()) {
+ // GLSL ES has no native support for the counterpart of
+ // textureNumLevels (textureQueryLevels) and textureNumSamples (textureSamples)
+ if (builtin->Type() == core::Function::kTextureNumLevels) {
+ return true;
+ }
+ if (builtin->Type() == core::Function::kTextureNumSamples) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+} // namespace
+
+TextureBuiltinsFromUniform::TextureBuiltinsFromUniform() = default;
+TextureBuiltinsFromUniform::~TextureBuiltinsFromUniform() = default;
+
+/// PIMPL state for the transform
+struct TextureBuiltinsFromUniform::State {
+ /// Constructor
+ /// @param program the source program
+ /// @param in the input transform data
+ /// @param out the output transform data
+ explicit State(const Program* program, const DataMap& in, DataMap& out)
+ : src(program), inputs(in), outputs(out) {}
+
+ /// Runs the transform
+ /// @returns the new program or SkipTransform if the transform is not required
+ ApplyResult Run() {
+ auto* cfg = inputs.Get<Config>();
+ if (cfg == nullptr) {
+ b.Diagnostics().add_error(
+ diag::System::Transform,
+ "missing transform data for " +
+ std::string(tint::TypeInfo::Of<TextureBuiltinsFromUniform>().name));
+ return resolver::Resolve(b);
+ }
+
+ if (!ShouldRun(ctx.src)) {
+ return SkipTransform;
+ }
+
+ // The dependency order declartions guaranteed that we traverse interested functions in the
+ // following order:
+ // 1. texture builtins
+ // 2. user function directly calls texture builtins
+ // 3. user function calls 2.
+ // 4. user function calls 3.
+ // ...
+ // n. entry point function.
+ for (auto* fn_decl : sem.Module()->DependencyOrderedDeclarations()) {
+ if (auto* fn = sem.Get<sem::Function>(fn_decl)) {
+ for (auto* call : fn->DirectCalls()) {
+ auto* call_expr = call->Declaration();
+
+ tint::Switch(
+ call->Target(),
+ [&](const sem::Builtin* builtin) {
+ if (builtin->Type() != core::Function::kTextureNumLevels &&
+ builtin->Type() != core::Function::kTextureNumSamples) {
+ return;
+ }
+ if (auto* call_stmt =
+ call->Stmt()->Declaration()->As<CallStatement>()) {
+ if (call_stmt->expr == call->Declaration()) {
+ // textureNumLevels() / textureNumSamples() is used as a
+ // statement. The argument expression must be side-effect free,
+ // so just drop the statement.
+ RemoveStatement(ctx, call_stmt);
+ return;
+ }
+ }
+
+ auto* texture_expr = call->Declaration()->args[0];
+ auto* texture_sem = sem.GetVal(texture_expr)->RootIdentifier();
+ TINT_ASSERT(texture_sem);
+
+ TextureBuiltinsFromUniformOptions::Field dataType =
+ GetFieldFromBuiltinFunctionType(builtin->Type());
+
+ tint::Switch(
+ texture_sem,
+ [&](const sem::GlobalVariable* global) {
+ // This texture variable is a global variable.
+ auto binding = GetAndRecordGlobalBinding(global, dataType);
+ // Record the call and binding to be replaced later.
+ builtin_to_replace.Add(call_expr, binding);
+ },
+ [&](const sem::Variable* variable) {
+ // This texture variable is a user function parameter.
+ auto new_param =
+ GetAndRecordFunctionParameter(fn, variable, dataType);
+ // Record the call and new_param to be replaced later.
+ builtin_to_replace.Add(call_expr, new_param);
+ },
+ [&](Default) {
+ TINT_ICE() << "unexpected texture root identifier";
+ });
+ },
+ [&](const sem::Function* user_fn) {
+ auto user_param_to_info = fn_to_data.Find(user_fn);
+ if (!user_param_to_info) {
+ // Uninterested function not calling texture builtins with function
+ // texture param.
+ return;
+ }
+ TINT_ASSERT(call->Arguments().Length() ==
+ user_fn->Declaration()->params.Length());
+ for (size_t i = 0; i < call->Arguments().Length(); i++) {
+ auto param = user_fn->Declaration()->params[i];
+ auto info = user_param_to_info->Get(param);
+ if (info.has_value()) {
+ auto* arg = call->Arguments()[i];
+ auto* texture_sem = arg->RootIdentifier();
+ auto& args = call_to_data.GetOrCreate(call_expr, [&] {
+ return Vector<
+ std::variant<BindingPoint, const ast::Parameter*>, 4>();
+ });
+
+ tint::Switch(
+ texture_sem,
+ [&](const sem::GlobalVariable* global) {
+ // This texture variable is a global variable.
+ auto binding =
+ GetAndRecordGlobalBinding(global, info->field);
+ // Record the binding to add to args.
+ args.Push(binding);
+ },
+ [&](const sem::Variable* variable) {
+ // This texture variable is a user function parameter.
+ auto new_param = GetAndRecordFunctionParameter(
+ fn, variable, info->field);
+ // Record adding extra function parameter
+ args.Push(new_param);
+ },
+ [&](Default) {
+ TINT_ICE() << "unexpected texture root identifier";
+ });
+ }
+ }
+ });
+ }
+ }
+ }
+
+ // If there's no interested texture builtin at all, skip the transform.
+ if (bindpoint_to_data.empty()) {
+ return SkipTransform;
+ }
+
+ // If any functions need extra params, add them now.
+ if (!fn_to_data.IsEmpty()) {
+ for (auto pair : fn_to_data) {
+ auto* fn = pair.key;
+
+ // Reorder the param to a vector to make sure params are in the correct order.
+ Vector<const ast::Parameter*, 4> extra_params_in_order;
+ extra_params_in_order.Resize(pair.value.Count());
+ for (auto t_p : pair.value) {
+ TINT_ASSERT(t_p.value.extra_idx < extra_params_in_order.Length());
+ extra_params_in_order[t_p.value.extra_idx] = t_p.value.param;
+ }
+
+ for (auto p : extra_params_in_order) {
+ ctx.InsertBack(fn->Declaration()->params, p);
+ }
+ }
+ }
+
+ // Replace all interested texture builtin calls.
+ for (auto pair : builtin_to_replace) {
+ auto call = pair.key;
+ if (std::holds_alternative<BindingPoint>(pair.value)) {
+ // This texture is a global variable with binding point.
+ // Read builtin value from uniform buffer.
+ auto* builtin_value = GetUniformValue(std::get<BindingPoint>(pair.value));
+ ctx.Replace(call, builtin_value);
+ } else {
+ // Otherwise this value comes from a function param
+ auto* param = std::get<const ast::Parameter*>(pair.value);
+ ctx.Replace(call, b.Expr(param));
+ }
+ }
+
+ // Insert all extra args to interested function calls.
+ for (auto pair : call_to_data) {
+ auto call = pair.key;
+ for (auto new_arg_info : pair.value) {
+ if (std::holds_alternative<BindingPoint>(new_arg_info)) {
+ // This texture is a global variable with binding point.
+ // Read builtin value from uniform buffer.
+ auto* builtin_value = GetUniformValue(std::get<BindingPoint>(new_arg_info));
+ ctx.InsertBack(call->args, builtin_value);
+ } else {
+ // Otherwise this value comes from a function param
+ auto* param = std::get<const ast::Parameter*>(new_arg_info);
+ ctx.InsertBack(call->args, b.Expr(param));
+ }
+ }
+ }
+
+ outputs.Add<Result>(bindpoint_to_data);
+
+ ctx.Clone();
+ return resolver::Resolve(b);
+ }
+
+ private:
+ /// The source program
+ const Program* const src;
+ /// The transform inputs
+ const DataMap& inputs;
+ /// The transform outputs
+ DataMap& outputs;
+ /// The target program builder
+ ProgramBuilder b;
+ /// The clone context
+ program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+ /// Alias to the semantic info in ctx.src
+ const sem::Info& sem = ctx.src->Sem();
+
+ /// The bindpoint to byte offset and field to pass out in transform result.
+ /// For one texture type, it could only be passed into one of the
+ /// textureNumLevels or textureNumSamples because their accepting param texture
+ /// type is different. There cannot be a binding entry with both field type.
+ /// Note: because this transform must be run before CombineSampler and BindingRemapper,
+ /// the binding number here is before remapped.
+ Result::BindingPointToFieldAndOffset bindpoint_to_data;
+
+ struct FunctionExtraParamInfo {
+ using Field = TextureBuiltinsFromUniformOptions::Field;
+ // The kind of texture information this parameter holds.
+ Field field = Field::TextureNumLevels;
+
+ // The extra passed in param that corresponds to the texture param.
+ const ast::Parameter* param = nullptr;
+
+ // id of this extra param e.g. f(t0, foo, t1, e0, e1) e0 and e1 are extra params, their
+ // extra_idx are 0 and 1. This is to help sort extra ids in the correct order.
+ size_t extra_idx = 0;
+ };
+
+ /// Store a map from function to a collection of extra params that need adding.
+ /// The value of the map is made a map instead of a vector to make it easier to find the param.
+ /// for call sites. e.g. fn f(t: texture_2d<f32>) -> u32 {
+ /// return textureNumLevels(t);
+ /// }
+ /// ->
+ /// fn f(t : texture_2d<f32>, tint_symbol : u32) -> u32 {
+ /// return tint_symbol;
+ /// }
+ Hashmap<const sem::Function*, Hashmap<const ast::Parameter*, FunctionExtraParamInfo, 4>, 8>
+ fn_to_data;
+
+ /// For each callsite of the above functions, record a vector of extra call args that need
+ /// inserting. e.g. f(tex)
+ /// ->
+ /// f(tex, internal_uniform.texture_builtin_value), if tex is from a global
+ /// variable, store the BindingPoint. or f(tex, extra_param_tex), if tex is from a function
+ /// param, store the texture function parameter pointer.
+ Hashmap<const CallExpression*, Vector<std::variant<BindingPoint, const ast::Parameter*>, 4>, 8>
+ call_to_data;
+
+ /// Texture builtin calls to be replaced by either uniform values or function parameters.
+ Hashmap<const CallExpression*, std::variant<BindingPoint, const ast::Parameter*>, 8>
+ builtin_to_replace;
+
+ /// A map from global texture bindpoint to the symbol storing its builtin value in the uniform
+ /// buffer struct.
+ Hashmap<BindingPoint, Symbol, 16> bindpoint_to_syms;
+
+ /// The internal uniform buffer
+ const Variable* ubo = nullptr;
+ /// Get or create a UBO including u32 scalars for texture builtin values.
+ /// @returns the symbol of the uniform buffer variable.
+ Symbol GetUboSym() {
+ if (ubo) {
+ // Already created
+ return ubo->name->symbol;
+ }
+
+ auto* cfg = inputs.Get<Config>();
+
+ Vector<const ast::StructMember*, 16> new_members;
+ new_members.Resize(bindpoint_to_data.size());
+ for (auto it : bindpoint_to_data) {
+ // Emit a u32 scalar for each binding that needs builtin value passed in.
+ size_t i = it.second.second / sizeof(uint32_t);
+ TINT_ASSERT(i < new_members.Length());
+ // Append the vector index with the variable name to avoid unstable naming issue.
+ auto sym = b.Symbols().New(std::string(kTextureBuiltinValuesMemberNamePrefix) +
+ std::to_string(i));
+ bindpoint_to_syms.Add(it.first, sym);
+ new_members[i] = b.Member(sym, b.ty.u32());
+ }
+
+ // Find if there's any existing global variable using the same cfg->ubo_binding
+ for (auto* var : src->AST().Globals<Var>()) {
+ if (var->HasBindingPoint()) {
+ auto* global_sem = sem.Get<sem::GlobalVariable>(var);
+
+ // The original binding point
+ BindingPoint binding_point = *global_sem->BindingPoint();
+
+ if (binding_point == cfg->ubo_binding) {
+ // This ubo_binding struct already exists.
+ // which should only be added by other *FromUniform transforms.
+ // Replace it with a new struct including the new_member.
+ // Then remove the old structure global declaration.
+
+ ubo = var->As<Variable>();
+
+ auto* ty = global_sem->Type()->UnwrapRef();
+ auto* str = ty->As<sem::Struct>();
+ if (TINT_UNLIKELY(!str)) {
+ TINT_ICE()
+ << "existing ubo binding " << cfg->ubo_binding << " is not a struct.";
+ return ctx.Clone(ubo->name->symbol);
+ }
+
+ for (auto new_member : new_members) {
+ ctx.InsertBack(str->Declaration()->members, new_member);
+ }
+ return ctx.Clone(ubo->name->symbol);
+ }
+ }
+ }
+
+ auto* buffer_struct = b.Structure(b.Sym(), std::move(new_members));
+ ubo = b.GlobalVar(b.Sym(), b.ty.Of(buffer_struct), core::AddressSpace::kUniform,
+ b.Group(core::AInt(cfg->ubo_binding.group)),
+ b.Binding(core::AInt(cfg->ubo_binding.binding)));
+ return ubo->name->symbol;
+ }
+
+ /// Get the expression of retrieving the builtin value from the uniform buffer.
+ /// @param binding of the global variable.
+ /// @returns an expression of the builtin value.
+ const ast::Expression* GetUniformValue(const BindingPoint& binding) {
+ auto iter = bindpoint_to_data.find(binding);
+ TINT_ASSERT(iter != bindpoint_to_data.end());
+
+ // Make sure GetUboSym() is called first to initialize the uniform buffer struct.
+ auto ubo_sym = GetUboSym();
+ // Load the builtin value from the UBO.
+ auto member_sym = bindpoint_to_syms.Get(binding);
+ TINT_ASSERT(member_sym.has_value());
+ auto* builtin_value = b.MemberAccessor(ubo_sym, *member_sym);
+
+ return builtin_value;
+ }
+
+ /// Get and return the binding of the global texture variable. Record in bindpoint_to_data if
+ /// first visited.
+ /// @param global global variable of the texture variable.
+ /// @param field type of the interested builtin function data related to this texture.
+ /// @returns binding of the global variable.
+ BindingPoint GetAndRecordGlobalBinding(const sem::GlobalVariable* global,
+ TextureBuiltinsFromUniformOptions::Field field) {
+ auto binding = global->BindingPoint().value();
+ auto iter = bindpoint_to_data.find(binding);
+ if (iter == bindpoint_to_data.end()) {
+ // First visit, recording the binding.
+ uint32_t index = static_cast<uint32_t>(bindpoint_to_data.size());
+ bindpoint_to_data.emplace(
+ binding,
+ Result::FieldAndOffset{field, index * static_cast<uint32_t>(sizeof(uint32_t))});
+ }
+ return binding;
+ }
+
+ /// Find which function param is the given texture variable.
+ /// Add a new u32 param relates to this texture param. Record in fn_to_data if first visited.
+ /// @param fn the current function scope.
+ /// @param var the texture variable.
+ /// @param field type of the interested builtin function data related to this texture.
+ /// @returns the new u32 function parameter.
+ const ast::Parameter* GetAndRecordFunctionParameter(
+ const sem::Function* fn,
+ const sem::Variable* var,
+ TextureBuiltinsFromUniformOptions::Field field) {
+ auto& param_to_info = fn_to_data.GetOrCreate(
+ fn, [&] { return Hashmap<const ast::Parameter*, FunctionExtraParamInfo, 4>(); });
+
+ const ast::Parameter* param = nullptr;
+ for (auto p : fn->Declaration()->params) {
+ if (p->As<Variable>() == var->Declaration()) {
+ param = p;
+ break;
+ }
+ }
+ TINT_ASSERT(param);
+ // Get or record a new u32 param to this function if first visited.
+ auto entry = param_to_info.Get(param);
+ if (entry.has_value()) {
+ return entry->param;
+ }
+ const ast::Parameter* new_param = b.Param(b.Sym(), b.ty.u32());
+ size_t idx = param_to_info.Count();
+ param_to_info.Add(param, FunctionExtraParamInfo{field, new_param, idx});
+ return new_param;
+ }
+
+ /// Get the uniform options field for the builtin function.
+ /// @param type of the builtin function
+ /// @returns corresponding TextureBuiltinsFromUniformOptions::Field for the builtin
+ static TextureBuiltinsFromUniformOptions::Field GetFieldFromBuiltinFunctionType(
+ core::Function type) {
+ switch (type) {
+ case core::Function::kTextureNumLevels:
+ return TextureBuiltinsFromUniformOptions::Field::TextureNumLevels;
+ case core::Function::kTextureNumSamples:
+ return TextureBuiltinsFromUniformOptions::Field::TextureNumSamples;
+ default:
+ TINT_UNREACHABLE() << "unsupported builtin function type " << type;
+ }
+ return TextureBuiltinsFromUniformOptions::Field::TextureNumLevels;
+ }
+};
+
+Transform::ApplyResult TextureBuiltinsFromUniform::Apply(const Program* src,
+ const DataMap& inputs,
+ DataMap& outputs) const {
+ return State{src, inputs, outputs}.Run();
+}
+
+TextureBuiltinsFromUniform::Config::Config(BindingPoint ubo_bp) : ubo_binding(ubo_bp) {}
+TextureBuiltinsFromUniform::Config::Config(const Config&) = default;
+TextureBuiltinsFromUniform::Config& TextureBuiltinsFromUniform::Config::operator=(const Config&) =
+ default;
+TextureBuiltinsFromUniform::Config::~Config() = default;
+
+TextureBuiltinsFromUniform::Result::Result(BindingPointToFieldAndOffset bindpoint_to_data_in)
+ : bindpoint_to_data(std::move(bindpoint_to_data_in)) {}
+TextureBuiltinsFromUniform::Result::Result(const Result&) = default;
+TextureBuiltinsFromUniform::Result::~Result() = default;
+
+} // namespace tint::ast::transform
diff --git a/src/tint/lang/wgsl/ast/transform/texture_builtins_from_uniform.h b/src/tint/lang/wgsl/ast/transform/texture_builtins_from_uniform.h
new file mode 100644
index 0000000..0db043e
--- /dev/null
+++ b/src/tint/lang/wgsl/ast/transform/texture_builtins_from_uniform.h
@@ -0,0 +1,119 @@
+// Copyright 2023 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.
+
+#ifndef SRC_TINT_LANG_WGSL_AST_TRANSFORM_TEXTURE_BUILTINS_FROM_UNIFORM_H_
+#define SRC_TINT_LANG_WGSL_AST_TRANSFORM_TEXTURE_BUILTINS_FROM_UNIFORM_H_
+
+#include <unordered_map>
+#include <unordered_set>
+
+#include "src/tint/api/common/binding_point.h"
+#include "src/tint/api/options/texture_builtins_from_uniform.h"
+#include "src/tint/lang/wgsl/ast/transform/transform.h"
+
+// Forward declarations
+namespace tint {
+class CloneContext;
+} // namespace tint
+
+namespace tint::ast::transform {
+
+/// TextureBuiltinsFromUniform is a transform that implements calls to textureNumLevels() and
+/// textureNumSamples() by retrieving the texture information from a uniform buffer, as those
+/// builtin functions are not available in some version of GLSL.
+///
+/// The generated uniform buffer will have the form:
+/// ```
+/// struct internal_uniform {
+/// texture_builtin_value_0 : u32,
+/// };
+///
+/// @group(0) @binding(0) var tex : texture_2d<f32>;
+/// ```
+/// The binding group and number used for this uniform buffer are provided via
+/// the `Config` transform input.
+///
+/// The transform coverts the texture builtins calls into values lookup from the internal
+/// buffer. If the texture is a function parameter instead of a global variable, this transform
+/// also takes care of adding extra paramters and arguments to these functions and their callsites.
+///
+/// This transform must run before `CombineSamplers` transform so that the binding point of the
+/// original texture object can be preserved.
+class TextureBuiltinsFromUniform final : public Castable<TextureBuiltinsFromUniform, Transform> {
+ public:
+ /// Constructor
+ TextureBuiltinsFromUniform();
+ /// Destructor
+ ~TextureBuiltinsFromUniform() override;
+
+ /// Configuration options for the TextureBuiltinsFromUniform transform.
+ struct Config final : public Castable<Config, Data> {
+ /// Constructor
+ /// @param ubo_bp the binding point to use for the generated uniform buffer.
+ explicit Config(BindingPoint ubo_bp);
+
+ /// Copy constructor
+ Config(const Config&);
+
+ /// Copy assignment
+ /// @return this Config
+ Config& operator=(const Config&);
+
+ /// Destructor
+ ~Config() override;
+
+ /// The binding point to use for the generated uniform buffer.
+ BindingPoint ubo_binding;
+ };
+
+ /// Information produced about what the transform did.
+ /// If there were no calls to the textureNumLevels() or textureNumSamples() builtin, then no
+ /// Result will be emitted.
+ struct Result final : public Castable<Result, Data> {
+ /// Using for shorter names
+ /// Records the field and the byte offset of the data to push in the internal uniform
+ /// buffer.
+ using FieldAndOffset = TextureBuiltinsFromUniformOptions::FieldAndOffset;
+ /// Maps from binding point to data entry with the information to populate the data.
+ using BindingPointToFieldAndOffset =
+ TextureBuiltinsFromUniformOptions::BindingPointToFieldAndOffset;
+
+ /// Constructor
+ /// @param bindpoint_to_data_in mapping from binding points of global texture variables to
+ /// the byte offsets and data types needed to be pushed into the internal uniform buffer.
+ explicit Result(BindingPointToFieldAndOffset bindpoint_to_data_in);
+
+ /// Copy constructor
+ Result(const Result&);
+
+ /// Destructor
+ ~Result() override;
+
+ /// A map of global texture variable binding point to the byte offset and data type to push
+ /// into the internal uniform buffer.
+ BindingPointToFieldAndOffset bindpoint_to_data;
+ };
+
+ /// @copydoc Transform::Apply
+ ApplyResult Apply(const Program* program,
+ const DataMap& inputs,
+ DataMap& outputs) const override;
+
+ private:
+ struct State;
+};
+
+} // namespace tint::ast::transform
+
+#endif // SRC_TINT_LANG_WGSL_AST_TRANSFORM_TEXTURE_BUILTINS_FROM_UNIFORM_H_
diff --git a/src/tint/lang/wgsl/ast/transform/texture_builtins_from_uniform_test.cc b/src/tint/lang/wgsl/ast/transform/texture_builtins_from_uniform_test.cc
new file mode 100644
index 0000000..21185e8
--- /dev/null
+++ b/src/tint/lang/wgsl/ast/transform/texture_builtins_from_uniform_test.cc
@@ -0,0 +1,687 @@
+// Copyright 2023 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/tint/lang/wgsl/ast/transform/texture_builtins_from_uniform.h"
+
+#include <utility>
+
+#include "src/tint/lang/wgsl/ast/transform/helper_test.h"
+
+namespace tint::ast::transform {
+namespace {
+
+using TextureBuiltinsFromUniformTest = TransformTest;
+
+TEST_F(TextureBuiltinsFromUniformTest, ShouldRunEmptyModule) {
+ auto* src = R"()";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ EXPECT_FALSE(ShouldRun<TextureBuiltinsFromUniform>(src, data));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, ShouldRunNoTextureNumLevels) {
+ auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ _ = textureDimensions(t);
+}
+)";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ EXPECT_FALSE(ShouldRun<TextureBuiltinsFromUniform>(src, data));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, ShouldRunWithTextureNumLevels) {
+ auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = textureNumLevels(t);
+}
+)";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ EXPECT_TRUE(ShouldRun<TextureBuiltinsFromUniform>(src, data));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, Error_MissingTransformData) {
+ auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = textureNumLevels(t);
+}
+)";
+
+ auto* expect =
+ "error: missing transform data for tint::ast::transform::TextureBuiltinsFromUniform";
+
+ auto got = Run<TextureBuiltinsFromUniform>(src);
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, BasicTextureNumLevels) {
+ auto* src = R"(
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = textureNumLevels(t);
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol {
+ texture_builtin_value_0 : u32,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = tint_symbol_1.texture_builtin_value_0;
+}
+)";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ auto got = Run<TextureBuiltinsFromUniform>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+ auto* val = got.data.Get<TextureBuiltinsFromUniform::Result>();
+ ASSERT_NE(val, nullptr);
+ // Note: Using the following EXPECT_EQ directly on BindingPointToFieldAndOffset seems to cause
+ // compiler to hang. EXPECT_EQ(
+ // TextureBuiltinsFromUniformOptions::BindingPointToFieldAndOffset{
+ // {BindgPoint{0u, 0u},
+ // std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 0u)}},
+ // val->bindpoint_to_data);
+ EXPECT_EQ(1u, val->bindpoint_to_data.size());
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 0u),
+ val->bindpoint_to_data.at(BindingPoint{0, 0}));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, BasicTextureNumSamples) {
+ auto* src = R"(
+@group(0) @binding(0) var t : texture_multisampled_2d<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ var samples : u32 = textureNumSamples(t);
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol {
+ texture_builtin_value_0 : u32,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+@group(0) @binding(0) var t : texture_multisampled_2d<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ var samples : u32 = tint_symbol_1.texture_builtin_value_0;
+}
+)";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ auto got = Run<TextureBuiltinsFromUniform>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+ auto* val = got.data.Get<TextureBuiltinsFromUniform::Result>();
+ ASSERT_NE(val, nullptr);
+ EXPECT_EQ(1u, val->bindpoint_to_data.size());
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumSamples, 0u),
+ val->bindpoint_to_data.at(BindingPoint{0, 0}));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, SameBuiltinCalledMultipleTimes) {
+ auto* src = R"(
+@group(0) @binding(0) var tex : texture_2d<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = textureNumLevels(tex);
+ len = textureNumLevels(tex);
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol {
+ texture_builtin_value_0 : u32,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+@group(0) @binding(0) var tex : texture_2d<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = tint_symbol_1.texture_builtin_value_0;
+ len = tint_symbol_1.texture_builtin_value_0;
+}
+)";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ auto got = Run<TextureBuiltinsFromUniform>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+ auto* val = got.data.Get<TextureBuiltinsFromUniform::Result>();
+ ASSERT_NE(val, nullptr);
+ EXPECT_EQ(1u, val->bindpoint_to_data.size());
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 0u),
+ val->bindpoint_to_data.at(BindingPoint{0, 0}));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, SameBuiltinCalledMultipleTimesTextureNumSamples) {
+ auto* src = R"(
+@group(0) @binding(0) var tex : texture_multisampled_2d<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = textureNumSamples(tex);
+ len = textureNumSamples(tex);
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol {
+ texture_builtin_value_0 : u32,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+@group(0) @binding(0) var tex : texture_multisampled_2d<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = tint_symbol_1.texture_builtin_value_0;
+ len = tint_symbol_1.texture_builtin_value_0;
+}
+)";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ auto got = Run<TextureBuiltinsFromUniform>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+ auto* val = got.data.Get<TextureBuiltinsFromUniform::Result>();
+ ASSERT_NE(val, nullptr);
+ EXPECT_EQ(1u, val->bindpoint_to_data.size());
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumSamples, 0u),
+ val->bindpoint_to_data.at(BindingPoint{0, 0}));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterBasic) {
+ auto* src = R"(
+@group(0) @binding(0) var tex : texture_2d<f32>;
+
+fn f(t: texture_2d<f32>) -> u32 {
+ return textureNumLevels(t);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = f(tex);
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol_1 {
+ texture_builtin_value_0 : u32,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_2 : tint_symbol_1;
+
+@group(0) @binding(0) var tex : texture_2d<f32>;
+
+fn f(t : texture_2d<f32>, tint_symbol : u32) -> u32 {
+ return tint_symbol;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = f(tex, tint_symbol_2.texture_builtin_value_0);
+}
+)";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ auto got = Run<TextureBuiltinsFromUniform>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+ auto* val = got.data.Get<TextureBuiltinsFromUniform::Result>();
+ ASSERT_NE(val, nullptr);
+ EXPECT_EQ(1u, val->bindpoint_to_data.size());
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 0u),
+ val->bindpoint_to_data.at(BindingPoint{0, 0}));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterUsedTwice) {
+ auto* src = R"(
+@group(0) @binding(0) var tex : texture_2d<f32>;
+
+fn f(t: texture_2d<f32>) -> u32 {
+ var len = textureNumLevels(t);
+ len += textureNumLevels(t);
+ return len;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = f(tex);
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol_1 {
+ texture_builtin_value_0 : u32,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_2 : tint_symbol_1;
+
+@group(0) @binding(0) var tex : texture_2d<f32>;
+
+fn f(t : texture_2d<f32>, tint_symbol : u32) -> u32 {
+ var len = tint_symbol;
+ len += tint_symbol;
+ return len;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = f(tex, tint_symbol_2.texture_builtin_value_0);
+}
+)";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ auto got = Run<TextureBuiltinsFromUniform>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+ auto* val = got.data.Get<TextureBuiltinsFromUniform::Result>();
+ ASSERT_NE(val, nullptr);
+ EXPECT_EQ(1u, val->bindpoint_to_data.size());
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 0u),
+ val->bindpoint_to_data.at(BindingPoint{0, 0}));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterMultipleParameters) {
+ auto* src = R"(
+@group(0) @binding(0) var tex1 : texture_2d<f32>;
+@group(0) @binding(1) var tex2 : texture_2d<f32>;
+@group(0) @binding(2) var tex3 : texture_2d<f32>;
+
+fn f(t1: texture_2d<f32>, t2: texture_2d<f32>, t3: texture_2d<f32>) -> u32 {
+ return textureNumLevels(t1) + textureNumLevels(t2) + textureNumLevels(t3);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = f(tex1, tex2, tex3);
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol_3 {
+ texture_builtin_value_0 : u32,
+ texture_builtin_value_1 : u32,
+ texture_builtin_value_2 : u32,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_4 : tint_symbol_3;
+
+@group(0) @binding(0) var tex1 : texture_2d<f32>;
+
+@group(0) @binding(1) var tex2 : texture_2d<f32>;
+
+@group(0) @binding(2) var tex3 : texture_2d<f32>;
+
+fn f(t1 : texture_2d<f32>, t2 : texture_2d<f32>, t3 : texture_2d<f32>, tint_symbol : u32, tint_symbol_1 : u32, tint_symbol_2 : u32) -> u32 {
+ return ((tint_symbol + tint_symbol_1) + tint_symbol_2);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = f(tex1, tex2, tex3, tint_symbol_4.texture_builtin_value_0, tint_symbol_4.texture_builtin_value_1, tint_symbol_4.texture_builtin_value_2);
+}
+)";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ auto got = Run<TextureBuiltinsFromUniform>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+ auto* val = got.data.Get<TextureBuiltinsFromUniform::Result>();
+ ASSERT_NE(val, nullptr);
+ EXPECT_EQ(3u, val->bindpoint_to_data.size());
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 0u),
+ val->bindpoint_to_data.at(BindingPoint{0, 0}));
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 4u),
+ val->bindpoint_to_data.at(BindingPoint{0, 1}));
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 8u),
+ val->bindpoint_to_data.at(BindingPoint{0, 2}));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterNested) {
+ auto* src = R"(
+@group(0) @binding(0) var tex : texture_2d<f32>;
+
+fn f2(tt: texture_2d<f32>) -> u32 {
+ return textureNumLevels(tt);
+}
+
+fn f1(t: texture_2d<f32>) -> u32 {
+ return f2(t);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = f1(tex);
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol_2 {
+ texture_builtin_value_0 : u32,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_3 : tint_symbol_2;
+
+@group(0) @binding(0) var tex : texture_2d<f32>;
+
+fn f2(tt : texture_2d<f32>, tint_symbol : u32) -> u32 {
+ return tint_symbol;
+}
+
+fn f1(t : texture_2d<f32>, tint_symbol_1 : u32) -> u32 {
+ return f2(t, tint_symbol_1);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = f1(tex, tint_symbol_3.texture_builtin_value_0);
+}
+)";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ auto got = Run<TextureBuiltinsFromUniform>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+ auto* val = got.data.Get<TextureBuiltinsFromUniform::Result>();
+ ASSERT_NE(val, nullptr);
+ EXPECT_EQ(1u, val->bindpoint_to_data.size());
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 0u),
+ val->bindpoint_to_data.at(BindingPoint{0, 0}));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, TextureAsFunctionParameterMixed) {
+ auto* src = R"(
+@group(0) @binding(0) var tex0 : texture_2d<f32>;
+@group(0) @binding(1) var tex1 : texture_2d<f32>;
+@group(0) @binding(2) var tex2 : texture_2d<f32>;
+@group(0) @binding(3) var tex3 : texture_2d<f32>;
+@group(0) @binding(4) var tex4 : texture_2d_array<f32>; // unused for textureNumLevels
+
+fn f_nested(t1: texture_2d<f32>, t2: texture_2d<f32>) -> u32 {
+ return textureNumLevels(t1) + textureNumLevels(t2);
+}
+
+fn f1(a: u32, t: texture_2d<f32>) -> u32 {
+ return a + f_nested(t, tex1) + textureNumLevels(tex3);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ _ = textureNumLayers(tex4);
+ _ = f1(9u, tex0);
+ _ = f_nested(tex2, tex2);
+ _ = f_nested(tex1, tex0);
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol_3 {
+ texture_builtin_value_0 : u32,
+ texture_builtin_value_1 : u32,
+ texture_builtin_value_2 : u32,
+ texture_builtin_value_3 : u32,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_4 : tint_symbol_3;
+
+@group(0) @binding(0) var tex0 : texture_2d<f32>;
+
+@group(0) @binding(1) var tex1 : texture_2d<f32>;
+
+@group(0) @binding(2) var tex2 : texture_2d<f32>;
+
+@group(0) @binding(3) var tex3 : texture_2d<f32>;
+
+@group(0) @binding(4) var tex4 : texture_2d_array<f32>;
+
+fn f_nested(t1 : texture_2d<f32>, t2 : texture_2d<f32>, tint_symbol : u32, tint_symbol_1 : u32) -> u32 {
+ return (tint_symbol + tint_symbol_1);
+}
+
+fn f1(a : u32, t : texture_2d<f32>, tint_symbol_2 : u32) -> u32 {
+ return ((a + f_nested(t, tex1, tint_symbol_2, tint_symbol_4.texture_builtin_value_0)) + tint_symbol_4.texture_builtin_value_1);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ _ = textureNumLayers(tex4);
+ _ = f1(9u, tex0, tint_symbol_4.texture_builtin_value_2);
+ _ = f_nested(tex2, tex2, tint_symbol_4.texture_builtin_value_3, tint_symbol_4.texture_builtin_value_3);
+ _ = f_nested(tex1, tex0, tint_symbol_4.texture_builtin_value_0, tint_symbol_4.texture_builtin_value_2);
+}
+)";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ auto got = Run<TextureBuiltinsFromUniform>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+ auto* val = got.data.Get<TextureBuiltinsFromUniform::Result>();
+ ASSERT_NE(val, nullptr);
+ EXPECT_EQ(4u, val->bindpoint_to_data.size());
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 0u),
+ val->bindpoint_to_data.at(BindingPoint{0, 1}));
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 4u),
+ val->bindpoint_to_data.at(BindingPoint{0, 3}));
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 8u),
+ val->bindpoint_to_data.at(BindingPoint{0, 0}));
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 12u),
+ val->bindpoint_to_data.at(BindingPoint{0, 2}));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, MultipleTextures) {
+ auto* src = R"(
+@group(0) @binding(0) var t0 : texture_2d<f32>;
+@group(0) @binding(1) var t1 : texture_multisampled_2d<f32>;
+@group(0) @binding(2) var t2 : texture_2d_array<f32>;
+@group(0) @binding(3) var t3 : texture_cube<f32>;
+@group(0) @binding(4) var t4 : texture_depth_2d;
+@group(1) @binding(0) var t5 : texture_depth_multisampled_2d;
+
+@compute @workgroup_size(1)
+fn main() {
+ _ = textureNumLevels(t0);
+ _ = textureNumSamples(t1);
+ _ = textureNumLevels(t2);
+ _ = textureNumLevels(t3);
+ _ = textureNumLevels(t4);
+ _ = textureNumSamples(t5);
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol {
+ texture_builtin_value_0 : u32,
+ texture_builtin_value_1 : u32,
+ texture_builtin_value_2 : u32,
+ texture_builtin_value_3 : u32,
+ texture_builtin_value_4 : u32,
+ texture_builtin_value_5 : u32,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+@group(0) @binding(0) var t0 : texture_2d<f32>;
+
+@group(0) @binding(1) var t1 : texture_multisampled_2d<f32>;
+
+@group(0) @binding(2) var t2 : texture_2d_array<f32>;
+
+@group(0) @binding(3) var t3 : texture_cube<f32>;
+
+@group(0) @binding(4) var t4 : texture_depth_2d;
+
+@group(1) @binding(0) var t5 : texture_depth_multisampled_2d;
+
+@compute @workgroup_size(1)
+fn main() {
+ _ = tint_symbol_1.texture_builtin_value_0;
+ _ = tint_symbol_1.texture_builtin_value_1;
+ _ = tint_symbol_1.texture_builtin_value_2;
+ _ = tint_symbol_1.texture_builtin_value_3;
+ _ = tint_symbol_1.texture_builtin_value_4;
+ _ = tint_symbol_1.texture_builtin_value_5;
+}
+)";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ auto got = Run<TextureBuiltinsFromUniform>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+ auto* val = got.data.Get<TextureBuiltinsFromUniform::Result>();
+ ASSERT_NE(val, nullptr);
+ EXPECT_EQ(6u, val->bindpoint_to_data.size());
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 0u),
+ val->bindpoint_to_data.at(BindingPoint{0, 0}));
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumSamples, 4u),
+ val->bindpoint_to_data.at(BindingPoint{0, 1}));
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 8u),
+ val->bindpoint_to_data.at(BindingPoint{0, 2}));
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 12u),
+ val->bindpoint_to_data.at(BindingPoint{0, 3}));
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 16u),
+ val->bindpoint_to_data.at(BindingPoint{0, 4}));
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumSamples, 20u),
+ val->bindpoint_to_data.at(BindingPoint{1, 0}));
+}
+
+TEST_F(TextureBuiltinsFromUniformTest, BindingPointExist) {
+ auto* src = R"(
+struct tint_symbol {
+ foo : array<vec4<u32>, 1u>,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = textureNumLevels(t);
+}
+)";
+
+ auto* expect = R"(
+struct tint_symbol {
+ foo : array<vec4<u32>, 1u>,
+ texture_builtin_value_0 : u32,
+}
+
+@group(0) @binding(30) var<uniform> tint_symbol_1 : tint_symbol;
+
+@group(0) @binding(0) var t : texture_2d<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ var len : u32 = tint_symbol_1.texture_builtin_value_0;
+}
+)";
+
+ TextureBuiltinsFromUniform::Config cfg({0, 30u});
+
+ DataMap data;
+ data.Add<TextureBuiltinsFromUniform::Config>(std::move(cfg));
+
+ auto got = Run<TextureBuiltinsFromUniform>(src, data);
+
+ EXPECT_EQ(expect, str(got));
+ auto* val = got.data.Get<TextureBuiltinsFromUniform::Result>();
+ ASSERT_NE(val, nullptr);
+ EXPECT_EQ(std::make_pair(TextureBuiltinsFromUniformOptions::Field::TextureNumLevels, 0u),
+ val->bindpoint_to_data.at(BindingPoint{0, 0}));
+}
+
+} // namespace
+} // namespace tint::ast::transform