1 //
2 // Copyright 2019 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 // TranslatorMetal:
7 // A GLSL-based translator that outputs shaders that fit GL_KHR_vulkan_glsl.
8 // It takes into account some considerations for Metal backend also.
9 // The shaders are then fed into glslang to spit out SPIR-V.
10 // See: https://www.khronos.org/registry/vulkan/specs/misc/GL_KHR_vulkan_glsl.txt
11 //
12 // The SPIR-V will then be translated to Metal Shading Language later in Metal backend.
13 //
14
15 #include "compiler/translator/TranslatorMetal.h"
16
17 #include "angle_gl.h"
18 #include "common/utilities.h"
19 #include "compiler/translator/OutputVulkanGLSL.h"
20 #include "compiler/translator/StaticType.h"
21 #include "compiler/translator/tree_ops/InitializeVariables.h"
22 #include "compiler/translator/tree_util/BuiltIn.h"
23 #include "compiler/translator/tree_util/DriverUniform.h"
24 #include "compiler/translator/tree_util/FindMain.h"
25 #include "compiler/translator/tree_util/FindSymbolNode.h"
26 #include "compiler/translator/tree_util/IntermNode_util.h"
27 #include "compiler/translator/tree_util/ReplaceArrayOfMatrixVarying.h"
28 #include "compiler/translator/tree_util/ReplaceVariable.h"
29 #include "compiler/translator/tree_util/RunAtTheEndOfShader.h"
30 #include "compiler/translator/util.h"
31
32 namespace sh
33 {
34
35 namespace mtl
36 {
37 /** extern */
38 const char kCoverageMaskEnabledConstName[] = "ANGLECoverageMaskEnabled";
39 const char kRasterizerDiscardEnabledConstName[] = "ANGLERasterizerDisabled";
40 } // namespace mtl
41
42 namespace
43 {
44
45 constexpr ImmutableString kSampleMaskWriteFuncName = ImmutableString("ANGLEWriteSampleMask");
46
47 // Unlike Vulkan having auto viewport flipping extension, in Metal we have to flip gl_Position.y
48 // manually.
49 // This operation performs flipping the gl_Position.y using this expression:
50 // gl_Position.y = gl_Position.y * negViewportScaleY
AppendVertexShaderPositionYCorrectionToMain(TCompiler * compiler,TIntermBlock * root,TSymbolTable * symbolTable,TIntermTyped * negFlipY)51 ANGLE_NO_DISCARD bool AppendVertexShaderPositionYCorrectionToMain(TCompiler *compiler,
52 TIntermBlock *root,
53 TSymbolTable *symbolTable,
54 TIntermTyped *negFlipY)
55 {
56 // Create a symbol reference to "gl_Position"
57 const TVariable *position = BuiltInVariable::gl_Position();
58 TIntermSymbol *positionRef = new TIntermSymbol(position);
59
60 // Create a swizzle to "gl_Position.y"
61 TVector<int> swizzleOffsetY;
62 swizzleOffsetY.push_back(1);
63 TIntermSwizzle *positionY = new TIntermSwizzle(positionRef, swizzleOffsetY);
64
65 // Create the expression "gl_Position.y * negFlipY"
66 TIntermBinary *inverseY = new TIntermBinary(EOpMul, positionY->deepCopy(), negFlipY);
67
68 // Create the assignment "gl_Position.y = gl_Position.y * negViewportScaleY
69 TIntermTyped *positionYLHS = positionY->deepCopy();
70 TIntermBinary *assignment = new TIntermBinary(TOperator::EOpAssign, positionYLHS, inverseY);
71
72 // Append the assignment as a statement at the end of the shader.
73 return RunAtTheEndOfShader(compiler, root, assignment, symbolTable);
74 }
75
76 // Initialize unused varying outputs.
InitializeUnusedOutputs(TIntermBlock * root,TSymbolTable * symbolTable,const InitVariableList & unusedVars)77 ANGLE_NO_DISCARD bool InitializeUnusedOutputs(TIntermBlock *root,
78 TSymbolTable *symbolTable,
79 const InitVariableList &unusedVars)
80 {
81 if (unusedVars.empty())
82 {
83 return true;
84 }
85
86 TIntermSequence insertSequence;
87
88 for (const sh::ShaderVariable &var : unusedVars)
89 {
90 ASSERT(!var.active);
91 const TIntermSymbol *symbol = FindSymbolNode(root, var.name);
92 ASSERT(symbol);
93
94 TIntermSequence initCode;
95 CreateInitCode(symbol, false, false, &initCode, symbolTable);
96
97 insertSequence.insert(insertSequence.end(), initCode.begin(), initCode.end());
98 }
99
100 if (!insertSequence.empty())
101 {
102 TIntermFunctionDefinition *main = FindMain(root);
103 TIntermSequence *mainSequence = main->getBody()->getSequence();
104
105 // Insert init code at the start of main()
106 mainSequence->insert(mainSequence->begin(), insertSequence.begin(), insertSequence.end());
107 }
108
109 return true;
110 }
111 } // anonymous namespace
112
TranslatorMetal(sh::GLenum type,ShShaderSpec spec)113 TranslatorMetal::TranslatorMetal(sh::GLenum type, ShShaderSpec spec) : TranslatorVulkan(type, spec)
114 {}
115
translate(TIntermBlock * root,ShCompileOptions compileOptions,PerformanceDiagnostics * perfDiagnostics)116 bool TranslatorMetal::translate(TIntermBlock *root,
117 ShCompileOptions compileOptions,
118 PerformanceDiagnostics *perfDiagnostics)
119 {
120 TInfoSinkBase sink;
121
122 SpecConstMetal specConst(&getSymbolTable(), compileOptions, getShaderType());
123 DriverUniformMetal driverUniforms;
124 if (!TranslatorVulkan::translateImpl(sink, root, compileOptions, perfDiagnostics, &specConst,
125 &driverUniforms))
126 {
127 return false;
128 }
129
130 // Replace array of matrix varyings
131 if (!ReplaceArrayOfMatrixVaryings(this, root, &getSymbolTable()))
132 {
133 return false;
134 }
135
136 if (getShaderType() == GL_VERTEX_SHADER)
137 {
138 TIntermTyped *negFlipY = driverUniforms.getNegFlipYRef();
139
140 // Append gl_Position.y correction to main
141 if (!AppendVertexShaderPositionYCorrectionToMain(this, root, &getSymbolTable(), negFlipY))
142 {
143 return false;
144 }
145
146 // Insert rasterizer discard logic
147 if (!insertRasterizerDiscardLogic(sink, root))
148 {
149 return false;
150 }
151 }
152 else if (getShaderType() == GL_FRAGMENT_SHADER)
153 {
154 if (!insertSampleMaskWritingLogic(sink, root, &driverUniforms))
155 {
156 return false;
157 }
158 }
159
160 // Initialize unused varying outputs to avoid spirv-cross dead-code removing them in later
161 // stage. Only do this if SH_INIT_OUTPUT_VARIABLES is not specified.
162 if ((getShaderType() == GL_VERTEX_SHADER || getShaderType() == GL_GEOMETRY_SHADER_EXT) &&
163 (compileOptions & SH_INIT_OUTPUT_VARIABLES) == 0)
164 {
165 InitVariableList list;
166 for (const sh::ShaderVariable &var : mOutputVaryings)
167 {
168 if (!var.active)
169 {
170 list.push_back(var);
171 }
172 }
173
174 if (!InitializeUnusedOutputs(root, &getSymbolTable(), list))
175 {
176 return false;
177 }
178 }
179
180 // Write translated shader.
181 TOutputVulkanGLSL outputGLSL(this, sink, true, compileOptions);
182 root->traverse(&outputGLSL);
183
184 return compileToSpirv(sink);
185 }
186
187 // Metal needs to inverse the depth if depthRange is is reverse order, i.e. depth near > depth far
188 // This is achieved by multiply the depth value with scale value stored in
189 // driver uniform's depthRange.reserved
transformDepthBeforeCorrection(TIntermBlock * root,const DriverUniform * driverUniforms)190 bool TranslatorMetal::transformDepthBeforeCorrection(TIntermBlock *root,
191 const DriverUniform *driverUniforms)
192 {
193 // Create a symbol reference to "gl_Position"
194 const TVariable *position = BuiltInVariable::gl_Position();
195 TIntermSymbol *positionRef = new TIntermSymbol(position);
196
197 // Create a swizzle to "gl_Position.z"
198 TVector<int> swizzleOffsetZ = {2};
199 TIntermSwizzle *positionZ = new TIntermSwizzle(positionRef, swizzleOffsetZ);
200
201 // Create a ref to "depthRange.reserved"
202 TIntermBinary *viewportZScale = driverUniforms->getDepthRangeReservedFieldRef();
203
204 // Create the expression "gl_Position.z * depthRange.reserved".
205 TIntermBinary *zScale = new TIntermBinary(EOpMul, positionZ->deepCopy(), viewportZScale);
206
207 // Create the assignment "gl_Position.z = gl_Position.z * depthRange.reserved"
208 TIntermTyped *positionZLHS = positionZ->deepCopy();
209 TIntermBinary *assignment = new TIntermBinary(TOperator::EOpAssign, positionZLHS, zScale);
210
211 // Append the assignment as a statement at the end of the shader.
212 return RunAtTheEndOfShader(this, root, assignment, &getSymbolTable());
213 }
214
215 // Add sample_mask writing to main, guarded by the specialization constant
216 // kCoverageMaskEnabledConstName
insertSampleMaskWritingLogic(TInfoSinkBase & sink,TIntermBlock * root,const DriverUniformMetal * driverUniforms)217 ANGLE_NO_DISCARD bool TranslatorMetal::insertSampleMaskWritingLogic(
218 TInfoSinkBase &sink,
219 TIntermBlock *root,
220 const DriverUniformMetal *driverUniforms)
221 {
222 // This transformation leaves the tree in an inconsistent state by using a variable that's
223 // defined in text, outside of the knowledge of the AST. Same with defining the function in
224 // text.
225 mValidateASTOptions.validateVariableReferences = false;
226 mValidateASTOptions.validateFunctionCall = false;
227
228 TSymbolTable *symbolTable = &getSymbolTable();
229
230 // Insert coverageMaskEnabled specialization constant and sample_mask writing function.
231 sink << "layout (constant_id=0) const bool " << mtl::kCoverageMaskEnabledConstName;
232 sink << " = false;\n";
233 sink << "void " << kSampleMaskWriteFuncName << "(uint mask)\n";
234 sink << "{\n";
235 sink << " if (" << mtl::kCoverageMaskEnabledConstName << ")\n";
236 sink << " {\n";
237 sink << " gl_SampleMask[0] = int(mask);\n";
238 sink << " }\n";
239 sink << "}\n";
240
241 // Create kCoverageMaskEnabledConstName and kSampleMaskWriteFuncName variable references.
242 TType *boolType = new TType(EbtBool);
243 boolType->setQualifier(EvqConst);
244 TVariable *coverageMaskEnabledVar =
245 new TVariable(symbolTable, ImmutableString(mtl::kCoverageMaskEnabledConstName), boolType,
246 SymbolType::AngleInternal);
247
248 TFunction *sampleMaskWriteFunc =
249 new TFunction(symbolTable, kSampleMaskWriteFuncName, SymbolType::AngleInternal,
250 StaticType::GetBasic<EbtVoid, EbpUndefined>(), false);
251
252 TType *uintType = new TType(EbtUInt);
253 TVariable *maskArg =
254 new TVariable(symbolTable, ImmutableString("mask"), uintType, SymbolType::AngleInternal);
255 sampleMaskWriteFunc->addParameter(maskArg);
256
257 // coverageMask
258 TIntermBinary *coverageMask = driverUniforms->getCoverageMaskFieldRef();
259
260 // Insert this code to the end of main()
261 // if (ANGLECoverageMaskEnabled)
262 // {
263 // ANGLEWriteSampleMask(ANGLEUniforms.coverageMask);
264 // }
265 TIntermSequence args;
266 args.push_back(coverageMask);
267 TIntermAggregate *callSampleMaskWriteFunc =
268 TIntermAggregate::CreateFunctionCall(*sampleMaskWriteFunc, &args);
269 TIntermBlock *callBlock = new TIntermBlock;
270 callBlock->appendStatement(callSampleMaskWriteFunc);
271
272 TIntermSymbol *coverageMaskEnabled = new TIntermSymbol(coverageMaskEnabledVar);
273 TIntermIfElse *ifCall = new TIntermIfElse(coverageMaskEnabled, callBlock, nullptr);
274
275 return RunAtTheEndOfShader(this, root, ifCall, symbolTable);
276 }
277
insertRasterizerDiscardLogic(TInfoSinkBase & sink,TIntermBlock * root)278 ANGLE_NO_DISCARD bool TranslatorMetal::insertRasterizerDiscardLogic(TInfoSinkBase &sink,
279 TIntermBlock *root)
280 {
281 // This transformation leaves the tree in an inconsistent state by using a variable that's
282 // defined in text, outside of the knowledge of the AST.
283 mValidateASTOptions.validateVariableReferences = false;
284
285 TSymbolTable *symbolTable = &getSymbolTable();
286
287 // Insert rasterizationDisabled specialization constant.
288 sink << "layout (constant_id=0) const bool " << mtl::kRasterizerDiscardEnabledConstName;
289 sink << " = false;\n";
290
291 // Create kRasterizerDiscardEnabledConstName variable reference.
292 TType *boolType = new TType(EbtBool);
293 boolType->setQualifier(EvqConst);
294 TVariable *discardEnabledVar =
295 new TVariable(symbolTable, ImmutableString(mtl::kRasterizerDiscardEnabledConstName),
296 boolType, SymbolType::AngleInternal);
297
298 // Insert this code to the end of main()
299 // if (ANGLERasterizerDisabled)
300 // {
301 // gl_Position = vec4(-3.0, -3.0, -3.0, 1.0);
302 // }
303 // Create a symbol reference to "gl_Position"
304 const TVariable *position = BuiltInVariable::gl_Position();
305 TIntermSymbol *positionRef = new TIntermSymbol(position);
306
307 // Create vec4(-3, -3, -3, 1):
308 auto vec4Type = new TType(EbtFloat, 4);
309 TIntermSequence vec4Args;
310 vec4Args.push_back(CreateFloatNode(-3.0f, EbpMedium));
311 vec4Args.push_back(CreateFloatNode(-3.0f, EbpMedium));
312 vec4Args.push_back(CreateFloatNode(-3.0f, EbpMedium));
313 vec4Args.push_back(CreateFloatNode(1.0f, EbpMedium));
314 TIntermAggregate *constVarConstructor =
315 TIntermAggregate::CreateConstructor(*vec4Type, &vec4Args);
316
317 // Create the assignment "gl_Position = vec4(-3, -3, -3, 1)"
318 TIntermBinary *assignment =
319 new TIntermBinary(TOperator::EOpAssign, positionRef->deepCopy(), constVarConstructor);
320
321 TIntermBlock *discardBlock = new TIntermBlock;
322 discardBlock->appendStatement(assignment);
323
324 TIntermSymbol *discardEnabled = new TIntermSymbol(discardEnabledVar);
325 TIntermIfElse *ifCall = new TIntermIfElse(discardEnabled, discardBlock, nullptr);
326
327 return RunAtTheEndOfShader(this, root, ifCall, symbolTable);
328 }
329
330 } // namespace sh
331