Resolver: collect unique texture/samplers pairs.
GLSL does not support separate textures and samplers, so they must be replaced with combined samplers. This is the first stage of that change, where we collect the unique texture/sampler pairs. Within a function, texture and sampler must be either a global variable or a function parameter. At the entry point level, all references must resolve to global variables, so by recursing the call graph we can determine all of the global pairs required. This information will be used by an upcoming transform to modify the AST to be GLSL-compliant: modifying function signatures, call sites, removing separate globals and adding combined globals. It will also eventually replace the pair-gathering currently performed by Inspector::GetSamplerTextureUses(). Bug: tint:1366 Change-Id: I89451b195649da26e45641ea2f6955683ae9fc66 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/75960 Reviewed-by: Ben Clayton <bclayton@google.com> Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: Stephen White <senorblanco@chromium.org>
This commit is contained in:
parent
d1efb5d48c
commit
3e354fd524
|
@ -1400,10 +1400,26 @@ sem::Call* Resolver::IntrinsicCall(
|
||||||
|
|
||||||
current_function_->AddDirectlyCalledIntrinsic(intrinsic);
|
current_function_->AddDirectlyCalledIntrinsic(intrinsic);
|
||||||
|
|
||||||
if (IsTextureIntrinsic(intrinsic_type) &&
|
if (IsTextureIntrinsic(intrinsic_type)) {
|
||||||
!ValidateTextureIntrinsicFunction(call)) {
|
if (!ValidateTextureIntrinsicFunction(call)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
// Collect a texture/sampler pair for this intrinsic.
|
||||||
|
const auto& signature = intrinsic->Signature();
|
||||||
|
int texture_index = signature.IndexOf(sem::ParameterUsage::kTexture);
|
||||||
|
if (texture_index == -1) {
|
||||||
|
TINT_ICE(Resolver, diagnostics_)
|
||||||
|
<< "texture intrinsic without texture parameter";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* texture = args[texture_index]->As<sem::VariableUser>()->Variable();
|
||||||
|
int sampler_index = signature.IndexOf(sem::ParameterUsage::kSampler);
|
||||||
|
const sem::Variable* sampler =
|
||||||
|
sampler_index != -1
|
||||||
|
? args[sampler_index]->As<sem::VariableUser>()->Variable()
|
||||||
|
: nullptr;
|
||||||
|
current_function_->AddTextureSamplerPair(texture, sampler);
|
||||||
|
}
|
||||||
|
|
||||||
if (!ValidateIntrinsicCall(call)) {
|
if (!ValidateIntrinsicCall(call)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -1439,6 +1455,26 @@ sem::Call* Resolver::FunctionCall(
|
||||||
for (auto* var : target->TransitivelyReferencedGlobals()) {
|
for (auto* var : target->TransitivelyReferencedGlobals()) {
|
||||||
current_function_->AddTransitivelyReferencedGlobal(var);
|
current_function_->AddTransitivelyReferencedGlobal(var);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map all texture/sampler pairs from the target function to the
|
||||||
|
// current function. These can only be global or parameter
|
||||||
|
// variables. Resolve any parameter variables to the corresponding
|
||||||
|
// argument passed to the current function. Leave global variables
|
||||||
|
// as-is. Then add the mapped pair to the current function's list of
|
||||||
|
// texture/sampler pairs.
|
||||||
|
for (sem::VariablePair pair : target->TextureSamplerPairs()) {
|
||||||
|
const sem::Variable* texture = pair.first;
|
||||||
|
const sem::Variable* sampler = pair.second;
|
||||||
|
if (auto* param = texture->As<sem::Parameter>()) {
|
||||||
|
texture = args[param->Index()]->As<sem::VariableUser>()->Variable();
|
||||||
|
}
|
||||||
|
if (sampler) {
|
||||||
|
if (auto* param = sampler->As<sem::Parameter>()) {
|
||||||
|
sampler = args[param->Index()]->As<sem::VariableUser>()->Variable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_function_->AddTextureSamplerPair(texture, sampler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target->AddCallSite(call);
|
target->AddCallSite(call);
|
||||||
|
|
|
@ -2021,6 +2021,146 @@ TEST_F(ResolverTest, UnaryOp_Negation) {
|
||||||
EXPECT_FALSE(r()->Resolve());
|
EXPECT_FALSE(r()->Resolve());
|
||||||
EXPECT_EQ(r()->error(), "12:34 error: cannot negate expression of type 'u32");
|
EXPECT_EQ(r()->error(), "12:34 error: cannot negate expression of type 'u32");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ResolverTest, TextureSampler_TextureSample) {
|
||||||
|
Global("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
|
||||||
|
GroupAndBinding(1, 1));
|
||||||
|
Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
|
||||||
|
|
||||||
|
auto* call = CallStmt(Call("textureSample", "t", "s", vec2<f32>(1.0f, 2.0f)));
|
||||||
|
const ast::Function* f = Func("test_function", {}, ty.void_(), {call},
|
||||||
|
{Stage(ast::PipelineStage::kFragment)});
|
||||||
|
|
||||||
|
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||||
|
|
||||||
|
const sem::Function* sf = Sem().Get(f);
|
||||||
|
auto pairs = sf->TextureSamplerPairs();
|
||||||
|
ASSERT_EQ(pairs.size(), 1u);
|
||||||
|
EXPECT_TRUE(pairs[0].first != nullptr);
|
||||||
|
EXPECT_TRUE(pairs[0].second != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ResolverTest, TextureSampler_TextureSampleInFunction) {
|
||||||
|
Global("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
|
||||||
|
GroupAndBinding(1, 1));
|
||||||
|
Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
|
||||||
|
|
||||||
|
auto* inner_call =
|
||||||
|
CallStmt(Call("textureSample", "t", "s", vec2<f32>(1.0f, 2.0f)));
|
||||||
|
const ast::Function* inner_func =
|
||||||
|
Func("inner_func", {}, ty.void_(), {inner_call});
|
||||||
|
auto* outer_call = CallStmt(Call("inner_func"));
|
||||||
|
const ast::Function* outer_func =
|
||||||
|
Func("outer_func", {}, ty.void_(), {outer_call},
|
||||||
|
{Stage(ast::PipelineStage::kFragment)});
|
||||||
|
|
||||||
|
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||||
|
|
||||||
|
auto inner_pairs = Sem().Get(inner_func)->TextureSamplerPairs();
|
||||||
|
ASSERT_EQ(inner_pairs.size(), 1u);
|
||||||
|
EXPECT_TRUE(inner_pairs[0].first != nullptr);
|
||||||
|
EXPECT_TRUE(inner_pairs[0].second != nullptr);
|
||||||
|
|
||||||
|
auto outer_pairs = Sem().Get(outer_func)->TextureSamplerPairs();
|
||||||
|
ASSERT_EQ(outer_pairs.size(), 1u);
|
||||||
|
EXPECT_TRUE(outer_pairs[0].first != nullptr);
|
||||||
|
EXPECT_TRUE(outer_pairs[0].second != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ResolverTest, TextureSampler_TextureSampleFunctionDiamondSameVariables) {
|
||||||
|
Global("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
|
||||||
|
GroupAndBinding(1, 1));
|
||||||
|
Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 2));
|
||||||
|
|
||||||
|
auto* inner_call_1 =
|
||||||
|
CallStmt(Call("textureSample", "t", "s", vec2<f32>(1.0f, 2.0f)));
|
||||||
|
const ast::Function* inner_func_1 =
|
||||||
|
Func("inner_func_1", {}, ty.void_(), {inner_call_1});
|
||||||
|
auto* inner_call_2 =
|
||||||
|
CallStmt(Call("textureSample", "t", "s", vec2<f32>(3.0f, 4.0f)));
|
||||||
|
const ast::Function* inner_func_2 =
|
||||||
|
Func("inner_func_2", {}, ty.void_(), {inner_call_2});
|
||||||
|
auto* outer_call_1 = CallStmt(Call("inner_func_1"));
|
||||||
|
auto* outer_call_2 = CallStmt(Call("inner_func_2"));
|
||||||
|
const ast::Function* outer_func =
|
||||||
|
Func("outer_func", {}, ty.void_(), {outer_call_1, outer_call_2},
|
||||||
|
{Stage(ast::PipelineStage::kFragment)});
|
||||||
|
|
||||||
|
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||||
|
|
||||||
|
auto inner_pairs_1 = Sem().Get(inner_func_1)->TextureSamplerPairs();
|
||||||
|
ASSERT_EQ(inner_pairs_1.size(), 1u);
|
||||||
|
EXPECT_TRUE(inner_pairs_1[0].first != nullptr);
|
||||||
|
EXPECT_TRUE(inner_pairs_1[0].second != nullptr);
|
||||||
|
|
||||||
|
auto inner_pairs_2 = Sem().Get(inner_func_2)->TextureSamplerPairs();
|
||||||
|
ASSERT_EQ(inner_pairs_1.size(), 1u);
|
||||||
|
EXPECT_TRUE(inner_pairs_2[0].first != nullptr);
|
||||||
|
EXPECT_TRUE(inner_pairs_2[0].second != nullptr);
|
||||||
|
|
||||||
|
auto outer_pairs = Sem().Get(outer_func)->TextureSamplerPairs();
|
||||||
|
ASSERT_EQ(outer_pairs.size(), 1u);
|
||||||
|
EXPECT_TRUE(outer_pairs[0].first != nullptr);
|
||||||
|
EXPECT_TRUE(outer_pairs[0].second != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ResolverTest,
|
||||||
|
TextureSampler_TextureSampleFunctionDiamondDifferentVariables) {
|
||||||
|
Global("t1", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
|
||||||
|
GroupAndBinding(1, 1));
|
||||||
|
Global("t2", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
|
||||||
|
GroupAndBinding(1, 2));
|
||||||
|
Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(1, 3));
|
||||||
|
|
||||||
|
auto* inner_call_1 =
|
||||||
|
CallStmt(Call("textureSample", "t1", "s", vec2<f32>(1.0f, 2.0f)));
|
||||||
|
const ast::Function* inner_func_1 =
|
||||||
|
Func("inner_func_1", {}, ty.void_(), {inner_call_1});
|
||||||
|
auto* inner_call_2 =
|
||||||
|
CallStmt(Call("textureSample", "t2", "s", vec2<f32>(3.0f, 4.0f)));
|
||||||
|
const ast::Function* inner_func_2 =
|
||||||
|
Func("inner_func_2", {}, ty.void_(), {inner_call_2});
|
||||||
|
auto* outer_call_1 = CallStmt(Call("inner_func_1"));
|
||||||
|
auto* outer_call_2 = CallStmt(Call("inner_func_2"));
|
||||||
|
const ast::Function* outer_func =
|
||||||
|
Func("outer_func", {}, ty.void_(), {outer_call_1, outer_call_2},
|
||||||
|
{Stage(ast::PipelineStage::kFragment)});
|
||||||
|
|
||||||
|
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||||
|
|
||||||
|
auto inner_pairs_1 = Sem().Get(inner_func_1)->TextureSamplerPairs();
|
||||||
|
ASSERT_EQ(inner_pairs_1.size(), 1u);
|
||||||
|
EXPECT_TRUE(inner_pairs_1[0].first != nullptr);
|
||||||
|
EXPECT_TRUE(inner_pairs_1[0].second != nullptr);
|
||||||
|
|
||||||
|
auto inner_pairs_2 = Sem().Get(inner_func_2)->TextureSamplerPairs();
|
||||||
|
ASSERT_EQ(inner_pairs_2.size(), 1u);
|
||||||
|
EXPECT_TRUE(inner_pairs_2[0].first != nullptr);
|
||||||
|
EXPECT_TRUE(inner_pairs_2[0].second != nullptr);
|
||||||
|
|
||||||
|
auto outer_pairs = Sem().Get(outer_func)->TextureSamplerPairs();
|
||||||
|
ASSERT_EQ(outer_pairs.size(), 2u);
|
||||||
|
EXPECT_TRUE(outer_pairs[0].first == inner_pairs_1[0].first);
|
||||||
|
EXPECT_TRUE(outer_pairs[0].second == inner_pairs_1[0].second);
|
||||||
|
EXPECT_TRUE(outer_pairs[1].first == inner_pairs_2[0].first);
|
||||||
|
EXPECT_TRUE(outer_pairs[1].second == inner_pairs_2[0].second);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ResolverTest, TextureSampler_TextureDimensions) {
|
||||||
|
Global("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
|
||||||
|
GroupAndBinding(1, 2));
|
||||||
|
|
||||||
|
auto* call = Call("textureDimensions", "t");
|
||||||
|
const ast::Function* f = WrapInFunction(call);
|
||||||
|
|
||||||
|
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||||
|
|
||||||
|
const sem::Function* sf = Sem().Get(f);
|
||||||
|
auto pairs = sf->TextureSamplerPairs();
|
||||||
|
ASSERT_EQ(pairs.size(), 1u);
|
||||||
|
EXPECT_TRUE(pairs[0].first != nullptr);
|
||||||
|
EXPECT_TRUE(pairs[0].second == nullptr);
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace resolver
|
} // namespace resolver
|
||||||
} // namespace tint
|
} // namespace tint
|
||||||
|
|
|
@ -132,6 +132,23 @@ class Function : public Castable<Function, CallTarget> {
|
||||||
directly_called_intrinsics_.add(intrinsic);
|
directly_called_intrinsics_.add(intrinsic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds the given texture/sampler pair to the list of unique pairs
|
||||||
|
/// that this function uses (directly or indirectly). These can only
|
||||||
|
/// be parameters to this function or global variables. Uniqueness is
|
||||||
|
/// ensured by texture_sampler_pairs_ being a UniqueVector.
|
||||||
|
/// @param texture the texture (must be non-null)
|
||||||
|
/// @param sampler the sampler (null indicates a texture-only reference)
|
||||||
|
void AddTextureSamplerPair(const sem::Variable* texture,
|
||||||
|
const sem::Variable* sampler) {
|
||||||
|
texture_sampler_pairs_.add(VariablePair(texture, sampler));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @returns the list of texture/sampler pairs that this function uses
|
||||||
|
/// (directly or indirectly).
|
||||||
|
const std::vector<VariablePair>& TextureSamplerPairs() const {
|
||||||
|
return texture_sampler_pairs_;
|
||||||
|
}
|
||||||
|
|
||||||
/// @returns the list of direct calls to functions / intrinsics made by this
|
/// @returns the list of direct calls to functions / intrinsics made by this
|
||||||
/// function
|
/// function
|
||||||
std::vector<const Call*> DirectCallStatements() const {
|
std::vector<const Call*> DirectCallStatements() const {
|
||||||
|
@ -259,6 +276,7 @@ class Function : public Castable<Function, CallTarget> {
|
||||||
utils::UniqueVector<const GlobalVariable*> transitively_referenced_globals_;
|
utils::UniqueVector<const GlobalVariable*> transitively_referenced_globals_;
|
||||||
utils::UniqueVector<const Function*> transitively_called_functions_;
|
utils::UniqueVector<const Function*> transitively_called_functions_;
|
||||||
utils::UniqueVector<const Intrinsic*> directly_called_intrinsics_;
|
utils::UniqueVector<const Intrinsic*> directly_called_intrinsics_;
|
||||||
|
utils::UniqueVector<VariablePair> texture_sampler_pairs_;
|
||||||
std::vector<const Call*> direct_calls_;
|
std::vector<const Call*> direct_calls_;
|
||||||
std::vector<const Call*> callsites_;
|
std::vector<const Call*> callsites_;
|
||||||
std::vector<const Function*> ancestor_entry_points_;
|
std::vector<const Function*> ancestor_entry_points_;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#ifndef SRC_SEM_VARIABLE_H_
|
#ifndef SRC_SEM_VARIABLE_H_
|
||||||
#define SRC_SEM_VARIABLE_H_
|
#define SRC_SEM_VARIABLE_H_
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "src/ast/access.h"
|
#include "src/ast/access.h"
|
||||||
|
@ -248,7 +249,25 @@ class VariableUser : public Castable<VariableUser, Expression> {
|
||||||
const sem::Variable* const variable_;
|
const sem::Variable* const variable_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A pair of sem::Variables. Can be hashed.
|
||||||
|
typedef std::pair<const Variable*, const Variable*> VariablePair;
|
||||||
|
|
||||||
} // namespace sem
|
} // namespace sem
|
||||||
} // namespace tint
|
} // namespace tint
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
|
||||||
|
/// Custom std::hash specialization for VariablePair
|
||||||
|
template <>
|
||||||
|
class hash<tint::sem::VariablePair> {
|
||||||
|
public:
|
||||||
|
/// @param i the variable pair to create a hash for
|
||||||
|
/// @return the hash value
|
||||||
|
inline std::size_t operator()(const tint::sem::VariablePair& i) const {
|
||||||
|
return tint::utils::Hash(i.first, i.second);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace std
|
||||||
|
|
||||||
#endif // SRC_SEM_VARIABLE_H_
|
#endif // SRC_SEM_VARIABLE_H_
|
||||||
|
|
|
@ -142,6 +142,9 @@ const ast::Type* Transform::CreateASTTypeFor(CloneContext& ctx,
|
||||||
if (auto* t = ty->As<sem::DepthMultisampledTexture>()) {
|
if (auto* t = ty->As<sem::DepthMultisampledTexture>()) {
|
||||||
return ctx.dst->create<ast::DepthMultisampledTexture>(t->dim());
|
return ctx.dst->create<ast::DepthMultisampledTexture>(t->dim());
|
||||||
}
|
}
|
||||||
|
if (ty->Is<sem::ExternalTexture>()) {
|
||||||
|
return ctx.dst->create<ast::ExternalTexture>();
|
||||||
|
}
|
||||||
if (auto* t = ty->As<sem::MultisampledTexture>()) {
|
if (auto* t = ty->As<sem::MultisampledTexture>()) {
|
||||||
return ctx.dst->create<ast::MultisampledTexture>(
|
return ctx.dst->create<ast::MultisampledTexture>(
|
||||||
t->dim(), CreateASTTypeFor(ctx, t->type()));
|
t->dim(), CreateASTTypeFor(ctx, t->type()));
|
||||||
|
|
Loading…
Reference in New Issue