Hash :
671f55d8
Author :
Date :
2025-04-03T22:54:44
Vulkan: Fix texelFetch(externalSampler) behavior GLES expects YUV decoding to happen with texelFetch when an external sampler is used, but texelFetch's translation (OpImageFetch) does not do such a thing. A transformation is added to replace that with a texture call at the right coordinate. Bug: angleproject:405149439 Change-Id: I3a8d07a6399705ec07718b38085ee4bc1ad2af6c Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/6431570 Reviewed-by: Cody Northrop <cnorthrop@google.com> Auto-Submit: Shahbaz Youssefi <syoussefi@chromium.org> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: Amirali Abdolrashidi <abdolrashidi@google.com>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
//
// Copyright 2025 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// RewriteSamplerExternalTexelFetch: Rewrite texelFetch() for external samplers to texture() so that
// yuv decoding happens according to the sampler.
//
#include "compiler/translator/tree_ops/spirv/RewriteSamplerExternalTexelFetch.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
namespace sh
{
namespace
{
// In GLES, texelFetch decodes YUV according to the sampler, while the SPIR-V equivalent
// (OpImageFetch) does not take a sampler and cannot do that. The texelFetch() call is changed to
// texture() here to get the GLES behavior.
class Traverser : public TIntermTraverser
{
public:
Traverser(TSymbolTable *symbolTable) : TIntermTraverser(true, false, false, symbolTable) {}
bool visitAggregate(Visit visit, TIntermAggregate *node) override;
};
bool Traverser::visitAggregate(Visit visit, TIntermAggregate *node)
{
if (node->getOp() != EOpTexelFetch)
{
return true;
}
const TIntermSequence &arguments = *node->getSequence();
ASSERT(arguments.size() == 3);
TIntermTyped *sampler = arguments[0]->getAsTyped();
TIntermTyped *coords = arguments[1]->getAsTyped();
TIntermTyped *lod = arguments[2]->getAsTyped();
const TBasicType samplerType = sampler->getType().getBasicType();
if (samplerType != EbtSamplerExternalOES && samplerType != EbtSamplerExternal2DY2YEXT)
{
return true;
}
// Change
//
// texelFetch(externalSampler, coords, lod)
//
// to
//
// texture(externalSampler, (vec2(coords) + vec2(0.5))
// / vec2(textureSize(externalSampler, lod)))
//
// Note that |texelFetch| takes integer coordinates, while |texture| takes normalized
// coordinates. The division by |textureSize| achieves normalization. Additionally, an offset
// of half a pixel (vec2(0.5)) is added to the coordinates such that |texture|'s sampling is
// done at the center of the pixel, returning only the value of that pixel and not an
// interpolation with its neighboring pixels.
//
const TPrecision coordsPrecision = coords->getType().getPrecision();
TType *vec2Type = new TType(EbtFloat, coordsPrecision, EvqTemporary, 2);
// textureSize(externalSampler, lod)
TIntermTyped *textureSizeCall =
CreateBuiltInFunctionCallNode("textureSize", {sampler, lod}, *mSymbolTable, 300);
// vec2(textureSize(externalSampler, lod))
textureSizeCall = TIntermAggregate::CreateConstructor(*vec2Type, {textureSizeCall});
constexpr float kHalfPixelOffset[2] = {0.5, 0.5};
TIntermTyped *halfPixel = CreateVecNode(kHalfPixelOffset, 2, coordsPrecision);
// vec2(coords)
TIntermTyped *scaledCoords = TIntermAggregate::CreateConstructor(*vec2Type, {coords});
// vec2(coords) + vec2(0.5)
scaledCoords = new TIntermBinary(EOpAdd, scaledCoords, halfPixel);
// (vec2(coords) + vec2(0.5)) / vec2(textureSize(externalSampler, lod)))
scaledCoords = new TIntermBinary(EOpDiv, scaledCoords, textureSizeCall);
TIntermTyped *textureCall = CreateBuiltInFunctionCallNode(
"texture", {sampler->deepCopy(), scaledCoords}, *mSymbolTable, 300);
queueReplacement(textureCall, OriginalNode::IS_DROPPED);
return true;
}
} // anonymous namespace
bool RewriteSamplerExternalTexelFetch(TCompiler *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable)
{
Traverser traverser(symbolTable);
root->traverse(&traverser);
return traverser.updateTree(compiler, root);
}
} // namespace sh