tint/transform/robustness: Implement predicated mode

This change overhauls the Robustness transform to support three modes,
per address space:
* ignore    - Disable robustness checks for the address space
* clamp     - Clamp indices / texture args to ensure they're in
              bounds. This was the old behavior, and continues to
              be the default.
* predicate - Condition all indexing / textureLoad / textureStore /
              atomic* operations on the bounds check. If any
              dependent value is out of bounds, then the operation
              is skipped.

This change also fixes multiple expression evaluation of the texture
builtin 'level' argument.

Change-Id: I2e300ddff2c8d3183a9701f06985ce1b262baf2c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/122343
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Ben Clayton <bclayton@chromium.org>
Commit-Queue: Ben Clayton <bclayton@chromium.org>
This commit is contained in:
Ben Clayton 2023-03-06 21:05:01 +00:00 committed by Dawn LUCI CQ
parent 5e56551551
commit 8525ff29da
11 changed files with 6012 additions and 1561 deletions

View File

@ -20,10 +20,15 @@
#include "src/tint/program_builder.h"
#include "src/tint/sem/block_statement.h"
#include "src/tint/sem/builtin.h"
#include "src/tint/sem/call.h"
#include "src/tint/sem/function.h"
#include "src/tint/sem/index_accessor_expression.h"
#include "src/tint/sem/load.h"
#include "src/tint/sem/member_accessor_expression.h"
#include "src/tint/sem/statement.h"
#include "src/tint/sem/value_expression.h"
#include "src/tint/transform/utils/hoist_to_decl_before.h"
#include "src/tint/type/reference.h"
TINT_INSTANTIATE_TYPEINFO(tint::transform::Robustness);
@ -36,16 +41,147 @@ namespace tint::transform {
/// PIMPL state for the transform
struct Robustness::State {
/// Constructor
/// @param program the source program
/// @param omitted the omitted address spaces
State(const Program* program, std::unordered_set<builtin::AddressSpace>&& omitted)
: src(program), omitted_address_spaces(std::move(omitted)) {}
/// @param p the source program
/// @param c the transform config
State(const Program* p, Config&& c) : src(p), cfg(std::move(c)) {}
/// Runs the transform
/// @returns the new program or SkipTransform if the transform is not required
ApplyResult Run() {
ctx.ReplaceAll([&](const ast::IndexAccessorExpression* expr) { return Transform(expr); });
ctx.ReplaceAll([&](const ast::CallExpression* expr) { return Transform(expr); });
if (HasAction(Action::kPredicate)) {
AddPredicateParameters();
}
// Walk all the AST nodes in the module, starting with the leaf nodes.
// The most deeply nested expressions will come first.
for (auto* node : ctx.src->ASTNodes().Objects()) {
Switch(
node, //
[&](const ast::IndexAccessorExpression* e) {
// obj[idx]
// Array, matrix and vector indexing may require robustness transformation.
auto* expr = sem.Get(e)->Unwrap()->As<sem::IndexAccessorExpression>();
switch (ActionFor(expr)) {
case Action::kPredicate:
PredicateIndexAccessor(expr);
break;
case Action::kClamp:
ClampIndexAccessor(expr);
break;
case Action::kIgnore:
break;
}
},
[&](const ast::IdentifierExpression* e) {
// Identifiers may resolve to pointer lets, which may be predicated.
// Inspect.
if (auto* user = sem.Get<sem::VariableUser>(e)) {
auto* v = user->Variable();
if (v->Type()->Is<type::Pointer>()) {
// Propagate predicate from pointer
if (auto pred = predicates.Get(v->Declaration()->initializer)) {
predicates.Add(e, *pred);
}
}
}
},
[&](const ast::AccessorExpression* e) {
// obj.member
// Propagate the predication from the object to this expression.
if (auto pred = predicates.Get(e->object)) {
predicates.Add(e, *pred);
}
},
[&](const ast::UnaryOpExpression* e) {
// Includes address-of, or indirection
// Propagate the predication from the inner expression to this expression.
if (auto pred = predicates.Get(e->expr)) {
predicates.Add(e, *pred);
}
},
[&](const ast::AssignmentStatement* s) {
if (auto pred = predicates.Get(s->lhs)) {
// Assignment target is predicated
// Replace statement with condition on the predicate
ctx.Replace(s, b.If(*pred, b.Block(ctx.Clone(s))));
}
},
[&](const ast::CompoundAssignmentStatement* s) {
if (auto pred = predicates.Get(s->lhs)) {
// Assignment expression is predicated
// Replace statement with condition on the predicate
ctx.Replace(s, b.If(*pred, b.Block(ctx.Clone(s))));
}
},
[&](const ast::IncrementDecrementStatement* s) {
if (auto pred = predicates.Get(s->lhs)) {
// Assignment expression is predicated
// Replace statement with condition on the predicate
ctx.Replace(s, b.If(*pred, b.Block(ctx.Clone(s))));
}
},
[&](const ast::CallExpression* e) {
if (auto* call = sem.Get<sem::Call>(e)) {
Switch(
call->Target(), //
[&](const sem::Builtin* builtin) {
// Calls to builtins may require robustness transformation.
// Inspect.
if (builtin->IsTexture()) {
switch (cfg.texture_action) {
case Action::kPredicate:
PredicateTextureBuiltin(call, builtin);
break;
case Action::kClamp:
ClampTextureBuiltin(call, builtin);
break;
case Action::kIgnore:
break;
}
} else {
MaybePredicateNonTextureBuiltin(call, builtin);
}
},
[&](const sem::Function* fn) {
// Calls to user function may require passing additional predicate
// arguments.
InsertPredicateArguments(call, fn);
});
}
});
// Check whether the node is an expression that:
// * Has a predicate
// * Is of a non-pointer or non-reference type
// If the above is true, then we need to predicate evaluation of this expression by
// replacing `expr` with `predicated_expr` and injecting the following above the
// expression's statement:
//
// var predicated_expr : expr_ty;
// if (predicate) {
// predicated_expr = expr;
// }
//
if (auto* expr = node->As<ast::Expression>()) {
if (auto pred = predicates.Get(expr)) {
// Expression is predicated
auto* sem_expr = sem.GetVal(expr);
if (!sem_expr->Type()->IsAnyOf<type::Reference, type::Pointer>()) {
auto pred_load = b.Symbols().New("predicated_expr");
auto ty = CreateASTTypeFor(ctx, sem_expr->Type());
hoist.InsertBefore(sem_expr->Stmt(), b.Decl(b.Var(pred_load, ty)));
hoist.InsertBefore(
sem_expr->Stmt(),
b.If(*pred, b.Block(b.Assign(pred_load, ctx.Clone(expr)))));
ctx.Replace(expr, b.Expr(pred_load));
// The predication has been consumed for this expression.
// Don't predicate expressions that use this expression.
predicates.Remove(expr);
}
}
}
}
ctx.Clone();
return Program(std::move(b));
@ -54,227 +190,486 @@ struct Robustness::State {
private:
/// The source program
const Program* const src;
/// The transform's config
Config cfg;
/// The target program builder
ProgramBuilder b;
ProgramBuilder b{};
/// The clone context
CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
/// Helper for hoisting declarations
HoistToDeclBefore hoist{ctx};
/// Alias to the source program's semantic info
const sem::Info& sem = ctx.src->Sem();
/// Map of expression to predicate condition
utils::Hashmap<const ast::Expression*, Symbol, 32> predicates{};
/// Set of address spaces to not apply the transform to
std::unordered_set<builtin::AddressSpace> omitted_address_spaces;
/// Apply bounds clamping to array, vector and matrix indexing
/// @param expr the array, vector or matrix index expression
/// @return the clamped replacement expression, or nullptr if `expr` should be cloned without
/// changes.
const ast::IndexAccessorExpression* Transform(const ast::IndexAccessorExpression* expr) {
auto* sem = src->Sem().Get(expr)->Unwrap()->As<sem::IndexAccessorExpression>();
auto* ret_type = sem->Type();
auto* ref = ret_type->As<type::Reference>();
if (ref && omitted_address_spaces.count(ref->AddressSpace()) != 0) {
return nullptr;
}
// idx return the cloned index expression, as a u32.
auto idx = [&]() -> const ast::Expression* {
auto* i = ctx.Clone(expr->index);
if (sem->Index()->Type()->is_signed_integer_scalar()) {
return b.Call<u32>(i); // u32(idx)
}
return i;
};
auto* clamped_idx = Switch(
sem->Object()->Type()->UnwrapRef(), //
/// @return the `u32` typed expression that represents the maximum indexable value for the index
/// accessor @p expr, or nullptr if there is no robustness limit for this expression.
const ast::Expression* DynamicLimitFor(const sem::IndexAccessorExpression* expr) {
auto* obj_type = expr->Object()->Type();
return Switch(
obj_type->UnwrapRef(), //
[&](const type::Vector* vec) -> const ast::Expression* {
if (sem->Index()->ConstantValue()) {
if (expr->Index()->ConstantValue() || expr->Index()->Is<sem::Swizzle>()) {
// Index and size is constant.
// Validation will have rejected any OOB accesses.
return nullptr;
}
return b.Call("min", idx(), u32(vec->Width() - 1u));
return b.Expr(u32(vec->Width() - 1u));
},
[&](const type::Matrix* mat) -> const ast::Expression* {
if (sem->Index()->ConstantValue()) {
if (expr->Index()->ConstantValue()) {
// Index and size is constant.
// Validation will have rejected any OOB accesses.
return nullptr;
}
return b.Call("min", idx(), u32(mat->columns() - 1u));
return b.Expr(u32(mat->columns() - 1u));
},
[&](const type::Array* arr) -> const ast::Expression* {
const ast::Expression* max = nullptr;
if (arr->Count()->Is<type::RuntimeArrayCount>()) {
// Size is unknown until runtime.
// Must clamp, even if the index is constant.
auto* arr_ptr = b.AddressOf(ctx.Clone(expr->object));
max = b.Sub(b.Call("arrayLength", arr_ptr), 1_u);
} else if (auto count = arr->ConstantCount()) {
if (sem->Index()->ConstantValue()) {
auto* arr_ptr = b.AddressOf(ctx.Clone(expr->Object()->Declaration()));
return b.Sub(b.Call(sem::BuiltinType::kArrayLength, arr_ptr), 1_u);
}
if (auto count = arr->ConstantCount()) {
if (expr->Index()->ConstantValue()) {
// Index and size is constant.
// Validation will have rejected any OOB accesses.
return nullptr;
}
max = b.Expr(u32(count.value() - 1u));
} else {
// Note: Don't be tempted to use the array override variable as an expression
// here, the name might be shadowed!
b.Diagnostics().add_error(diag::System::Transform,
type::Array::kErrExpectedConstantCount);
return nullptr;
return b.Expr(u32(count.value() - 1u));
}
return b.Call("min", idx(), max);
// Note: Don't be tempted to use the array override variable as an expression here,
// the name might be shadowed!
b.Diagnostics().add_error(diag::System::Transform,
type::Array::kErrExpectedConstantCount);
return nullptr;
},
[&](Default) {
[&](Default) -> const ast::Expression* {
TINT_ICE(Transform, b.Diagnostics())
<< "unhandled object type in robustness of array index: "
<< src->FriendlyName(ret_type->UnwrapRef());
<< src->FriendlyName(obj_type->UnwrapRef());
return nullptr;
});
if (!clamped_idx) {
return nullptr; // Clamping not needed
}
auto idx_src = ctx.Clone(expr->source);
auto* idx_obj = ctx.Clone(expr->object);
return b.IndexAccessor(idx_src, idx_obj, clamped_idx);
}
/// @param type builtin type
/// @returns true if the given builtin is a texture function that requires
/// argument clamping,
bool TextureBuiltinNeedsClamping(sem::BuiltinType type) {
return type == sem::BuiltinType::kTextureLoad || type == sem::BuiltinType::kTextureStore;
/// Transform the program to insert additional predicate parameters to all user functions that
/// have a pointer parameter type in an address space that has predicate action.
void AddPredicateParameters() {
for (auto* fn : src->AST().Functions()) {
for (auto* param : fn->params) {
auto* sem_param = sem.Get(param);
if (auto* ptr = sem_param->Type()->As<type::Pointer>()) {
if (ActionFor(ptr->AddressSpace()) == Action::kPredicate) {
auto name = b.Symbols().New(src->Symbols().NameFor(param->name->symbol) +
"_predicate");
ctx.InsertAfter(fn->params, param, b.Param(name, b.ty.bool_()));
// Associate the pointer parameter expressions with the predicate.
for (auto* user : sem_param->Users()) {
predicates.Add(user->Declaration(), name);
}
}
}
}
}
}
/// Apply bounds clamping to the coordinates, array index and level arguments
/// of the `textureLoad()` and `textureStore()` builtins.
/// @param expr the builtin call expression
/// @return the clamped replacement call expression, or nullptr if `expr`
/// should be cloned without changes.
const ast::CallExpression* Transform(const ast::CallExpression* expr) {
auto* call = src->Sem().Get(expr)->UnwrapMaterialize()->As<sem::Call>();
auto* call_target = call->Target();
auto* builtin = call_target->As<sem::Builtin>();
if (!builtin || !TextureBuiltinNeedsClamping(builtin->Type())) {
return nullptr; // No transform, just clone.
/// Transforms call expressions to user functions, inserting additional predicate arguments
/// after all pointer parameters with a type in an address space that has predicate action.
void InsertPredicateArguments(const sem::Call* call, const sem::Function* fn) {
auto* expr = call->Declaration();
for (size_t i = 0; i < fn->Parameters().Length(); i++) {
auto* param = fn->Parameters()[i];
if (auto* ptr = param->Type()->As<type::Pointer>()) {
if (ActionFor(ptr->AddressSpace()) == Action::kPredicate) {
auto* arg = expr->args[i];
if (auto predicate = predicates.Get(arg)) {
ctx.InsertAfter(expr->args, arg, b.Expr(*predicate));
} else {
ctx.InsertAfter(expr->args, arg, b.Expr(true));
}
}
}
}
}
/// Applies predication to the index on an array, vector or matrix.
/// @param expr the index accessor expression.
void PredicateIndexAccessor(const sem::IndexAccessorExpression* expr) {
auto* obj = expr->Object()->Declaration();
auto* idx = expr->Index()->Declaration();
auto* max = DynamicLimitFor(expr);
if (!max) {
// robustness is not required
// Just propagate predicate from object
if (auto pred = predicates.Get(obj)) {
predicates.Add(expr->Declaration(), *pred);
}
return;
}
auto* stmt = expr->Stmt();
auto obj_pred = *predicates.GetOrZero(obj);
auto idx_let = b.Symbols().New("index");
auto pred = b.Symbols().New("predicate");
hoist.InsertBefore(stmt, b.Decl(b.Let(idx_let, ctx.Clone(idx))));
ctx.Replace(idx, b.Expr(idx_let));
auto* cond = b.LessThanEqual(b.Call<u32>(b.Expr(idx_let)), max);
if (obj_pred.IsValid()) {
cond = b.And(b.Expr(obj_pred), cond);
}
hoist.InsertBefore(stmt, b.Decl(b.Let(pred, cond)));
predicates.Add(expr->Declaration(), pred);
}
/// Applies bounds clamping to the index on an array, vector or matrix.
/// @param expr the index accessor expression.
void ClampIndexAccessor(const sem::IndexAccessorExpression* expr) {
auto* max = DynamicLimitFor(expr);
if (!max) {
return; // robustness is not required
}
auto* expr_sem = expr->Unwrap()->As<sem::IndexAccessorExpression>();
auto idx = ctx.Clone(expr->Declaration()->index);
if (expr_sem->Index()->Type()->is_signed_integer_scalar()) {
idx = b.Call<u32>(idx); // u32(idx)
}
auto* clamped_idx = b.Call(sem::BuiltinType::kMin, idx, max);
ctx.Replace(expr->Declaration()->index, clamped_idx);
}
/// Applies predication to the non-texture builtin call, if required.
void MaybePredicateNonTextureBuiltin(const sem::Call* call, const sem::Builtin* builtin) {
// Gather the predications for the builtin arguments
const ast::Expression* predicate = nullptr;
for (auto* arg : call->Declaration()->args) {
if (auto pred = predicates.Get(arg)) {
predicate = And(predicate, b.Expr(*pred));
}
}
if (predicate) {
if (builtin->Type() == sem::BuiltinType::kWorkgroupUniformLoad) {
// https://www.w3.org/TR/WGSL/#workgroupUniformLoad-builtin:
// "Executes a control barrier synchronization function that affects memory and
// atomic operations in the workgroup address space."
// Because the call acts like a control barrier, we need to make sure that we still
// trigger a workgroup barrier if the predicate fails.
PredicateCall(call, predicate,
b.Block(b.CallStmt(b.Call(sem::BuiltinType::kWorkgroupBarrier))));
} else {
PredicateCall(call, predicate);
}
}
}
/// Applies predication to texture builtins, based on whether the coordinates, array index and
/// level arguments are all in bounds.
void PredicateTextureBuiltin(const sem::Call* call, const sem::Builtin* builtin) {
if (!TextureBuiltinNeedsRobustness(builtin->Type())) {
return;
}
auto* expr = call->Declaration();
auto* stmt = call->Stmt();
// Indices of the mandatory texture and coords parameters, and the optional
// array and level parameters.
auto& signature = builtin->Signature();
auto texture_idx = signature.IndexOf(sem::ParameterUsage::kTexture);
auto coords_idx = signature.IndexOf(sem::ParameterUsage::kCoords);
auto array_idx = signature.IndexOf(sem::ParameterUsage::kArrayIndex);
auto level_idx = signature.IndexOf(sem::ParameterUsage::kLevel);
auto texture_arg_idx = signature.IndexOf(sem::ParameterUsage::kTexture);
auto coords_arg_idx = signature.IndexOf(sem::ParameterUsage::kCoords);
auto array_arg_idx = signature.IndexOf(sem::ParameterUsage::kArrayIndex);
auto level_arg_idx = signature.IndexOf(sem::ParameterUsage::kLevel);
auto* texture_arg = expr->args[static_cast<size_t>(texture_idx)];
auto* coords_arg = expr->args[static_cast<size_t>(coords_idx)];
auto* coords_ty = builtin->Parameters()[static_cast<size_t>(coords_idx)]->Type();
auto* texture_arg = expr->args[static_cast<size_t>(texture_arg_idx)];
auto width_of = [&](const type::Type* ty) {
if (auto* vec = ty->As<type::Vector>()) {
return vec->Width();
// Build the builtin predicate from the arguments
const ast::Expression* predicate = nullptr;
Symbol level_idx, num_levels;
if (level_arg_idx >= 0) {
auto* param = builtin->Parameters()[static_cast<size_t>(level_arg_idx)];
if (param->Type()->is_integer_scalar()) {
// let level_idx = u32(level-arg);
level_idx = b.Symbols().New("level_idx");
auto* arg = expr->args[static_cast<size_t>(level_arg_idx)];
hoist.InsertBefore(stmt,
b.Decl(b.Let(level_idx, CastToUnsigned(ctx.Clone(arg), 1u))));
// let num_levels = textureNumLevels(texture-arg);
num_levels = b.Symbols().New("num_levels");
hoist.InsertBefore(
stmt, b.Decl(b.Let(num_levels, b.Call(sem::BuiltinType::kTextureNumLevels,
ctx.Clone(texture_arg)))));
// predicate: level_idx < num_levels
predicate = And(predicate, b.LessThan(level_idx, num_levels));
// Replace the level argument with `level_idx`
ctx.Replace(arg, b.Expr(level_idx));
}
return 1u;
};
auto scalar_or_vec_ty = [&](ast::Type scalar, uint32_t width) {
if (width > 1) {
return b.ty.vec(scalar, width);
}
Symbol coords;
if (coords_arg_idx >= 0) {
auto* param = builtin->Parameters()[static_cast<size_t>(coords_arg_idx)];
if (param->Type()->is_integer_scalar_or_vector()) {
// let coords = u32(coords-arg)
coords = b.Symbols().New("coords");
auto* arg = expr->args[static_cast<size_t>(coords_arg_idx)];
hoist.InsertBefore(stmt,
b.Decl(b.Let(coords, CastToUnsigned(b.Expr(ctx.Clone(arg)),
WidthOf(param->Type())))));
// predicate: all(coords < textureDimensions(texture))
auto* dimensions =
level_idx.IsValid()
? b.Call(sem::BuiltinType::kTextureDimensions, ctx.Clone(texture_arg),
b.Call(sem::BuiltinType::kMin, b.Expr(level_idx),
b.Sub(num_levels, 1_a)))
: b.Call(sem::BuiltinType::kTextureDimensions, ctx.Clone(texture_arg));
predicate =
And(predicate, b.Call(sem::BuiltinType::kAll, b.LessThan(coords, dimensions)));
// Replace the level argument with `coord`
ctx.Replace(arg, b.Expr(coords));
}
return scalar;
};
auto scalar_or_vec = [&](const ast::Expression* scalar,
uint32_t width) -> const ast::Expression* {
if (width > 1) {
return b.Call(b.ty.vec<Infer>(width), scalar);
}
if (array_arg_idx >= 0) {
// let array_idx = u32(array-arg)
auto* arg = expr->args[static_cast<size_t>(array_arg_idx)];
auto* num_layers = b.Call(sem::BuiltinType::kTextureNumLayers, ctx.Clone(texture_arg));
auto array_idx = b.Symbols().New("array_idx");
hoist.InsertBefore(stmt, b.Decl(b.Let(array_idx, CastToUnsigned(ctx.Clone(arg), 1u))));
// predicate: array_idx < textureNumLayers(texture)
predicate = And(predicate, b.LessThan(array_idx, num_layers));
// Replace the array index argument with `array_idx`
ctx.Replace(arg, b.Expr(array_idx));
}
if (predicate) {
PredicateCall(call, predicate);
}
}
/// Applies bounds clamping to the coordinates, array index and level arguments of the texture
/// builtin.
void ClampTextureBuiltin(const sem::Call* call, const sem::Builtin* builtin) {
if (!TextureBuiltinNeedsRobustness(builtin->Type())) {
return;
}
auto* expr = call->Declaration();
auto* stmt = call->Stmt();
// Indices of the mandatory texture and coords parameters, and the optional
// array and level parameters.
auto& signature = builtin->Signature();
auto texture_arg_idx = signature.IndexOf(sem::ParameterUsage::kTexture);
auto coords_arg_idx = signature.IndexOf(sem::ParameterUsage::kCoords);
auto array_arg_idx = signature.IndexOf(sem::ParameterUsage::kArrayIndex);
auto level_arg_idx = signature.IndexOf(sem::ParameterUsage::kLevel);
auto* texture_arg = expr->args[static_cast<size_t>(texture_arg_idx)];
// If the level is provided, then we need to clamp this. As the level is used by
// textureDimensions() and the texture[Load|Store]() calls, we need to clamp both usages.
Symbol level_idx;
if (level_arg_idx >= 0) {
const auto* param = builtin->Parameters()[static_cast<size_t>(level_arg_idx)];
if (param->Type()->is_integer_scalar()) {
const auto* arg = expr->args[static_cast<size_t>(level_arg_idx)];
level_idx = b.Symbols().New("level_idx");
const auto* num_levels =
b.Call(sem::BuiltinType::kTextureNumLevels, ctx.Clone(texture_arg));
const auto* max = b.Sub(num_levels, 1_a);
hoist.InsertBefore(
stmt, b.Decl(b.Let(level_idx, b.Call(sem::BuiltinType::kMin,
b.Call<u32>(ctx.Clone(arg)), max))));
ctx.Replace(arg, b.Expr(level_idx));
}
return scalar;
};
auto cast_to_signed = [&](const ast::Expression* val, uint32_t width) {
return b.Call(scalar_or_vec_ty(b.ty.i32(), width), val);
};
auto cast_to_unsigned = [&](const ast::Expression* val, uint32_t width) {
return b.Call(scalar_or_vec_ty(b.ty.u32(), width), val);
};
// If the level is provided, then we need to clamp this. As the level is
// used by textureDimensions() and the texture[Load|Store]() calls, we need
// to clamp both usages.
// TODO(bclayton): We probably want to place this into a let so that the
// calculation can be reused. This is fiddly to get right.
std::function<const ast::Expression*()> level_arg;
if (level_idx >= 0) {
level_arg = [&] {
const auto* arg = expr->args[static_cast<size_t>(level_idx)];
const auto* target_ty =
builtin->Parameters()[static_cast<size_t>(level_idx)]->Type();
const auto* num_levels = b.Call("textureNumLevels", ctx.Clone(texture_arg));
// TODO(crbug.com/tint/1526) remove when num_levels returns u32
num_levels = cast_to_unsigned(num_levels, 1u);
const auto* unsigned_max = b.Sub(num_levels, 1_a);
if (target_ty->is_signed_integer_scalar()) {
const auto* signed_max = cast_to_signed(unsigned_max, 1u);
return b.Call("clamp", ctx.Clone(arg), 0_a, signed_max);
} else {
return b.Call("min", ctx.Clone(arg), unsigned_max);
}
};
}
// Clamp the coordinates argument
{
const auto* target_ty = coords_ty;
const auto width = width_of(target_ty);
const auto* texture_dims =
level_arg ? b.Call("textureDimensions", ctx.Clone(texture_arg), level_arg())
: b.Call("textureDimensions", ctx.Clone(texture_arg));
if (coords_arg_idx >= 0) {
const auto* param = builtin->Parameters()[static_cast<size_t>(coords_arg_idx)];
if (param->Type()->is_integer_scalar_or_vector()) {
auto* arg = expr->args[static_cast<size_t>(coords_arg_idx)];
const auto width = WidthOf(param->Type());
const auto* dimensions =
level_idx.IsValid()
? b.Call(sem::BuiltinType::kTextureDimensions, ctx.Clone(texture_arg),
level_idx)
: b.Call(sem::BuiltinType::kTextureDimensions, ctx.Clone(texture_arg));
// TODO(crbug.com/tint/1526) remove when texture_dims returns u32 or vecN<u32>
texture_dims = cast_to_unsigned(texture_dims, width);
// texture_dims is u32 or vecN<u32>
const auto* unsigned_max = b.Sub(texture_dims, scalar_or_vec(b.Expr(1_a), width));
if (target_ty->is_signed_integer_scalar_or_vector()) {
const auto* zero = scalar_or_vec(b.Expr(0_a), width);
const auto* signed_max = cast_to_signed(unsigned_max, width);
ctx.Replace(coords_arg, b.Call("clamp", ctx.Clone(coords_arg), zero, signed_max));
} else {
ctx.Replace(coords_arg, b.Call("min", ctx.Clone(coords_arg), unsigned_max));
// dimensions is u32 or vecN<u32>
const auto* unsigned_max = b.Sub(dimensions, ScalarOrVec(b.Expr(1_a), width));
if (param->Type()->is_signed_integer_scalar_or_vector()) {
const auto* zero = ScalarOrVec(b.Expr(0_a), width);
const auto* signed_max = CastToSigned(unsigned_max, width);
ctx.Replace(arg,
b.Call(sem::BuiltinType::kClamp, ctx.Clone(arg), zero, signed_max));
} else {
ctx.Replace(arg, b.Call(sem::BuiltinType::kMin, ctx.Clone(arg), unsigned_max));
}
}
}
// Clamp the array_index argument, if provided
if (array_idx >= 0) {
auto* target_ty = builtin->Parameters()[static_cast<size_t>(array_idx)]->Type();
auto* arg = expr->args[static_cast<size_t>(array_idx)];
auto* num_layers = b.Call("textureNumLayers", ctx.Clone(texture_arg));
// TODO(crbug.com/tint/1526) remove when num_layers returns u32
num_layers = cast_to_unsigned(num_layers, 1u);
if (array_arg_idx >= 0) {
auto* param = builtin->Parameters()[static_cast<size_t>(array_arg_idx)];
auto* arg = expr->args[static_cast<size_t>(array_arg_idx)];
auto* num_layers = b.Call(sem::BuiltinType::kTextureNumLayers, ctx.Clone(texture_arg));
const auto* unsigned_max = b.Sub(num_layers, 1_a);
if (target_ty->is_signed_integer_scalar()) {
const auto* signed_max = cast_to_signed(unsigned_max, 1u);
ctx.Replace(arg, b.Call("clamp", ctx.Clone(arg), 0_a, signed_max));
if (param->Type()->is_signed_integer_scalar()) {
const auto* signed_max = CastToSigned(unsigned_max, 1u);
ctx.Replace(arg, b.Call(sem::BuiltinType::kClamp, ctx.Clone(arg), 0_a, signed_max));
} else {
ctx.Replace(arg, b.Call("min", ctx.Clone(arg), unsigned_max));
ctx.Replace(arg, b.Call(sem::BuiltinType::kMin, ctx.Clone(arg), unsigned_max));
}
}
}
// Clamp the level argument, if provided
if (level_idx >= 0) {
auto* arg = expr->args[static_cast<size_t>(level_idx)];
ctx.Replace(arg, level_arg ? level_arg() : b.Expr(0_a));
/// @param type builtin type
/// @returns true if the given builtin is a texture function that requires predication or
/// clamping of arguments.
bool TextureBuiltinNeedsRobustness(sem::BuiltinType type) {
return type == sem::BuiltinType::kTextureLoad || type == sem::BuiltinType::kTextureStore ||
type == sem::BuiltinType::kTextureDimensions;
}
/// @returns a bitwise and of the two expressions, or the other expression if one is null.
const ast::Expression* And(const ast::Expression* lhs, const ast::Expression* rhs) {
if (lhs && rhs) {
return b.And(lhs, rhs);
}
if (lhs) {
return lhs;
}
return rhs;
}
return nullptr; // Clone, which will use the argument replacements above.
/// Transforms a call statement or expression so that the expression is predicated by @p
/// predicate.
/// @param else_stmt - the statement to execute for the predication failure
void PredicateCall(const sem::Call* call,
const ast::Expression* predicate,
const ast::BlockStatement* else_stmt = nullptr) {
auto* expr = call->Declaration();
auto* stmt = call->Stmt();
auto* call_stmt = stmt->Declaration()->As<ast::CallStatement>();
if (call_stmt && call_stmt->expr == expr) {
// Wrap the statement in an if-statement with the predicate condition.
hoist.Replace(stmt, b.If(predicate, b.Block(ctx.Clone(stmt->Declaration())),
ProgramBuilder::ElseStmt(else_stmt)));
} else {
// Emit the following before the expression's statement:
// var predicated_value : return-type;
// if (predicate) {
// predicated_value = call(...);
// }
auto value = b.Symbols().New("predicated_value");
hoist.InsertBefore(stmt, b.Decl(b.Var(value, CreateASTTypeFor(ctx, call->Type()))));
hoist.InsertBefore(stmt, b.If(predicate, b.Block(b.Assign(value, ctx.Clone(expr))),
ProgramBuilder::ElseStmt(else_stmt)));
// Replace the call expression with `predicated_value`
ctx.Replace(expr, b.Expr(value));
}
}
/// @returns true if @p action is enabled for any address space
bool HasAction(Action action) const {
return action == cfg.function_action || //
action == cfg.texture_action || //
action == cfg.private_action || //
action == cfg.push_constant_action || //
action == cfg.storage_action || //
action == cfg.uniform_action || //
action == cfg.workgroup_action;
}
/// @returns the robustness action to perform for an OOB access with the expression @p expr
Action ActionFor(const sem::ValueExpression* expr) {
return Switch(
expr->Type(), //
[&](const type::Reference* t) { return ActionFor(t->AddressSpace()); },
[&](Default) { return cfg.value_action; });
}
/// @returns the robustness action to perform for an OOB access in the address space @p
/// address_space
Action ActionFor(builtin::AddressSpace address_space) {
switch (address_space) {
case builtin::AddressSpace::kFunction:
return cfg.function_action;
case builtin::AddressSpace::kHandle:
return cfg.texture_action;
case builtin::AddressSpace::kPrivate:
return cfg.private_action;
case builtin::AddressSpace::kPushConstant:
return cfg.push_constant_action;
case builtin::AddressSpace::kStorage:
return cfg.storage_action;
case builtin::AddressSpace::kUniform:
return cfg.uniform_action;
case builtin::AddressSpace::kWorkgroup:
return cfg.workgroup_action;
default:
break;
}
TINT_UNREACHABLE(Transform, b.Diagnostics()) << "unhandled address space" << address_space;
return Action::kDefault;
}
/// @returns the vector width of @p ty, or 1 if @p ty is not a vector
static uint32_t WidthOf(const type::Type* ty) {
if (auto* vec = ty->As<type::Vector>()) {
return vec->Width();
}
return 1u;
}
/// @returns a scalar or vector type with the element type @p scalar and width @p width
ast::Type ScalarOrVecTy(ast::Type scalar, uint32_t width) const {
if (width > 1) {
return b.ty.vec(scalar, width);
}
return scalar;
}
/// @returns a vector constructed with the scalar expression @p scalar if @p width > 1,
/// otherwise returns @p scalar.
const ast::Expression* ScalarOrVec(const ast::Expression* scalar, uint32_t width) {
if (width > 1) {
return b.Call(b.ty.vec<Infer>(width), scalar);
}
return scalar;
}
/// @returns @p val cast to a `vecN<i32>`, where `N` is @p width, or cast to i32 if @p width
/// is 1.
const ast::CallExpression* CastToSigned(const ast::Expression* val, uint32_t width) {
return b.Call(ScalarOrVecTy(b.ty.i32(), width), val);
}
/// @returns @p val cast to a `vecN<u32>`, where `N` is @p width, or cast to u32 if @p width
/// is 1.
const ast::CallExpression* CastToUnsigned(const ast::Expression* val, uint32_t width) {
return b.Call(ScalarOrVecTy(b.ty.u32(), width), val);
}
};
@ -294,19 +689,7 @@ Transform::ApplyResult Robustness::Apply(const Program* src,
cfg = *cfg_data;
}
std::unordered_set<builtin::AddressSpace> omitted_address_spaces;
for (auto sc : cfg.omitted_address_spaces) {
switch (sc) {
case AddressSpace::kUniform:
omitted_address_spaces.insert(builtin::AddressSpace::kUniform);
break;
case AddressSpace::kStorage:
omitted_address_spaces.insert(builtin::AddressSpace::kStorage);
break;
}
}
return State{src, std::move(omitted_address_spaces)}.Run();
return State{src, std::move(cfg)}.Run();
}
} // namespace tint::transform

View File

@ -15,29 +15,33 @@
#ifndef SRC_TINT_TRANSFORM_ROBUSTNESS_H_
#define SRC_TINT_TRANSFORM_ROBUSTNESS_H_
#include <unordered_set>
#include "src/tint/transform/transform.h"
// Forward declarations
namespace tint::ast {
class IndexAccessorExpression;
class CallExpression;
} // namespace tint::ast
namespace tint::transform {
/// This transform is responsible for clamping all array accesses to be within
/// the bounds of the array. Any access before the start of the array will clamp
/// to zero and any access past the end of the array will clamp to
/// (array length - 1).
/// @note This transform must come before the BuiltinPolyfill transform
/// This transform is responsible for ensuring that all out of bounds accesses are prevented,
/// either by conditioning the access (predication) or through clamping of the index to keep the
/// access in bounds.
/// @note Robustness must come after:
/// * PromoteSideEffectsToDecl as Robustness requires side-effecting expressions to be hoisted
/// to their own statements.
/// Robustness must come before:
/// * BuiltinPolyfill as 'clamp' and binary operators may need to be polyfilled.
/// * CanonicalizeEntryPointIO as the transform does not support the 'in' and 'out' address
/// spaces.
class Robustness final : public Castable<Robustness, Transform> {
public:
/// Address space to be skipped in the transform
enum class AddressSpace {
kUniform,
kStorage,
/// Robustness action for out-of-bounds indexing.
enum class Action {
/// Do nothing to prevent the out-of-bounds action.
kIgnore,
/// Clamp the index to be within bounds.
kClamp,
/// Do not execute the read or write if the index is out-of-bounds.
kPredicate,
/// The default action
kDefault = kClamp,
};
/// Configuration options for the transform
@ -55,9 +59,24 @@ class Robustness final : public Castable<Robustness, Transform> {
/// @returns this Config
Config& operator=(const Config&);
/// Address spaces to omit from apply the transform to.
/// This allows for optimizing on hardware that provide safe accesses.
std::unordered_set<AddressSpace> omitted_address_spaces;
/// Robustness action for values
Action value_action = Action::kDefault;
/// Robustness action for non-sampling texture operations
Action texture_action = Action::kDefault;
/// Robustness action for variables in the 'function' address space
Action function_action = Action::kDefault;
/// Robustness action for variables in the 'private' address space
Action private_action = Action::kDefault;
/// Robustness action for variables in the 'push_constant' address space
Action push_constant_action = Action::kDefault;
/// Robustness action for variables in the 'storage' address space
Action storage_action = Action::kDefault;
/// Robustness action for variables in the 'uniform' address space
Action uniform_action = Action::kDefault;
/// Robustness action for variables in the 'workgroup' address space
Action workgroup_action = Action::kDefault;
};
/// Constructor

File diff suppressed because it is too large Load Diff

View File

@ -166,7 +166,8 @@ SanitizedResult Sanitize(const Program* in,
manager.Add<transform::PromoteSideEffectsToDecl>();
if (!options.disable_robustness) {
// Robustness must come before BuiltinPolyfill
// Robustness must come after PromoteSideEffectsToDecl
// Robustness must come before BuiltinPolyfill and CanonicalizeEntryPointIO
manager.Add<transform::Robustness>();
}

View File

@ -182,7 +182,8 @@ SanitizedResult Sanitize(const Program* in, const Options& options) {
manager.Add<transform::PromoteSideEffectsToDecl>();
if (!options.disable_robustness) {
// Robustness must come before BuiltinPolyfill
// Robustness must come after PromoteSideEffectsToDecl
// Robustness must come before BuiltinPolyfill and CanonicalizeEntryPointIO
manager.Add<transform::Robustness>();
}

View File

@ -207,7 +207,8 @@ SanitizedResult Sanitize(const Program* in, const Options& options) {
manager.Add<transform::PromoteSideEffectsToDecl>();
if (!options.disable_robustness) {
// Robustness must come before BuiltinPolyfill
// Robustness must come after PromoteSideEffectsToDecl
// Robustness must come before BuiltinPolyfill and CanonicalizeEntryPointIO
manager.Add<transform::Robustness>();
}

View File

@ -68,7 +68,8 @@ SanitizedResult Sanitize(const Program* in, const Options& options) {
manager.Add<transform::MergeReturn>();
if (!options.disable_robustness) {
// Robustness must come before BuiltinPolyfill
// Robustness must come after PromoteSideEffectsToDecl
// Robustness must come before BuiltinPolyfill and CanonicalizeEntryPointIO
manager.Add<transform::Robustness>();
}

View File

@ -1,8 +1,4 @@
int tint_clamp(int e, int low, int high) {
return min(max(e, low), high);
}
int2 tint_clamp_1(int2 e, int2 low, int2 high) {
int2 tint_clamp(int2 e, int2 low, int2 high) {
return min(max(e, low), high);
}
@ -45,26 +41,23 @@ float4 textureLoadExternal(Texture2D<float4> plane0, Texture2D<float4> plane1, i
float3 color = float3(0.0f, 0.0f, 0.0f);
if ((params.numPlanes == 1u)) {
int3 tint_tmp;
plane0.GetDimensions(0, tint_tmp.x, tint_tmp.y, tint_tmp.z);
const uint level_idx = min(0u, (tint_tmp.z - 1u));
int3 tint_tmp_1;
plane0.GetDimensions(0, tint_tmp_1.x, tint_tmp_1.y, tint_tmp_1.z);
plane0.GetDimensions(tint_clamp(0, 0, int((uint(tint_tmp_1.z) - 1u))), tint_tmp.x, tint_tmp.y, tint_tmp.z);
plane0.GetDimensions(level_idx, tint_tmp_1.x, tint_tmp_1.y, tint_tmp_1.z);
color = plane0.Load(int3(tint_clamp(coord, (0).xx, int2((tint_tmp_1.xy - (1u).xx))), int(level_idx))).rgb;
} else {
int3 tint_tmp_2;
plane0.GetDimensions(0, tint_tmp_2.x, tint_tmp_2.y, tint_tmp_2.z);
color = plane0.Load(int3(tint_clamp_1(coord, (0).xx, int2((uint2(tint_tmp.xy) - (1u).xx))), tint_clamp(0, 0, int((uint(tint_tmp_2.z) - 1u))))).rgb;
} else {
const uint level_idx_1 = min(0u, (tint_tmp_2.z - 1u));
int3 tint_tmp_3;
plane1.GetDimensions(0, tint_tmp_3.x, tint_tmp_3.y, tint_tmp_3.z);
const uint level_idx_2 = min(0u, (tint_tmp_3.z - 1u));
int3 tint_tmp_4;
plane0.GetDimensions(0, tint_tmp_4.x, tint_tmp_4.y, tint_tmp_4.z);
plane0.GetDimensions(tint_clamp(0, 0, int((uint(tint_tmp_4.z) - 1u))), tint_tmp_3.x, tint_tmp_3.y, tint_tmp_3.z);
plane0.GetDimensions(level_idx_1, tint_tmp_4.x, tint_tmp_4.y, tint_tmp_4.z);
int3 tint_tmp_5;
plane0.GetDimensions(0, tint_tmp_5.x, tint_tmp_5.y, tint_tmp_5.z);
int3 tint_tmp_6;
int3 tint_tmp_7;
plane1.GetDimensions(0, tint_tmp_7.x, tint_tmp_7.y, tint_tmp_7.z);
plane1.GetDimensions(tint_clamp(0, 0, int((uint(tint_tmp_7.z) - 1u))), tint_tmp_6.x, tint_tmp_6.y, tint_tmp_6.z);
int3 tint_tmp_8;
plane1.GetDimensions(0, tint_tmp_8.x, tint_tmp_8.y, tint_tmp_8.z);
color = mul(params.yuvToRgbConversionMatrix, float4(plane0.Load(int3(tint_clamp_1(coord, (0).xx, int2((uint2(tint_tmp_3.xy) - (1u).xx))), tint_clamp(0, 0, int((uint(tint_tmp_5.z) - 1u))))).r, plane1.Load(int3(tint_clamp_1(coord1, (0).xx, int2((uint2(tint_tmp_6.xy) - (1u).xx))), tint_clamp(0, 0, int((uint(tint_tmp_8.z) - 1u))))).rg, 1.0f));
plane1.GetDimensions(level_idx_2, tint_tmp_5.x, tint_tmp_5.y, tint_tmp_5.z);
color = mul(params.yuvToRgbConversionMatrix, float4(plane0.Load(int3(tint_clamp(coord, (0).xx, int2((tint_tmp_4.xy - (1u).xx))), int(level_idx_1))).r, plane1.Load(int3(tint_clamp(coord1, (0).xx, int2((tint_tmp_5.xy - (1u).xx))), int(level_idx_2))).rg, 1.0f));
}
if ((params.doYuvToRgbConversionOnly == 0u)) {
color = gammaCorrection(color, params.gammaDecodeParams);
@ -121,12 +114,12 @@ ExternalTextureParams ext_tex_params_load(uint offset) {
[numthreads(1, 1, 1)]
void main() {
float4 red = textureLoadExternal(t, ext_tex_plane_1, (10).xx, ext_tex_params_load(0u));
int2 tint_tmp_9;
outImage.GetDimensions(tint_tmp_9.x, tint_tmp_9.y);
outImage[tint_clamp_1((0).xx, (0).xx, int2((uint2(tint_tmp_9) - (1u).xx)))] = red;
int2 tint_tmp_6;
outImage.GetDimensions(tint_tmp_6.x, tint_tmp_6.y);
outImage[tint_clamp((0).xx, (0).xx, int2((tint_tmp_6 - (1u).xx)))] = red;
float4 green = textureLoadExternal(t, ext_tex_plane_1, int2(70, 118), ext_tex_params_load(0u));
int2 tint_tmp_10;
outImage.GetDimensions(tint_tmp_10.x, tint_tmp_10.y);
outImage[tint_clamp_1(int2(1, 0), (0).xx, int2((uint2(tint_tmp_10) - (1u).xx)))] = green;
int2 tint_tmp_7;
outImage.GetDimensions(tint_tmp_7.x, tint_tmp_7.y);
outImage[tint_clamp(int2(1, 0), (0).xx, int2((tint_tmp_7 - (1u).xx)))] = green;
return;
}

View File

@ -1,8 +1,4 @@
int tint_clamp(int e, int low, int high) {
return min(max(e, low), high);
}
int2 tint_clamp_1(int2 e, int2 low, int2 high) {
int2 tint_clamp(int2 e, int2 low, int2 high) {
return min(max(e, low), high);
}
@ -45,26 +41,23 @@ float4 textureLoadExternal(Texture2D<float4> plane0, Texture2D<float4> plane1, i
float3 color = float3(0.0f, 0.0f, 0.0f);
if ((params.numPlanes == 1u)) {
int3 tint_tmp;
plane0.GetDimensions(0, tint_tmp.x, tint_tmp.y, tint_tmp.z);
const uint level_idx = min(0u, (tint_tmp.z - 1u));
int3 tint_tmp_1;
plane0.GetDimensions(0, tint_tmp_1.x, tint_tmp_1.y, tint_tmp_1.z);
plane0.GetDimensions(tint_clamp(0, 0, int((uint(tint_tmp_1.z) - 1u))), tint_tmp.x, tint_tmp.y, tint_tmp.z);
plane0.GetDimensions(level_idx, tint_tmp_1.x, tint_tmp_1.y, tint_tmp_1.z);
color = plane0.Load(int3(tint_clamp(coord, (0).xx, int2((tint_tmp_1.xy - (1u).xx))), int(level_idx))).rgb;
} else {
int3 tint_tmp_2;
plane0.GetDimensions(0, tint_tmp_2.x, tint_tmp_2.y, tint_tmp_2.z);
color = plane0.Load(int3(tint_clamp_1(coord, (0).xx, int2((uint2(tint_tmp.xy) - (1u).xx))), tint_clamp(0, 0, int((uint(tint_tmp_2.z) - 1u))))).rgb;
} else {
const uint level_idx_1 = min(0u, (tint_tmp_2.z - 1u));
int3 tint_tmp_3;
plane1.GetDimensions(0, tint_tmp_3.x, tint_tmp_3.y, tint_tmp_3.z);
const uint level_idx_2 = min(0u, (tint_tmp_3.z - 1u));
int3 tint_tmp_4;
plane0.GetDimensions(0, tint_tmp_4.x, tint_tmp_4.y, tint_tmp_4.z);
plane0.GetDimensions(tint_clamp(0, 0, int((uint(tint_tmp_4.z) - 1u))), tint_tmp_3.x, tint_tmp_3.y, tint_tmp_3.z);
plane0.GetDimensions(level_idx_1, tint_tmp_4.x, tint_tmp_4.y, tint_tmp_4.z);
int3 tint_tmp_5;
plane0.GetDimensions(0, tint_tmp_5.x, tint_tmp_5.y, tint_tmp_5.z);
int3 tint_tmp_6;
int3 tint_tmp_7;
plane1.GetDimensions(0, tint_tmp_7.x, tint_tmp_7.y, tint_tmp_7.z);
plane1.GetDimensions(tint_clamp(0, 0, int((uint(tint_tmp_7.z) - 1u))), tint_tmp_6.x, tint_tmp_6.y, tint_tmp_6.z);
int3 tint_tmp_8;
plane1.GetDimensions(0, tint_tmp_8.x, tint_tmp_8.y, tint_tmp_8.z);
color = mul(params.yuvToRgbConversionMatrix, float4(plane0.Load(int3(tint_clamp_1(coord, (0).xx, int2((uint2(tint_tmp_3.xy) - (1u).xx))), tint_clamp(0, 0, int((uint(tint_tmp_5.z) - 1u))))).r, plane1.Load(int3(tint_clamp_1(coord1, (0).xx, int2((uint2(tint_tmp_6.xy) - (1u).xx))), tint_clamp(0, 0, int((uint(tint_tmp_8.z) - 1u))))).rg, 1.0f));
plane1.GetDimensions(level_idx_2, tint_tmp_5.x, tint_tmp_5.y, tint_tmp_5.z);
color = mul(params.yuvToRgbConversionMatrix, float4(plane0.Load(int3(tint_clamp(coord, (0).xx, int2((tint_tmp_4.xy - (1u).xx))), int(level_idx_1))).r, plane1.Load(int3(tint_clamp(coord1, (0).xx, int2((tint_tmp_5.xy - (1u).xx))), int(level_idx_2))).rg, 1.0f));
}
if ((params.doYuvToRgbConversionOnly == 0u)) {
color = gammaCorrection(color, params.gammaDecodeParams);
@ -121,12 +114,12 @@ ExternalTextureParams ext_tex_params_load(uint offset) {
[numthreads(1, 1, 1)]
void main() {
float4 red = textureLoadExternal(t, ext_tex_plane_1, (10).xx, ext_tex_params_load(0u));
int2 tint_tmp_9;
outImage.GetDimensions(tint_tmp_9.x, tint_tmp_9.y);
outImage[tint_clamp_1((0).xx, (0).xx, int2((uint2(tint_tmp_9) - (1u).xx)))] = red;
int2 tint_tmp_6;
outImage.GetDimensions(tint_tmp_6.x, tint_tmp_6.y);
outImage[tint_clamp((0).xx, (0).xx, int2((tint_tmp_6 - (1u).xx)))] = red;
float4 green = textureLoadExternal(t, ext_tex_plane_1, int2(70, 118), ext_tex_params_load(0u));
int2 tint_tmp_10;
outImage.GetDimensions(tint_tmp_10.x, tint_tmp_10.y);
outImage[tint_clamp_1(int2(1, 0), (0).xx, int2((uint2(tint_tmp_10) - (1u).xx)))] = green;
int2 tint_tmp_7;
outImage.GetDimensions(tint_tmp_7.x, tint_tmp_7.y);
outImage[tint_clamp(int2(1, 0), (0).xx, int2((tint_tmp_7 - (1u).xx)))] = green;
return;
}

View File

@ -72,11 +72,7 @@ ExternalTextureParams tint_unpack_vec3_in_composite_1(ExternalTextureParams_tint
return result;
}
int tint_clamp(int e, int low, int high) {
return min(max(e, low), high);
}
int2 tint_clamp_1(int2 e, int2 low, int2 high) {
int2 tint_clamp(int2 e, int2 low, int2 high) {
return min(max(e, low), high);
}
@ -91,9 +87,12 @@ float4 textureLoadExternal(texture2d<float, access::sample> plane0, texture2d<fl
int2 const coord1 = (coord >> uint2(1u));
float3 color = 0.0f;
if ((params.numPlanes == 1u)) {
color = plane0.read(uint2(tint_clamp_1(coord, int2(0), int2((uint2(uint2(plane0.get_width(tint_clamp(0, 0, int((uint(plane0.get_num_mip_levels()) - 1u)))), plane0.get_height(tint_clamp(0, 0, int((uint(plane0.get_num_mip_levels()) - 1u)))))) - uint2(1u))))), tint_clamp(0, 0, int((uint(plane0.get_num_mip_levels()) - 1u)))).rgb;
uint const level_idx = min(0u, (plane0.get_num_mip_levels() - 1u));
color = plane0.read(uint2(tint_clamp(coord, int2(0), int2((uint2(plane0.get_width(level_idx), plane0.get_height(level_idx)) - uint2(1u))))), level_idx).rgb;
} else {
color = (float4(plane0.read(uint2(tint_clamp_1(coord, int2(0), int2((uint2(uint2(plane0.get_width(tint_clamp(0, 0, int((uint(plane0.get_num_mip_levels()) - 1u)))), plane0.get_height(tint_clamp(0, 0, int((uint(plane0.get_num_mip_levels()) - 1u)))))) - uint2(1u))))), tint_clamp(0, 0, int((uint(plane0.get_num_mip_levels()) - 1u))))[0], plane1.read(uint2(tint_clamp_1(coord1, int2(0), int2((uint2(uint2(plane1.get_width(tint_clamp(0, 0, int((uint(plane1.get_num_mip_levels()) - 1u)))), plane1.get_height(tint_clamp(0, 0, int((uint(plane1.get_num_mip_levels()) - 1u)))))) - uint2(1u))))), tint_clamp(0, 0, int((uint(plane1.get_num_mip_levels()) - 1u)))).rg, 1.0f) * params.yuvToRgbConversionMatrix);
uint const level_idx_1 = min(0u, (plane0.get_num_mip_levels() - 1u));
uint const level_idx_2 = min(0u, (plane1.get_num_mip_levels() - 1u));
color = (float4(plane0.read(uint2(tint_clamp(coord, int2(0), int2((uint2(plane0.get_width(level_idx_1), plane0.get_height(level_idx_1)) - uint2(1u))))), level_idx_1)[0], plane1.read(uint2(tint_clamp(coord1, int2(0), int2((uint2(plane1.get_width(level_idx_2), plane1.get_height(level_idx_2)) - uint2(1u))))), level_idx_2).rg, 1.0f) * params.yuvToRgbConversionMatrix);
}
if ((params.doYuvToRgbConversionOnly == 0u)) {
color = gammaCorrection(color, params.gammaDecodeParams);
@ -105,9 +104,9 @@ float4 textureLoadExternal(texture2d<float, access::sample> plane0, texture2d<fl
kernel void tint_symbol(texture2d<float, access::sample> tint_symbol_1 [[texture(0)]], texture2d<float, access::sample> tint_symbol_2 [[texture(1)]], const constant ExternalTextureParams_tint_packed_vec3* tint_symbol_3 [[buffer(0)]], texture2d<float, access::write> tint_symbol_4 [[texture(2)]]) {
float4 red = textureLoadExternal(tint_symbol_1, tint_symbol_2, int2(10), tint_unpack_vec3_in_composite_1(*(tint_symbol_3)));
tint_symbol_4.write(red, uint2(tint_clamp_1(int2(0), int2(0), int2((uint2(uint2(tint_symbol_4.get_width(), tint_symbol_4.get_height())) - uint2(1u))))));
tint_symbol_4.write(red, uint2(tint_clamp(int2(0), int2(0), int2((uint2(tint_symbol_4.get_width(), tint_symbol_4.get_height()) - uint2(1u))))));
float4 green = textureLoadExternal(tint_symbol_1, tint_symbol_2, int2(70, 118), tint_unpack_vec3_in_composite_1(*(tint_symbol_3)));
tint_symbol_4.write(green, uint2(tint_clamp_1(int2(1, 0), int2(0), int2((uint2(uint2(tint_symbol_4.get_width(), tint_symbol_4.get_height())) - uint2(1u))))));
tint_symbol_4.write(green, uint2(tint_clamp(int2(1, 0), int2(0), int2((uint2(tint_symbol_4.get_width(), tint_symbol_4.get_height()) - uint2(1u))))));
return;
}

View File

@ -1,11 +1,11 @@
; SPIR-V
; Version: 1.3
; Generator: Google Tint Compiler; 0
; Bound: 237
; Bound: 203
; Schema: 0
OpCapability Shader
OpCapability ImageQuery
%28 = OpExtInstImport "GLSL.std.450"
%29 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
@ -38,10 +38,6 @@
OpName %e "e"
OpName %low "low"
OpName %high "high"
OpName %tint_clamp_1 "tint_clamp_1"
OpName %e_0 "e"
OpName %low_0 "low"
OpName %high_0 "high"
OpName %gammaCorrection "gammaCorrection"
OpName %v "v"
OpName %params "params"
@ -130,233 +126,208 @@
%_ptr_UniformConstant_19 = OpTypePointer UniformConstant %19
%outImage = OpVariable %_ptr_UniformConstant_19 UniformConstant
%int = OpTypeInt 32 1
%20 = OpTypeFunction %int %int %int %int
%v2int = OpTypeVector %int 2
%30 = OpTypeFunction %v2int %v2int %v2int %v2int
%39 = OpTypeFunction %v3float %v3float %GammaTransferParams
%20 = OpTypeFunction %v2int %v2int %v2int %v2int
%31 = OpTypeFunction %v3float %v3float %GammaTransferParams
%bool = OpTypeBool
%v3bool = OpTypeVector %bool 3
%_ptr_Function_v3float = OpTypePointer Function %v3float
%58 = OpConstantNull %v3float
%50 = OpConstantNull %v3float
%mat3v2float = OpTypeMatrix %v2float 3
%ExternalTextureParams = OpTypeStruct %uint %uint %mat3v4float %GammaTransferParams %GammaTransferParams %mat3v3float %mat3v2float
%78 = OpTypeFunction %v4float %3 %3 %v2int %ExternalTextureParams
%70 = OpTypeFunction %v4float %3 %3 %v2int %ExternalTextureParams
%v2uint = OpTypeVector %uint 2
%uint_1 = OpConstant %uint 1
%89 = OpConstantComposite %v2uint %uint_1 %uint_1
%99 = OpConstantNull %v2int
%104 = OpConstantNull %int
%81 = OpConstantComposite %v2uint %uint_1 %uint_1
%90 = OpConstantNull %uint
%95 = OpConstantNull %v2int
%float_1 = OpConstant %float 1
%157 = OpConstantNull %uint
%175 = OpTypeFunction %ExternalTextureParams %ExternalTextureParams_std140
%142 = OpTypeFunction %ExternalTextureParams %ExternalTextureParams_std140
%void = OpTypeVoid
%190 = OpTypeFunction %void
%157 = OpTypeFunction %void
%int_10 = OpConstant %int 10
%198 = OpConstantComposite %v2int %int_10 %int_10
%165 = OpConstantComposite %v2int %int_10 %int_10
%uint_0 = OpConstant %uint 0
%_ptr_Uniform_ExternalTextureParams_std140 = OpTypePointer Uniform %ExternalTextureParams_std140
%_ptr_Function_v4float = OpTypePointer Function %v4float
%206 = OpConstantNull %v4float
%173 = OpConstantNull %v4float
%int_70 = OpConstant %int 70
%int_118 = OpConstant %int 118
%221 = OpConstantComposite %v2int %int_70 %int_118
%187 = OpConstantComposite %v2int %int_70 %int_118
%int_1 = OpConstant %int 1
%230 = OpConstantComposite %v2int %int_1 %104
%tint_clamp = OpFunction %int None %20
%e = OpFunctionParameter %int
%low = OpFunctionParameter %int
%high = OpFunctionParameter %int
%26 = OpLabel
%29 = OpExtInst %int %28 SMax %e %low
%27 = OpExtInst %int %28 SMin %29 %high
OpReturnValue %27
%196 = OpConstantNull %int
%197 = OpConstantComposite %v2int %int_1 %196
%tint_clamp = OpFunction %v2int None %20
%e = OpFunctionParameter %v2int
%low = OpFunctionParameter %v2int
%high = OpFunctionParameter %v2int
%27 = OpLabel
%30 = OpExtInst %v2int %29 SMax %e %low
%28 = OpExtInst %v2int %29 SMin %30 %high
OpReturnValue %28
OpFunctionEnd
%tint_clamp_1 = OpFunction %v2int None %30
%e_0 = OpFunctionParameter %v2int
%low_0 = OpFunctionParameter %v2int
%high_0 = OpFunctionParameter %v2int
%36 = OpLabel
%38 = OpExtInst %v2int %28 SMax %e_0 %low_0
%37 = OpExtInst %v2int %28 SMin %38 %high_0
OpReturnValue %37
OpFunctionEnd
%gammaCorrection = OpFunction %v3float None %39
%gammaCorrection = OpFunction %v3float None %31
%v = OpFunctionParameter %v3float
%params = OpFunctionParameter %GammaTransferParams
%43 = OpLabel
%56 = OpVariable %_ptr_Function_v3float Function %58
%68 = OpVariable %_ptr_Function_v3float Function %58
%74 = OpVariable %_ptr_Function_v3float Function %58
%44 = OpExtInst %v3float %28 FAbs %v
%45 = OpCompositeExtract %float %params 4
%46 = OpCompositeConstruct %v3float %45 %45 %45
%47 = OpFOrdLessThan %v3bool %44 %46
%50 = OpExtInst %v3float %28 FSign %v
%51 = OpCompositeExtract %float %params 3
%52 = OpExtInst %v3float %28 FAbs %v
%53 = OpVectorTimesScalar %v3float %52 %51
%54 = OpCompositeExtract %float %params 6
%59 = OpCompositeConstruct %v3float %54 %54 %54
%55 = OpFAdd %v3float %53 %59
%60 = OpFMul %v3float %50 %55
%61 = OpExtInst %v3float %28 FSign %v
%63 = OpCompositeExtract %float %params 1
%64 = OpExtInst %v3float %28 FAbs %v
%65 = OpVectorTimesScalar %v3float %64 %63
%66 = OpCompositeExtract %float %params 2
%69 = OpCompositeConstruct %v3float %66 %66 %66
%67 = OpFAdd %v3float %65 %69
%70 = OpCompositeExtract %float %params 0
%71 = OpCompositeConstruct %v3float %70 %70 %70
%62 = OpExtInst %v3float %28 Pow %67 %71
%72 = OpCompositeExtract %float %params 5
%75 = OpCompositeConstruct %v3float %72 %72 %72
%73 = OpFAdd %v3float %62 %75
%76 = OpFMul %v3float %61 %73
%77 = OpSelect %v3float %47 %60 %76
OpReturnValue %77
%35 = OpLabel
%48 = OpVariable %_ptr_Function_v3float Function %50
%60 = OpVariable %_ptr_Function_v3float Function %50
%66 = OpVariable %_ptr_Function_v3float Function %50
%36 = OpExtInst %v3float %29 FAbs %v
%37 = OpCompositeExtract %float %params 4
%38 = OpCompositeConstruct %v3float %37 %37 %37
%39 = OpFOrdLessThan %v3bool %36 %38
%42 = OpExtInst %v3float %29 FSign %v
%43 = OpCompositeExtract %float %params 3
%44 = OpExtInst %v3float %29 FAbs %v
%45 = OpVectorTimesScalar %v3float %44 %43
%46 = OpCompositeExtract %float %params 6
%51 = OpCompositeConstruct %v3float %46 %46 %46
%47 = OpFAdd %v3float %45 %51
%52 = OpFMul %v3float %42 %47
%53 = OpExtInst %v3float %29 FSign %v
%55 = OpCompositeExtract %float %params 1
%56 = OpExtInst %v3float %29 FAbs %v
%57 = OpVectorTimesScalar %v3float %56 %55
%58 = OpCompositeExtract %float %params 2
%61 = OpCompositeConstruct %v3float %58 %58 %58
%59 = OpFAdd %v3float %57 %61
%62 = OpCompositeExtract %float %params 0
%63 = OpCompositeConstruct %v3float %62 %62 %62
%54 = OpExtInst %v3float %29 Pow %59 %63
%64 = OpCompositeExtract %float %params 5
%67 = OpCompositeConstruct %v3float %64 %64 %64
%65 = OpFAdd %v3float %54 %67
%68 = OpFMul %v3float %53 %65
%69 = OpSelect %v3float %39 %52 %68
OpReturnValue %69
OpFunctionEnd
%textureLoadExternal = OpFunction %v4float None %78
%textureLoadExternal = OpFunction %v4float None %70
%plane0 = OpFunctionParameter %3
%plane1 = OpFunctionParameter %3
%coord = OpFunctionParameter %v2int
%params_0 = OpFunctionParameter %ExternalTextureParams
%78 = OpLabel
%color = OpVariable %_ptr_Function_v3float Function %50
%82 = OpShiftRightArithmetic %v2int %coord %81
%84 = OpCompositeExtract %uint %params_0 0
%85 = OpIEqual %bool %84 %uint_1
OpSelectionMerge %86 None
OpBranchConditional %85 %87 %88
%87 = OpLabel
%91 = OpImageQueryLevels %uint %plane0
%92 = OpISub %uint %91 %uint_1
%89 = OpExtInst %uint %29 UMin %90 %92
%97 = OpImageQuerySizeLod %v2uint %plane0 %89
%98 = OpISub %v2uint %97 %81
%96 = OpBitcast %v2int %98
%94 = OpFunctionCall %v2int %tint_clamp %coord %95 %96
%93 = OpImageFetch %v4float %plane0 %94 Lod %89
%99 = OpVectorShuffle %v3float %93 %93 0 1 2
OpStore %color %99
OpBranch %86
%88 = OpLabel
%101 = OpImageQueryLevels %uint %plane0
%102 = OpISub %uint %101 %uint_1
%100 = OpExtInst %uint %29 UMin %90 %102
%104 = OpImageQueryLevels %uint %plane1
%105 = OpISub %uint %104 %uint_1
%103 = OpExtInst %uint %29 UMin %90 %105
%109 = OpImageQuerySizeLod %v2uint %plane0 %100
%110 = OpISub %v2uint %109 %81
%108 = OpBitcast %v2int %110
%107 = OpFunctionCall %v2int %tint_clamp %coord %95 %108
%106 = OpImageFetch %v4float %plane0 %107 Lod %100
%111 = OpCompositeExtract %float %106 0
%115 = OpImageQuerySizeLod %v2uint %plane1 %103
%116 = OpISub %v2uint %115 %81
%114 = OpBitcast %v2int %116
%113 = OpFunctionCall %v2int %tint_clamp %82 %95 %114
%112 = OpImageFetch %v4float %plane1 %113 Lod %103
%117 = OpVectorShuffle %v2float %112 %112 0 1
%118 = OpCompositeExtract %float %117 0
%119 = OpCompositeExtract %float %117 1
%121 = OpCompositeConstruct %v4float %111 %118 %119 %float_1
%122 = OpCompositeExtract %mat3v4float %params_0 2
%123 = OpVectorTimesMatrix %v3float %121 %122
OpStore %color %123
OpBranch %86
%86 = OpLabel
%color = OpVariable %_ptr_Function_v3float Function %58
%90 = OpShiftRightArithmetic %v2int %coord %89
%92 = OpCompositeExtract %uint %params_0 0
%93 = OpIEqual %bool %92 %uint_1
OpSelectionMerge %94 None
OpBranchConditional %93 %95 %96
%95 = OpLabel
%107 = OpImageQueryLevels %uint %plane0
%108 = OpISub %uint %107 %uint_1
%105 = OpBitcast %int %108
%103 = OpFunctionCall %int %tint_clamp %104 %104 %105
%102 = OpImageQuerySizeLod %v2uint %plane0 %103
%109 = OpISub %v2uint %102 %89
%100 = OpBitcast %v2int %109
%98 = OpFunctionCall %v2int %tint_clamp_1 %coord %99 %100
%113 = OpImageQueryLevels %uint %plane0
%114 = OpISub %uint %113 %uint_1
%111 = OpBitcast %int %114
%110 = OpFunctionCall %int %tint_clamp %104 %104 %111
%97 = OpImageFetch %v4float %plane0 %98 Lod %110
%115 = OpVectorShuffle %v3float %97 %97 0 1 2
OpStore %color %115
OpBranch %94
%96 = OpLabel
%124 = OpImageQueryLevels %uint %plane0
%125 = OpISub %uint %124 %uint_1
%122 = OpBitcast %int %125
%121 = OpFunctionCall %int %tint_clamp %104 %104 %122
%120 = OpImageQuerySizeLod %v2uint %plane0 %121
%126 = OpISub %v2uint %120 %89
%118 = OpBitcast %v2int %126
%117 = OpFunctionCall %v2int %tint_clamp_1 %coord %99 %118
%130 = OpImageQueryLevels %uint %plane0
%131 = OpISub %uint %130 %uint_1
%128 = OpBitcast %int %131
%127 = OpFunctionCall %int %tint_clamp %104 %104 %128
%116 = OpImageFetch %v4float %plane0 %117 Lod %127
%132 = OpCompositeExtract %float %116 0
%141 = OpImageQueryLevels %uint %plane1
%142 = OpISub %uint %141 %uint_1
%139 = OpBitcast %int %142
%138 = OpFunctionCall %int %tint_clamp %104 %104 %139
%137 = OpImageQuerySizeLod %v2uint %plane1 %138
%143 = OpISub %v2uint %137 %89
%135 = OpBitcast %v2int %143
%134 = OpFunctionCall %v2int %tint_clamp_1 %90 %99 %135
%147 = OpImageQueryLevels %uint %plane1
%148 = OpISub %uint %147 %uint_1
%145 = OpBitcast %int %148
%144 = OpFunctionCall %int %tint_clamp %104 %104 %145
%133 = OpImageFetch %v4float %plane1 %134 Lod %144
%149 = OpVectorShuffle %v2float %133 %133 0 1
%150 = OpCompositeExtract %float %149 0
%151 = OpCompositeExtract %float %149 1
%153 = OpCompositeConstruct %v4float %132 %150 %151 %float_1
%154 = OpCompositeExtract %mat3v4float %params_0 2
%155 = OpVectorTimesMatrix %v3float %153 %154
OpStore %color %155
OpBranch %94
%94 = OpLabel
%156 = OpCompositeExtract %uint %params_0 1
%158 = OpIEqual %bool %156 %157
OpSelectionMerge %159 None
OpBranchConditional %158 %160 %159
%160 = OpLabel
%162 = OpLoad %v3float %color
%163 = OpCompositeExtract %GammaTransferParams %params_0 3
%161 = OpFunctionCall %v3float %gammaCorrection %162 %163
OpStore %color %161
%164 = OpCompositeExtract %mat3v3float %params_0 5
%165 = OpLoad %v3float %color
%166 = OpMatrixTimesVector %v3float %164 %165
OpStore %color %166
%168 = OpLoad %v3float %color
%169 = OpCompositeExtract %GammaTransferParams %params_0 4
%167 = OpFunctionCall %v3float %gammaCorrection %168 %169
OpStore %color %167
OpBranch %159
%159 = OpLabel
%170 = OpLoad %v3float %color
%171 = OpCompositeExtract %float %170 0
%172 = OpCompositeExtract %float %170 1
%173 = OpCompositeExtract %float %170 2
%174 = OpCompositeConstruct %v4float %171 %172 %173 %float_1
OpReturnValue %174
%124 = OpCompositeExtract %uint %params_0 1
%125 = OpIEqual %bool %124 %90
OpSelectionMerge %126 None
OpBranchConditional %125 %127 %126
%127 = OpLabel
%129 = OpLoad %v3float %color
%130 = OpCompositeExtract %GammaTransferParams %params_0 3
%128 = OpFunctionCall %v3float %gammaCorrection %129 %130
OpStore %color %128
%131 = OpCompositeExtract %mat3v3float %params_0 5
%132 = OpLoad %v3float %color
%133 = OpMatrixTimesVector %v3float %131 %132
OpStore %color %133
%135 = OpLoad %v3float %color
%136 = OpCompositeExtract %GammaTransferParams %params_0 4
%134 = OpFunctionCall %v3float %gammaCorrection %135 %136
OpStore %color %134
OpBranch %126
%126 = OpLabel
%137 = OpLoad %v3float %color
%138 = OpCompositeExtract %float %137 0
%139 = OpCompositeExtract %float %137 1
%140 = OpCompositeExtract %float %137 2
%141 = OpCompositeConstruct %v4float %138 %139 %140 %float_1
OpReturnValue %141
OpFunctionEnd
%conv_ExternalTextureParams = OpFunction %ExternalTextureParams None %175
%conv_ExternalTextureParams = OpFunction %ExternalTextureParams None %142
%val = OpFunctionParameter %ExternalTextureParams_std140
%178 = OpLabel
%179 = OpCompositeExtract %uint %val 0
%180 = OpCompositeExtract %uint %val 1
%181 = OpCompositeExtract %mat3v4float %val 2
%182 = OpCompositeExtract %GammaTransferParams %val 3
%183 = OpCompositeExtract %GammaTransferParams %val 4
%184 = OpCompositeExtract %mat3v3float %val 5
%185 = OpCompositeExtract %v2float %val 6
%186 = OpCompositeExtract %v2float %val 7
%187 = OpCompositeExtract %v2float %val 8
%188 = OpCompositeConstruct %mat3v2float %185 %186 %187
%189 = OpCompositeConstruct %ExternalTextureParams %179 %180 %181 %182 %183 %184 %188
OpReturnValue %189
%145 = OpLabel
%146 = OpCompositeExtract %uint %val 0
%147 = OpCompositeExtract %uint %val 1
%148 = OpCompositeExtract %mat3v4float %val 2
%149 = OpCompositeExtract %GammaTransferParams %val 3
%150 = OpCompositeExtract %GammaTransferParams %val 4
%151 = OpCompositeExtract %mat3v3float %val 5
%152 = OpCompositeExtract %v2float %val 6
%153 = OpCompositeExtract %v2float %val 7
%154 = OpCompositeExtract %v2float %val 8
%155 = OpCompositeConstruct %mat3v2float %152 %153 %154
%156 = OpCompositeConstruct %ExternalTextureParams %146 %147 %148 %149 %150 %151 %155
OpReturnValue %156
OpFunctionEnd
%main = OpFunction %void None %190
%193 = OpLabel
%red = OpVariable %_ptr_Function_v4float Function %206
%green = OpVariable %_ptr_Function_v4float Function %206
%195 = OpLoad %3 %t
%196 = OpLoad %3 %ext_tex_plane_1
%202 = OpAccessChain %_ptr_Uniform_ExternalTextureParams_std140 %ext_tex_params %uint_0
%203 = OpLoad %ExternalTextureParams_std140 %202
%199 = OpFunctionCall %ExternalTextureParams %conv_ExternalTextureParams %203
%194 = OpFunctionCall %v4float %textureLoadExternal %195 %196 %198 %199
OpStore %red %194
%208 = OpLoad %19 %outImage
%213 = OpLoad %19 %outImage
%212 = OpImageQuerySize %v2uint %213
%214 = OpISub %v2uint %212 %89
%210 = OpBitcast %v2int %214
%209 = OpFunctionCall %v2int %tint_clamp_1 %99 %99 %210
%215 = OpLoad %v4float %red
OpImageWrite %208 %209 %215
%217 = OpLoad %3 %t
%218 = OpLoad %3 %ext_tex_plane_1
%223 = OpAccessChain %_ptr_Uniform_ExternalTextureParams_std140 %ext_tex_params %uint_0
%224 = OpLoad %ExternalTextureParams_std140 %223
%222 = OpFunctionCall %ExternalTextureParams %conv_ExternalTextureParams %224
%216 = OpFunctionCall %v4float %textureLoadExternal %217 %218 %221 %222
OpStore %green %216
%227 = OpLoad %19 %outImage
%234 = OpLoad %19 %outImage
%233 = OpImageQuerySize %v2uint %234
%235 = OpISub %v2uint %233 %89
%231 = OpBitcast %v2int %235
%228 = OpFunctionCall %v2int %tint_clamp_1 %230 %99 %231
%236 = OpLoad %v4float %green
OpImageWrite %227 %228 %236
%main = OpFunction %void None %157
%160 = OpLabel
%red = OpVariable %_ptr_Function_v4float Function %173
%green = OpVariable %_ptr_Function_v4float Function %173
%162 = OpLoad %3 %t
%163 = OpLoad %3 %ext_tex_plane_1
%169 = OpAccessChain %_ptr_Uniform_ExternalTextureParams_std140 %ext_tex_params %uint_0
%170 = OpLoad %ExternalTextureParams_std140 %169
%166 = OpFunctionCall %ExternalTextureParams %conv_ExternalTextureParams %170
%161 = OpFunctionCall %v4float %textureLoadExternal %162 %163 %165 %166
OpStore %red %161
%175 = OpLoad %19 %outImage
%179 = OpLoad %19 %outImage
%178 = OpImageQuerySize %v2uint %179
%180 = OpISub %v2uint %178 %81
%177 = OpBitcast %v2int %180
%176 = OpFunctionCall %v2int %tint_clamp %95 %95 %177
%181 = OpLoad %v4float %red
OpImageWrite %175 %176 %181
%183 = OpLoad %3 %t
%184 = OpLoad %3 %ext_tex_plane_1
%189 = OpAccessChain %_ptr_Uniform_ExternalTextureParams_std140 %ext_tex_params %uint_0
%190 = OpLoad %ExternalTextureParams_std140 %189
%188 = OpFunctionCall %ExternalTextureParams %conv_ExternalTextureParams %190
%182 = OpFunctionCall %v4float %textureLoadExternal %183 %184 %187 %188
OpStore %green %182
%193 = OpLoad %19 %outImage
%200 = OpLoad %19 %outImage
%199 = OpImageQuerySize %v2uint %200
%201 = OpISub %v2uint %199 %81
%198 = OpBitcast %v2int %201
%194 = OpFunctionCall %v2int %tint_clamp %197 %95 %198
%202 = OpLoad %v4float %green
OpImageWrite %193 %194 %202
OpReturn
OpFunctionEnd