1 //
2 // Copyright 2023 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // ReswizzleYUVOps: Adjusts swizzles for YUV channel order difference between
7 // GLES and Vulkan
8 //
9 //
10
11 #include "compiler/translator/tree_ops/spirv/EmulateYUVBuiltIns.h"
12
13 #include "compiler/translator/StaticType.h"
14 #include "compiler/translator/SymbolTable.h"
15 #include "compiler/translator/tree_util/IntermNode_util.h"
16 #include "compiler/translator/tree_util/IntermTraverse.h"
17 #include "compiler/translator/tree_util/RunAtTheEndOfShader.h"
18
19 namespace sh
20 {
21 namespace
22 {
23 // A traverser that adjusts channel order for various yuv ops.
24 class ReswizzleYUVOpsTraverser : public TIntermTraverser
25 {
26 public:
ReswizzleYUVOpsTraverser(TSymbolTable * symbolTable)27 ReswizzleYUVOpsTraverser(TSymbolTable *symbolTable)
28 : TIntermTraverser(true, false, false, symbolTable)
29 {}
30
31 bool visitAggregate(Visit visit, TIntermAggregate *node) override;
32 bool visitSwizzle(Visit visit, TIntermSwizzle *node) override;
33 bool adjustOutput(TCompiler *compiler, TIntermBlock *root, const TIntermSymbol &yuvOutput);
34
35 private:
36 };
37
38 // OpenGLES and Vulkan has different color component mapping for YUV. OpenGL spec maps R_gl=y,
39 // G_gl=u, B_gl=v, but Vulkan wants R_vulkan=v, G_vulkan=y, B_vulkan=u. We want all calculation to
40 // be in OpenGLES mapping during shader execution, but the actual buffer/image will be stored as
41 // vulkan mapping. This means when we sample from VkImage, we need to map from vulkan order back to
42 // GL order, which comes out to be R_gl=y=G_vulkan=1, G_gl=u=B_vulkan=2, B_gl=v=R_vulkan=0. i.e, {1,
43 // 2, 0, 3}. This function will check if the aggregate is a texture{proj|fetch}(samplerExternal,...)
44 // and if yes it will compose and return a swizzle node.
CheckTextureOpWithSamplerExternal2DY2YAndSwizzle(Visit visit,TIntermAggregate * node)45 TIntermSwizzle *CheckTextureOpWithSamplerExternal2DY2YAndSwizzle(Visit visit,
46 TIntermAggregate *node)
47 {
48 if (visit != Visit::PreVisit)
49 {
50 return nullptr;
51 }
52
53 if (!BuiltInGroup::IsBuiltIn(node->getOp()))
54 {
55 return nullptr;
56 }
57
58 TOperator op = node->getFunction()->getBuiltInOp();
59 if (op == EOpTexture || op == EOpTextureProj || op == EOpTexelFetch)
60 {
61 TIntermSequence *arguments = node->getSequence();
62 TType const &samplerType = (*arguments)[0]->getAsTyped()->getType();
63 if (samplerType.getBasicType() != EbtSamplerExternal2DY2YEXT)
64 {
65 return nullptr;
66 }
67
68 // texture(...).gbra
69 TIntermSwizzle *yuvSwizzle = new TIntermSwizzle(node, {1, 2, 0, 3});
70 return yuvSwizzle;
71 }
72
73 return nullptr;
74 }
75
visitAggregate(Visit visit,TIntermAggregate * node)76 bool ReswizzleYUVOpsTraverser::visitAggregate(Visit visit, TIntermAggregate *node)
77 {
78 TIntermSwizzle *yuvSwizzle = CheckTextureOpWithSamplerExternal2DY2YAndSwizzle(visit, node);
79 if (yuvSwizzle != nullptr)
80 {
81 ASSERT(!getParentNode()->getAsSwizzleNode());
82 queueReplacement(yuvSwizzle, OriginalNode::BECOMES_CHILD);
83 return false;
84 }
85
86 return true;
87 }
88
visitSwizzle(Visit visit,TIntermSwizzle * node)89 bool ReswizzleYUVOpsTraverser::visitSwizzle(Visit visit, TIntermSwizzle *node)
90 {
91 TIntermAggregate *aggregate = node->getOperand()->getAsAggregate();
92 if (aggregate == nullptr)
93 {
94 return true;
95 }
96
97 // There is swizzle on YUV texture sampler, and we need to apply YUV swizzle first and
98 // then followed by the original swizzle. Finally we fold the two swizzles into one.
99 TIntermSwizzle *yuvSwizzle = CheckTextureOpWithSamplerExternal2DY2YAndSwizzle(visit, aggregate);
100 if (yuvSwizzle != nullptr)
101 {
102 TIntermTyped *replacement = new TIntermSwizzle(yuvSwizzle, node->getSwizzleOffsets());
103 replacement = replacement->fold(nullptr);
104 queueReplacement(replacement, OriginalNode::IS_DROPPED);
105 return false;
106 }
107
108 return true;
109 }
110
111 // OpenGLES and Vulkan has different color component mapping for YUV. When we write YUV data, we
112 // need to convert OpenGL mapping to vulkan's mapping, which comes out to be {2, 0, 1, 3}.
adjustOutput(TCompiler * compiler,TIntermBlock * root,const TIntermSymbol & yuvOutput)113 bool ReswizzleYUVOpsTraverser::adjustOutput(TCompiler *compiler,
114 TIntermBlock *root,
115 const TIntermSymbol &yuvOutput)
116 {
117 TIntermBlock *block = new TIntermBlock;
118
119 // output = output.brga
120 TVector<int> swizzle = {2, 0, 1, 3};
121 const int size = yuvOutput.getType().getNominalSize();
122 if (size < 4)
123 {
124 swizzle.resize(size);
125 }
126
127 TIntermTyped *assignment = new TIntermBinary(EOpAssign, yuvOutput.deepCopy(),
128 new TIntermSwizzle(yuvOutput.deepCopy(), swizzle));
129 block->appendStatement(assignment);
130
131 return RunAtTheEndOfShader(compiler, root, block, mSymbolTable);
132 }
133 } // anonymous namespace
134
ReswizzleYUVOps(TCompiler * compiler,TIntermBlock * root,TSymbolTable * symbolTable,const TIntermSymbol * yuvOutput)135 bool ReswizzleYUVOps(TCompiler *compiler,
136 TIntermBlock *root,
137 TSymbolTable *symbolTable,
138 const TIntermSymbol *yuvOutput)
139 {
140 ReswizzleYUVOpsTraverser traverser(symbolTable);
141 root->traverse(&traverser);
142
143 if (!traverser.updateTree(compiler, root))
144 {
145 return false;
146 }
147
148 if (yuvOutput != nullptr && !traverser.adjustOutput(compiler, root, *yuvOutput))
149 {
150 return false;
151 }
152 return true;
153 }
154 } // namespace sh
155