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/OutputVulkanGLSLForMetal.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 // Metal specific driver uniforms
45 constexpr const char kHalfRenderArea[] = "halfRenderArea";
46 constexpr const char kFlipXY[] = "flipXY";
47 constexpr const char kNegFlipXY[] = "negFlipXY";
48 constexpr const char kCoverageMask[] = "coverageMask";
49
50 constexpr ImmutableString kSampleMaskWriteFuncName = ImmutableString("ANGLEWriteSampleMask");
51
52 // Unlike Vulkan having auto viewport flipping extension, in Metal we have to flip gl_Position.y
53 // manually.
54 // This operation performs flipping the gl_Position.y using this expression:
55 // gl_Position.y = gl_Position.y * negViewportScaleY
AppendVertexShaderPositionYCorrectionToMain(TCompiler * compiler,TIntermBlock * root,TSymbolTable * symbolTable,TIntermTyped * negFlipY)56 ANGLE_NO_DISCARD bool AppendVertexShaderPositionYCorrectionToMain(TCompiler *compiler,
57 TIntermBlock *root,
58 TSymbolTable *symbolTable,
59 TIntermTyped *negFlipY)
60 {
61 // Create a symbol reference to "gl_Position"
62 const TVariable *position = BuiltInVariable::gl_Position();
63 TIntermSymbol *positionRef = new TIntermSymbol(position);
64
65 // Create a swizzle to "gl_Position.y"
66 TVector<int> swizzleOffsetY;
67 swizzleOffsetY.push_back(1);
68 TIntermSwizzle *positionY = new TIntermSwizzle(positionRef, swizzleOffsetY);
69
70 // Create the expression "gl_Position.y * negFlipY"
71 TIntermBinary *inverseY = new TIntermBinary(EOpMul, positionY->deepCopy(), negFlipY);
72
73 // Create the assignment "gl_Position.y = gl_Position.y * negViewportScaleY
74 TIntermTyped *positionYLHS = positionY->deepCopy();
75 TIntermBinary *assignment = new TIntermBinary(TOperator::EOpAssign, positionYLHS, inverseY);
76
77 // Append the assignment as a statement at the end of the shader.
78 return RunAtTheEndOfShader(compiler, root, assignment, symbolTable);
79 }
80
81 // Initialize unused varying outputs.
InitializeUnusedOutputs(TIntermBlock * root,TSymbolTable * symbolTable,const InitVariableList & unusedVars)82 ANGLE_NO_DISCARD bool InitializeUnusedOutputs(TIntermBlock *root,
83 TSymbolTable *symbolTable,
84 const InitVariableList &unusedVars)
85 {
86 if (unusedVars.empty())
87 {
88 return true;
89 }
90
91 TIntermSequence insertSequence;
92
93 for (const sh::ShaderVariable &var : unusedVars)
94 {
95 ASSERT(!var.active);
96 const TIntermSymbol *symbol = FindSymbolNode(root, var.name);
97 ASSERT(symbol);
98
99 TIntermSequence initCode;
100 CreateInitCode(symbol, false, false, &initCode, symbolTable);
101
102 insertSequence.insert(insertSequence.end(), initCode.begin(), initCode.end());
103 }
104
105 if (!insertSequence.empty())
106 {
107 TIntermFunctionDefinition *main = FindMain(root);
108 TIntermSequence *mainSequence = main->getBody()->getSequence();
109
110 // Insert init code at the start of main()
111 mainSequence->insert(mainSequence->begin(), insertSequence.begin(), insertSequence.end());
112 }
113
114 return true;
115 }
116 } // anonymous namespace
117
118 // class DriverUniformMetal
createUniformFields(TSymbolTable * symbolTable)119 TFieldList *DriverUniformMetal::createUniformFields(TSymbolTable *symbolTable)
120 {
121 TFieldList *driverFieldList = DriverUniform::createUniformFields(symbolTable);
122
123 constexpr size_t kNumGraphicsDriverUniformsMetal = 4;
124 constexpr std::array<const char *, kNumGraphicsDriverUniformsMetal>
125 kGraphicsDriverUniformNamesMetal = {{kHalfRenderArea, kFlipXY, kNegFlipXY, kCoverageMask}};
126
127 const std::array<TType *, kNumGraphicsDriverUniformsMetal> kDriverUniformTypesMetal = {{
128 new TType(EbtFloat, 2), // halfRenderArea
129 new TType(EbtFloat, 2), // flipXY
130 new TType(EbtFloat, 2), // negFlipXY
131 new TType(EbtUInt), // kCoverageMask
132 }};
133
134 for (size_t uniformIndex = 0; uniformIndex < kNumGraphicsDriverUniformsMetal; ++uniformIndex)
135 {
136 TField *driverUniformField =
137 new TField(kDriverUniformTypesMetal[uniformIndex],
138 ImmutableString(kGraphicsDriverUniformNamesMetal[uniformIndex]),
139 TSourceLoc(), SymbolType::AngleInternal);
140 driverFieldList->push_back(driverUniformField);
141 }
142
143 return driverFieldList;
144 }
145
getHalfRenderAreaRef() const146 TIntermBinary *DriverUniformMetal::getHalfRenderAreaRef() const
147 {
148 return createDriverUniformRef(kHalfRenderArea);
149 }
150
getFlipXYRef() const151 TIntermBinary *DriverUniformMetal::getFlipXYRef() const
152 {
153 return createDriverUniformRef(kFlipXY);
154 }
155
getNegFlipXYRef() const156 TIntermBinary *DriverUniformMetal::getNegFlipXYRef() const
157 {
158 return createDriverUniformRef(kNegFlipXY);
159 }
160
getNegFlipYRef() const161 TIntermSwizzle *DriverUniformMetal::getNegFlipYRef() const
162 {
163 // Create a swizzle to "negFlipXY.y"
164 TIntermBinary *negFlipXY = createDriverUniformRef(kNegFlipXY);
165 TVector<int> swizzleOffsetY = {1};
166 TIntermSwizzle *negFlipY = new TIntermSwizzle(negFlipXY, swizzleOffsetY);
167 return negFlipY;
168 }
169
getCoverageMaskFieldRef() const170 TIntermBinary *DriverUniformMetal::getCoverageMaskFieldRef() const
171 {
172 return createDriverUniformRef(kCoverageMask);
173 }
174
TranslatorMetal(sh::GLenum type,ShShaderSpec spec)175 TranslatorMetal::TranslatorMetal(sh::GLenum type, ShShaderSpec spec) : TranslatorVulkan(type, spec)
176 {}
177
translate(TIntermBlock * root,ShCompileOptions compileOptions,PerformanceDiagnostics * perfDiagnostics)178 bool TranslatorMetal::translate(TIntermBlock *root,
179 ShCompileOptions compileOptions,
180 PerformanceDiagnostics *perfDiagnostics)
181 {
182 TInfoSinkBase sink;
183
184 SpecConstMetal specConst(&getSymbolTable(), compileOptions, getShaderType());
185 DriverUniformMetal driverUniforms;
186 if (!TranslatorVulkan::translateImpl(sink, root, compileOptions, perfDiagnostics, &specConst,
187 &driverUniforms))
188 {
189 return false;
190 }
191
192 // Replace array of matrix varyings
193 if (!ReplaceArrayOfMatrixVaryings(this, root, &getSymbolTable()))
194 {
195 return false;
196 }
197
198 if (getShaderType() == GL_VERTEX_SHADER)
199 {
200 TIntermTyped *negFlipY = driverUniforms.getNegFlipYRef();
201
202 // Append gl_Position.y correction to main
203 if (!AppendVertexShaderPositionYCorrectionToMain(this, root, &getSymbolTable(), negFlipY))
204 {
205 return false;
206 }
207
208 // Insert rasterizer discard logic
209 if (!insertRasterizerDiscardLogic(sink, root))
210 {
211 return false;
212 }
213 }
214 else if (getShaderType() == GL_FRAGMENT_SHADER)
215 {
216 if (!insertSampleMaskWritingLogic(sink, root, &driverUniforms))
217 {
218 return false;
219 }
220 }
221
222 // Initialize unused varying outputs to avoid spirv-cross dead-code removing them in later
223 // stage. Only do this if SH_INIT_OUTPUT_VARIABLES is not specified.
224 if ((getShaderType() == GL_VERTEX_SHADER || getShaderType() == GL_GEOMETRY_SHADER_EXT) &&
225 (compileOptions & SH_INIT_OUTPUT_VARIABLES) == 0)
226 {
227 InitVariableList list;
228 for (const sh::ShaderVariable &var : mOutputVaryings)
229 {
230 if (!var.active)
231 {
232 list.push_back(var);
233 }
234 }
235
236 if (!InitializeUnusedOutputs(root, &getSymbolTable(), list))
237 {
238 return false;
239 }
240 }
241
242 // Write translated shader.
243 TOutputVulkanGLSL outputGLSL(sink, getHashFunction(), getNameMap(), &getSymbolTable(),
244 getShaderType(), getShaderVersion(), getOutputType(), false, true,
245 compileOptions);
246 root->traverse(&outputGLSL);
247
248 return compileToSpirv(sink);
249 }
250
251 // Metal needs to inverse the depth if depthRange is is reverse order, i.e. depth near > depth far
252 // This is achieved by multiply the depth value with scale value stored in
253 // driver uniform's depthRange.reserved
transformDepthBeforeCorrection(TIntermBlock * root,const DriverUniform * driverUniforms)254 bool TranslatorMetal::transformDepthBeforeCorrection(TIntermBlock *root,
255 const DriverUniform *driverUniforms)
256 {
257 // Create a symbol reference to "gl_Position"
258 const TVariable *position = BuiltInVariable::gl_Position();
259 TIntermSymbol *positionRef = new TIntermSymbol(position);
260
261 // Create a swizzle to "gl_Position.z"
262 TVector<int> swizzleOffsetZ = {2};
263 TIntermSwizzle *positionZ = new TIntermSwizzle(positionRef, swizzleOffsetZ);
264
265 // Create a ref to "depthRange.reserved"
266 TIntermBinary *viewportZScale = driverUniforms->getDepthRangeReservedFieldRef();
267
268 // Create the expression "gl_Position.z * depthRange.reserved".
269 TIntermBinary *zScale = new TIntermBinary(EOpMul, positionZ->deepCopy(), viewportZScale);
270
271 // Create the assignment "gl_Position.z = gl_Position.z * depthRange.reserved"
272 TIntermTyped *positionZLHS = positionZ->deepCopy();
273 TIntermBinary *assignment = new TIntermBinary(TOperator::EOpAssign, positionZLHS, zScale);
274
275 // Append the assignment as a statement at the end of the shader.
276 return RunAtTheEndOfShader(this, root, assignment, &getSymbolTable());
277 }
278
279 // Add sample_mask writing to main, guarded by the specialization constant
280 // kCoverageMaskEnabledConstName
insertSampleMaskWritingLogic(TInfoSinkBase & sink,TIntermBlock * root,const DriverUniformMetal * driverUniforms)281 ANGLE_NO_DISCARD bool TranslatorMetal::insertSampleMaskWritingLogic(
282 TInfoSinkBase &sink,
283 TIntermBlock *root,
284 const DriverUniformMetal *driverUniforms)
285 {
286 // This transformation leaves the tree in an inconsistent state by using a variable that's
287 // defined in text, outside of the knowledge of the AST.
288 mValidateASTOptions.validateVariableReferences = false;
289
290 TSymbolTable *symbolTable = &getSymbolTable();
291
292 // Insert coverageMaskEnabled specialization constant and sample_mask writing function.
293 sink << "layout (constant_id=0) const bool " << mtl::kCoverageMaskEnabledConstName;
294 sink << " = false;\n";
295 sink << "void " << kSampleMaskWriteFuncName << "(uint mask)\n";
296 sink << "{\n";
297 sink << " if (" << mtl::kCoverageMaskEnabledConstName << ")\n";
298 sink << " {\n";
299 sink << " gl_SampleMask[0] = int(mask);\n";
300 sink << " }\n";
301 sink << "}\n";
302
303 // Create kCoverageMaskEnabledConstName and kSampleMaskWriteFuncName variable references.
304 TType *boolType = new TType(EbtBool);
305 boolType->setQualifier(EvqConst);
306 TVariable *coverageMaskEnabledVar =
307 new TVariable(symbolTable, ImmutableString(mtl::kCoverageMaskEnabledConstName), boolType,
308 SymbolType::AngleInternal);
309
310 TFunction *sampleMaskWriteFunc =
311 new TFunction(symbolTable, kSampleMaskWriteFuncName, SymbolType::AngleInternal,
312 StaticType::GetBasic<EbtVoid>(), false);
313
314 TType *uintType = new TType(EbtUInt);
315 TVariable *maskArg =
316 new TVariable(symbolTable, ImmutableString("mask"), uintType, SymbolType::AngleInternal);
317 sampleMaskWriteFunc->addParameter(maskArg);
318
319 // coverageMask
320 TIntermBinary *coverageMask = driverUniforms->getCoverageMaskFieldRef();
321
322 // Insert this code to the end of main()
323 // if (ANGLECoverageMaskEnabled)
324 // {
325 // ANGLEWriteSampleMask(ANGLEUniforms.coverageMask);
326 // }
327 TIntermSequence args;
328 args.push_back(coverageMask);
329 TIntermAggregate *callSampleMaskWriteFunc =
330 TIntermAggregate::CreateFunctionCall(*sampleMaskWriteFunc, &args);
331 TIntermBlock *callBlock = new TIntermBlock;
332 callBlock->appendStatement(callSampleMaskWriteFunc);
333
334 TIntermSymbol *coverageMaskEnabled = new TIntermSymbol(coverageMaskEnabledVar);
335 TIntermIfElse *ifCall = new TIntermIfElse(coverageMaskEnabled, callBlock, nullptr);
336
337 return RunAtTheEndOfShader(this, root, ifCall, symbolTable);
338 }
339
insertRasterizerDiscardLogic(TInfoSinkBase & sink,TIntermBlock * root)340 ANGLE_NO_DISCARD bool TranslatorMetal::insertRasterizerDiscardLogic(TInfoSinkBase &sink,
341 TIntermBlock *root)
342 {
343 // This transformation leaves the tree in an inconsistent state by using a variable that's
344 // defined in text, outside of the knowledge of the AST.
345 mValidateASTOptions.validateVariableReferences = false;
346
347 TSymbolTable *symbolTable = &getSymbolTable();
348
349 // Insert rasterizationDisabled specialization constant.
350 sink << "layout (constant_id=0) const bool " << mtl::kRasterizerDiscardEnabledConstName;
351 sink << " = false;\n";
352
353 // Create kRasterizerDiscardEnabledConstName variable reference.
354 TType *boolType = new TType(EbtBool);
355 boolType->setQualifier(EvqConst);
356 TVariable *discardEnabledVar =
357 new TVariable(symbolTable, ImmutableString(mtl::kRasterizerDiscardEnabledConstName),
358 boolType, SymbolType::AngleInternal);
359
360 // Insert this code to the end of main()
361 // if (ANGLERasterizerDisabled)
362 // {
363 // gl_Position = vec4(-3.0, -3.0, -3.0, 1.0);
364 // }
365 // Create a symbol reference to "gl_Position"
366 const TVariable *position = BuiltInVariable::gl_Position();
367 TIntermSymbol *positionRef = new TIntermSymbol(position);
368
369 // Create vec4(-3, -3, -3, 1):
370 auto vec4Type = new TType(EbtFloat, 4);
371 TIntermSequence vec4Args;
372 vec4Args.push_back(CreateFloatNode(-3.0f));
373 vec4Args.push_back(CreateFloatNode(-3.0f));
374 vec4Args.push_back(CreateFloatNode(-3.0f));
375 vec4Args.push_back(CreateFloatNode(1.0f));
376 TIntermAggregate *constVarConstructor =
377 TIntermAggregate::CreateConstructor(*vec4Type, &vec4Args);
378
379 // Create the assignment "gl_Position = vec4(-3, -3, -3, 1)"
380 TIntermBinary *assignment =
381 new TIntermBinary(TOperator::EOpAssign, positionRef->deepCopy(), constVarConstructor);
382
383 TIntermBlock *discardBlock = new TIntermBlock;
384 discardBlock->appendStatement(assignment);
385
386 TIntermSymbol *discardEnabled = new TIntermSymbol(discardEnabledVar);
387 TIntermIfElse *ifCall = new TIntermIfElse(discardEnabled, discardBlock, nullptr);
388
389 return RunAtTheEndOfShader(this, root, ifCall, symbolTable);
390 }
391
392 } // namespace sh
393