blob: 98ba3b132317d71bb52f48b2e0af7685b4430c93 [file] [log] [blame]
//
// Copyright 2016 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.
//
// TranslatorVulkan:
// A GLSL-based translator that outputs shaders that fit GL_KHR_vulkan_glsl.
// The shaders are then fed into glslang to spit out SPIR-V (libANGLE-side).
// See: https://www.khronos.org/registry/vulkan/specs/misc/GL_KHR_vulkan_glsl.txt
//
#include "compiler/translator/TranslatorVulkan.h"
#include "angle_gl.h"
#include "common/utilities.h"
#include "compiler/translator/BuiltinsWorkaroundGLSL.h"
#include "compiler/translator/ImmutableStringBuilder.h"
#include "compiler/translator/OutputVulkanGLSL.h"
#include "compiler/translator/StaticType.h"
#include "compiler/translator/tree_ops/NameEmbeddedUniformStructs.h"
#include "compiler/translator/tree_ops/NameNamelessUniformBuffers.h"
#include "compiler/translator/tree_ops/RewriteAtomicCounters.h"
#include "compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.h"
#include "compiler/translator/tree_ops/RewriteDfdy.h"
#include "compiler/translator/tree_ops/RewriteRowMajorMatrices.h"
#include "compiler/translator/tree_ops/RewriteStructSamplers.h"
#include "compiler/translator/tree_util/BuiltIn.h"
#include "compiler/translator/tree_util/FindFunction.h"
#include "compiler/translator/tree_util/FindMain.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/ReplaceVariable.h"
#include "compiler/translator/tree_util/RunAtTheEndOfShader.h"
#include "compiler/translator/util.h"
namespace sh
{
namespace
{
// This traverses nodes, find the struct ones and add their declarations to the sink. It also
// removes the nodes from the tree as it processes them.
class DeclareStructTypesTraverser : public TIntermTraverser
{
public:
explicit DeclareStructTypesTraverser(TOutputVulkanGLSL *outputVulkanGLSL)
: TIntermTraverser(true, false, false), mOutputVulkanGLSL(outputVulkanGLSL)
{}
bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
{
ASSERT(visit == PreVisit);
if (!mInGlobalScope)
{
return false;
}
const TIntermSequence &sequence = *(node->getSequence());
TIntermTyped *declarator = sequence.front()->getAsTyped();
const TType &type = declarator->getType();
if (type.isStructSpecifier())
{
const TStructure *structure = type.getStruct();
// Embedded structs should be parsed away by now.
ASSERT(structure->symbolType() != SymbolType::Empty);
mOutputVulkanGLSL->writeStructType(structure);
TIntermSymbol *symbolNode = declarator->getAsSymbolNode();
if (symbolNode && symbolNode->variable().symbolType() == SymbolType::Empty)
{
// Remove the struct specifier declaration from the tree so it isn't parsed again.
TIntermSequence emptyReplacement;
mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
emptyReplacement);
}
}
return false;
}
private:
TOutputVulkanGLSL *mOutputVulkanGLSL;
};
class DeclareDefaultUniformsTraverser : public TIntermTraverser
{
public:
DeclareDefaultUniformsTraverser(TInfoSinkBase *sink,
ShHashFunction64 hashFunction,
NameMap *nameMap)
: TIntermTraverser(true, true, true),
mSink(sink),
mHashFunction(hashFunction),
mNameMap(nameMap),
mInDefaultUniform(false)
{}
bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
{
const TIntermSequence &sequence = *(node->getSequence());
// TODO(jmadill): Compound declarations.
ASSERT(sequence.size() == 1);
TIntermTyped *variable = sequence.front()->getAsTyped();
const TType &type = variable->getType();
bool isUniform = type.getQualifier() == EvqUniform && !type.isInterfaceBlock() &&
!IsOpaqueType(type.getBasicType());
if (visit == PreVisit)
{
if (isUniform)
{
(*mSink) << " " << GetTypeName(type, mHashFunction, mNameMap) << " ";
mInDefaultUniform = true;
}
}
else if (visit == InVisit)
{
mInDefaultUniform = isUniform;
}
else if (visit == PostVisit)
{
if (isUniform)
{
(*mSink) << ";\n";
// Remove the uniform declaration from the tree so it isn't parsed again.
TIntermSequence emptyReplacement;
mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
emptyReplacement);
}
mInDefaultUniform = false;
}
return true;
}
void visitSymbol(TIntermSymbol *symbol) override
{
if (mInDefaultUniform)
{
const ImmutableString &name = symbol->variable().name();
ASSERT(!name.beginsWith("gl_"));
(*mSink) << HashName(&symbol->variable(), mHashFunction, mNameMap)
<< ArrayString(symbol->getType());
}
}
private:
TInfoSinkBase *mSink;
ShHashFunction64 mHashFunction;
NameMap *mNameMap;
bool mInDefaultUniform;
};
constexpr ImmutableString kFlippedPointCoordName = ImmutableString("flippedPointCoord");
constexpr ImmutableString kFlippedFragCoordName = ImmutableString("flippedFragCoord");
constexpr ImmutableString kEmulatedDepthRangeParams = ImmutableString("ANGLEDepthRangeParams");
constexpr ImmutableString kUniformsBlockName = ImmutableString("ANGLEUniformBlock");
constexpr ImmutableString kUniformsVarName = ImmutableString("ANGLEUniforms");
constexpr const char kViewport[] = "viewport";
constexpr const char kHalfRenderAreaHeight[] = "halfRenderAreaHeight";
constexpr const char kViewportYScale[] = "viewportYScale";
constexpr const char kNegViewportYScale[] = "negViewportYScale";
constexpr const char kXfbActiveUnpaused[] = "xfbActiveUnpaused";
constexpr const char kXfbBufferOffsets[] = "xfbBufferOffsets";
constexpr const char kAcbBufferOffsets[] = "acbBufferOffsets";
constexpr const char kDepthRange[] = "depthRange";
constexpr size_t kNumGraphicsDriverUniforms = 8;
constexpr std::array<const char *, kNumGraphicsDriverUniforms> kGraphicsDriverUniformNames = {
{kViewport, kHalfRenderAreaHeight, kViewportYScale, kNegViewportYScale, kXfbActiveUnpaused,
kXfbBufferOffsets, kAcbBufferOffsets, kDepthRange}};
constexpr size_t kNumComputeDriverUniforms = 1;
constexpr std::array<const char *, kNumComputeDriverUniforms> kComputeDriverUniformNames = {
{kAcbBufferOffsets}};
size_t FindFieldIndex(const TFieldList &fieldList, const char *fieldName)
{
for (size_t fieldIndex = 0; fieldIndex < fieldList.size(); ++fieldIndex)
{
if (strcmp(fieldList[fieldIndex]->name().data(), fieldName) == 0)
{
return fieldIndex;
}
}
UNREACHABLE();
return 0;
}
TIntermBinary *CreateDriverUniformRef(const TVariable *driverUniforms, const char *fieldName)
{
size_t fieldIndex =
FindFieldIndex(driverUniforms->getType().getInterfaceBlock()->fields(), fieldName);
TIntermSymbol *angleUniformsRef = new TIntermSymbol(driverUniforms);
TConstantUnion *uniformIndex = new TConstantUnion;
uniformIndex->setIConst(static_cast<int>(fieldIndex));
TIntermConstantUnion *indexRef =
new TIntermConstantUnion(uniformIndex, *StaticType::GetBasic<EbtInt>());
return new TIntermBinary(EOpIndexDirectInterfaceBlock, angleUniformsRef, indexRef);
}
// Replaces a builtin variable with a version that corrects the Y coordinate.
ANGLE_NO_DISCARD bool FlipBuiltinVariable(TCompiler *compiler,
TIntermBlock *root,
TIntermSequence *insertSequence,
TIntermTyped *viewportYScale,
TSymbolTable *symbolTable,
const TVariable *builtin,
const ImmutableString &flippedVariableName,
TIntermTyped *pivot)
{
// Create a symbol reference to 'builtin'.
TIntermSymbol *builtinRef = new TIntermSymbol(builtin);
// Create a swizzle to "builtin.y"
TVector<int> swizzleOffsetY = {1};
TIntermSwizzle *builtinY = new TIntermSwizzle(builtinRef, swizzleOffsetY);
// Create a symbol reference to our new variable that will hold the modified builtin.
const TType *type = StaticType::GetForVec<EbtFloat>(
EvqGlobal, static_cast<unsigned char>(builtin->getType().getNominalSize()));
TVariable *replacementVar =
new TVariable(symbolTable, flippedVariableName, type, SymbolType::AngleInternal);
DeclareGlobalVariable(root, replacementVar);
TIntermSymbol *flippedBuiltinRef = new TIntermSymbol(replacementVar);
// Use this new variable instead of 'builtin' everywhere.
if (!ReplaceVariable(compiler, root, builtin, replacementVar))
{
return false;
}
// Create the expression "(builtin.y - pivot) * viewportYScale + pivot
TIntermBinary *removePivot = new TIntermBinary(EOpSub, builtinY, pivot);
TIntermBinary *inverseY = new TIntermBinary(EOpMul, removePivot, viewportYScale);
TIntermBinary *plusPivot = new TIntermBinary(EOpAdd, inverseY, pivot->deepCopy());
// Create the corrected variable and copy the value of the original builtin.
TIntermSequence *sequence = new TIntermSequence();
sequence->push_back(builtinRef->deepCopy());
TIntermAggregate *aggregate = TIntermAggregate::CreateConstructor(builtin->getType(), sequence);
TIntermBinary *assignment = new TIntermBinary(EOpInitialize, flippedBuiltinRef, aggregate);
// Create an assignment to the replaced variable's y.
TIntermSwizzle *correctedY = new TIntermSwizzle(flippedBuiltinRef->deepCopy(), swizzleOffsetY);
TIntermBinary *assignToY = new TIntermBinary(EOpAssign, correctedY, plusPivot);
// Add this assigment at the beginning of the main function
insertSequence->insert(insertSequence->begin(), assignToY);
insertSequence->insert(insertSequence->begin(), assignment);
return compiler->validateAST(root);
}
TIntermSequence *GetMainSequence(TIntermBlock *root)
{
TIntermFunctionDefinition *main = FindMain(root);
return main->getBody()->getSequence();
}
// Declares a new variable to replace gl_DepthRange, its values are fed from a driver uniform.
ANGLE_NO_DISCARD bool ReplaceGLDepthRangeWithDriverUniform(TCompiler *compiler,
TIntermBlock *root,
const TVariable *driverUniforms,
TSymbolTable *symbolTable)
{
// Create a symbol reference to "gl_DepthRange"
const TVariable *depthRangeVar = static_cast<const TVariable *>(
symbolTable->findBuiltIn(ImmutableString("gl_DepthRange"), 0));
// ANGLEUniforms.depthRange
TIntermBinary *angleEmulatedDepthRangeRef = CreateDriverUniformRef(driverUniforms, kDepthRange);
// Use this variable instead of gl_DepthRange everywhere.
return ReplaceVariableWithTyped(compiler, root, depthRangeVar, angleEmulatedDepthRangeRef);
}
// This operation performs the viewport depth translation needed by Vulkan. In GL the viewport
// transformation is slightly different - see the GL 2.0 spec section "2.12.1 Controlling the
// Viewport". In Vulkan the corresponding spec section is currently "23.4. Coordinate
// Transformations".
// The equations reduce to an expression:
//
// z_vk = 0.5 * (w_gl + z_gl)
//
// where z_vk is the depth output of a Vulkan vertex shader and z_gl is the same for GL.
ANGLE_NO_DISCARD bool AppendVertexShaderDepthCorrectionToMain(TCompiler *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable)
{
// Create a symbol reference to "gl_Position"
const TVariable *position = BuiltInVariable::gl_Position();
TIntermSymbol *positionRef = new TIntermSymbol(position);
// Create a swizzle to "gl_Position.z"
TVector<int> swizzleOffsetZ = {2};
TIntermSwizzle *positionZ = new TIntermSwizzle(positionRef, swizzleOffsetZ);
// Create a constant "0.5"
TIntermConstantUnion *oneHalf = CreateFloatNode(0.5f);
// Create a swizzle to "gl_Position.w"
TVector<int> swizzleOffsetW = {3};
TIntermSwizzle *positionW = new TIntermSwizzle(positionRef->deepCopy(), swizzleOffsetW);
// Create the expression "(gl_Position.z + gl_Position.w) * 0.5".
TIntermBinary *zPlusW = new TIntermBinary(EOpAdd, positionZ->deepCopy(), positionW->deepCopy());
TIntermBinary *halfZPlusW = new TIntermBinary(EOpMul, zPlusW, oneHalf->deepCopy());
// Create the assignment "gl_Position.z = (gl_Position.z + gl_Position.w) * 0.5"
TIntermTyped *positionZLHS = positionZ->deepCopy();
TIntermBinary *assignment = new TIntermBinary(TOperator::EOpAssign, positionZLHS, halfZPlusW);
// Append the assignment as a statement at the end of the shader.
return RunAtTheEndOfShader(compiler, root, assignment, symbolTable);
}
ANGLE_NO_DISCARD bool AppendVertexShaderTransformFeedbackOutputToMain(TCompiler *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable)
{
TVariable *xfbPlaceholder = new TVariable(symbolTable, ImmutableString("@@ XFB-OUT @@"),
new TType(), SymbolType::AngleInternal);
// Append the assignment as a statement at the end of the shader.
return RunAtTheEndOfShader(compiler, root, new TIntermSymbol(xfbPlaceholder), symbolTable);
}
// The Add*DriverUniformsToShader operation adds an internal uniform block to a shader. The driver
// block is used to implement Vulkan-specific features and workarounds. Returns the driver uniforms
// variable.
//
// There are Graphics and Compute variations as they require different uniforms.
const TVariable *AddGraphicsDriverUniformsToShader(TIntermBlock *root, TSymbolTable *symbolTable)
{
// Init the depth range type.
TFieldList *depthRangeParamsFields = new TFieldList();
depthRangeParamsFields->push_back(new TField(new TType(EbtFloat, EbpHigh, EvqGlobal, 1, 1),
ImmutableString("near"), TSourceLoc(),
SymbolType::AngleInternal));
depthRangeParamsFields->push_back(new TField(new TType(EbtFloat, EbpHigh, EvqGlobal, 1, 1),
ImmutableString("far"), TSourceLoc(),
SymbolType::AngleInternal));
depthRangeParamsFields->push_back(new TField(new TType(EbtFloat, EbpHigh, EvqGlobal, 1, 1),
ImmutableString("diff"), TSourceLoc(),
SymbolType::AngleInternal));
// This additional field might be used by subclass such as TranslatorMetal.
depthRangeParamsFields->push_back(new TField(new TType(EbtFloat, EbpHigh, EvqGlobal, 1, 1),
ImmutableString("reserved"), TSourceLoc(),
SymbolType::AngleInternal));
TStructure *emulatedDepthRangeParams = new TStructure(
symbolTable, kEmulatedDepthRangeParams, depthRangeParamsFields, SymbolType::AngleInternal);
TType *emulatedDepthRangeType = new TType(emulatedDepthRangeParams, false);
// Declare a global depth range variable.
TVariable *depthRangeVar =
new TVariable(symbolTable->nextUniqueId(), kEmptyImmutableString, SymbolType::Empty,
TExtension::UNDEFINED, emulatedDepthRangeType);
DeclareGlobalVariable(root, depthRangeVar);
// This field list mirrors the structure of GraphicsDriverUniforms in ContextVk.cpp.
TFieldList *driverFieldList = new TFieldList;
const std::array<TType *, kNumGraphicsDriverUniforms> kDriverUniformTypes = {{
new TType(EbtFloat, 4),
new TType(EbtFloat),
new TType(EbtFloat),
new TType(EbtFloat),
new TType(EbtUInt),
new TType(EbtInt, 4),
new TType(EbtUInt, 4),
emulatedDepthRangeType,
}};
for (size_t uniformIndex = 0; uniformIndex < kNumGraphicsDriverUniforms; ++uniformIndex)
{
TField *driverUniformField =
new TField(kDriverUniformTypes[uniformIndex],
ImmutableString(kGraphicsDriverUniformNames[uniformIndex]), TSourceLoc(),
SymbolType::AngleInternal);
driverFieldList->push_back(driverUniformField);
}
// Define a driver uniform block "ANGLEUniformBlock" with instance name "ANGLEUniforms".
return DeclareInterfaceBlock(root, symbolTable, driverFieldList, EvqUniform,
TMemoryQualifier::Create(), 0, kUniformsBlockName,
kUniformsVarName);
}
const TVariable *AddComputeDriverUniformsToShader(TIntermBlock *root, TSymbolTable *symbolTable)
{
// This field list mirrors the structure of ComputeDriverUniforms in ContextVk.cpp.
TFieldList *driverFieldList = new TFieldList;
const std::array<TType *, kNumComputeDriverUniforms> kDriverUniformTypes = {{
new TType(EbtUInt, 4),
}};
for (size_t uniformIndex = 0; uniformIndex < kNumComputeDriverUniforms; ++uniformIndex)
{
TField *driverUniformField =
new TField(kDriverUniformTypes[uniformIndex],
ImmutableString(kComputeDriverUniformNames[uniformIndex]), TSourceLoc(),
SymbolType::AngleInternal);
driverFieldList->push_back(driverUniformField);
}
// Define a driver uniform block "ANGLEUniformBlock" with instance name "ANGLEUniforms".
return DeclareInterfaceBlock(root, symbolTable, driverFieldList, EvqUniform,
TMemoryQualifier::Create(), 0, kUniformsBlockName,
kUniformsVarName);
}
TIntermPreprocessorDirective *GenerateLineRasterIfDef()
{
return new TIntermPreprocessorDirective(
PreprocessorDirective::Ifdef, ImmutableString("ANGLE_ENABLE_LINE_SEGMENT_RASTERIZATION"));
}
TIntermPreprocessorDirective *GenerateEndIf()
{
return new TIntermPreprocessorDirective(PreprocessorDirective::Endif, kEmptyImmutableString);
}
TVariable *AddANGLEPositionVaryingDeclaration(TIntermBlock *root,
TSymbolTable *symbolTable,
TQualifier qualifier)
{
TIntermSequence *insertSequence = new TIntermSequence;
insertSequence->push_back(GenerateLineRasterIfDef());
// Define a driver varying vec2 "ANGLEPosition".
TType *varyingType = new TType(EbtFloat, EbpMedium, qualifier, 2);
TVariable *varyingVar = new TVariable(symbolTable, ImmutableString("ANGLEPosition"),
varyingType, SymbolType::AngleInternal);
TIntermSymbol *varyingDeclarator = new TIntermSymbol(varyingVar);
TIntermDeclaration *varyingDecl = new TIntermDeclaration;
varyingDecl->appendDeclarator(varyingDeclarator);
insertSequence->push_back(varyingDecl);
insertSequence->push_back(GenerateEndIf());
// Insert the declarations before Main.
size_t mainIndex = FindMainIndex(root);
root->insertChildNodes(mainIndex, *insertSequence);
return varyingVar;
}
ANGLE_NO_DISCARD bool AddBresenhamEmulationVS(TCompiler *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable,
const TVariable *driverUniforms)
{
TVariable *anglePosition = AddANGLEPositionVaryingDeclaration(root, symbolTable, EvqVaryingOut);
// Clamp position to subpixel grid.
// Do perspective divide (get normalized device coords)
// "vec2 ndc = gl_Position.xy / gl_Position.w"
const TType *vec2Type = StaticType::GetBasic<EbtFloat, 2>();
TIntermBinary *viewportRef = CreateDriverUniformRef(driverUniforms, kViewport);
TIntermSymbol *glPos = new TIntermSymbol(BuiltInVariable::gl_Position());
TIntermSwizzle *glPosXY = CreateSwizzle(glPos, 0, 1);
TIntermSwizzle *glPosW = CreateSwizzle(glPos->deepCopy(), 3);
TVariable *ndc = CreateTempVariable(symbolTable, vec2Type);
TIntermBinary *noPerspective = new TIntermBinary(EOpDiv, glPosXY, glPosW);
TIntermDeclaration *ndcDecl = CreateTempInitDeclarationNode(ndc, noPerspective);
// Convert NDC to window coordinates. According to Vulkan spec.
// "vec2 window = 0.5 * viewport.wh * (ndc + 1) + viewport.xy"
TIntermBinary *ndcPlusOne =
new TIntermBinary(EOpAdd, CreateTempSymbolNode(ndc), CreateFloatNode(1.0f));
TIntermSwizzle *viewportZW = CreateSwizzle(viewportRef, 2, 3);
TIntermBinary *ndcViewport = new TIntermBinary(EOpMul, viewportZW, ndcPlusOne);
TIntermBinary *ndcViewportHalf =
new TIntermBinary(EOpVectorTimesScalar, ndcViewport, CreateFloatNode(0.5f));
TIntermSwizzle *viewportXY = CreateSwizzle(viewportRef->deepCopy(), 0, 1);
TIntermBinary *ndcToWindow = new TIntermBinary(EOpAdd, ndcViewportHalf, viewportXY);
TVariable *windowCoords = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *windowDecl = CreateTempInitDeclarationNode(windowCoords, ndcToWindow);
// Clamp to subpixel grid.
// "vec2 clamped = round(window * 2^{subpixelBits}) / 2^{subpixelBits}"
int subpixelBits = compiler->getResources().SubPixelBits;
TIntermConstantUnion *scaleConstant = CreateFloatNode(static_cast<float>(1 << subpixelBits));
TIntermBinary *windowScaled =
new TIntermBinary(EOpVectorTimesScalar, CreateTempSymbolNode(windowCoords), scaleConstant);
TIntermUnary *windowRounded = new TIntermUnary(EOpRound, windowScaled, nullptr);
TIntermBinary *windowRoundedBack =
new TIntermBinary(EOpDiv, windowRounded, scaleConstant->deepCopy());
TVariable *clampedWindowCoords = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *clampedDecl =
CreateTempInitDeclarationNode(clampedWindowCoords, windowRoundedBack);
// Set varying.
// "ANGLEPosition = 2 * (clamped - viewport.xy) / viewport.wh - 1"
TIntermBinary *clampedOffset = new TIntermBinary(
EOpSub, CreateTempSymbolNode(clampedWindowCoords), viewportXY->deepCopy());
TIntermBinary *clampedOff2x =
new TIntermBinary(EOpVectorTimesScalar, clampedOffset, CreateFloatNode(2.0f));
TIntermBinary *clampedDivided = new TIntermBinary(EOpDiv, clampedOff2x, viewportZW->deepCopy());
TIntermBinary *clampedNDC = new TIntermBinary(EOpSub, clampedDivided, CreateFloatNode(1.0f));
TIntermSymbol *varyingRef = new TIntermSymbol(anglePosition);
TIntermBinary *varyingAssign = new TIntermBinary(EOpAssign, varyingRef, clampedNDC);
// Ensure the statements run at the end of the main() function.
TIntermFunctionDefinition *main = FindMain(root);
TIntermBlock *mainBody = main->getBody();
mainBody->appendStatement(GenerateLineRasterIfDef());
mainBody->appendStatement(ndcDecl);
mainBody->appendStatement(windowDecl);
mainBody->appendStatement(clampedDecl);
mainBody->appendStatement(varyingAssign);
mainBody->appendStatement(GenerateEndIf());
return compiler->validateAST(root);
}
ANGLE_NO_DISCARD bool InsertFragCoordCorrection(TCompiler *compiler,
TIntermBlock *root,
TIntermSequence *insertSequence,
TSymbolTable *symbolTable,
const TVariable *driverUniforms)
{
TIntermBinary *viewportYScale = CreateDriverUniformRef(driverUniforms, kViewportYScale);
TIntermBinary *pivot = CreateDriverUniformRef(driverUniforms, kHalfRenderAreaHeight);
return FlipBuiltinVariable(compiler, root, insertSequence, viewportYScale, symbolTable,
BuiltInVariable::gl_FragCoord(), kFlippedFragCoordName, pivot);
}
// This block adds OpenGL line segment rasterization emulation behind #ifdef guards.
// OpenGL's simple rasterization algorithm is a strict subset of the pixels generated by the Vulkan
// algorithm. Thus we can implement a shader patch that rejects pixels if they would not be
// generated by the OpenGL algorithm. OpenGL's algorithm is similar to Bresenham's line algorithm.
// It is implemented for each pixel by testing if the line segment crosses a small diamond inside
// the pixel. See the OpenGL ES 2.0 spec section "3.4.1 Basic Line Segment Rasterization". Also
// see the Vulkan spec section "24.6.1. Basic Line Segment Rasterization":
// https://khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#primsrast-lines-basic
//
// Using trigonometric math and the fact that we know the size of the diamond we can derive a
// formula to test if the line segment crosses the pixel center. gl_FragCoord is used along with an
// internal position varying to determine the inputs to the formula.
//
// The implementation of the test is similar to the following pseudocode:
//
// void main()
// {
// vec2 p = (((((ANGLEPosition.xy) * 0.5) + 0.5) * viewport.zw) + viewport.xy);
// vec2 d = dFdx(p) + dFdy(p);
// vec2 f = gl_FragCoord.xy;
// vec2 p_ = p.yx;
// vec2 d_ = d.yx;
// vec2 f_ = f.yx;
//
// vec2 i = abs(p - f + (d / d_) * (f_ - p_));
//
// if (i.x > (0.5 + e) && i.y > (0.5 + e))
// discard;
// <otherwise run fragment shader main>
// }
//
// Note this emulation can not provide fully correct rasterization. See the docs more more info.
ANGLE_NO_DISCARD bool AddBresenhamEmulationFS(TCompiler *compiler,
TInfoSinkBase &sink,
TIntermBlock *root,
TSymbolTable *symbolTable,
const TVariable *driverUniforms,
bool usesFragCoord)
{
TVariable *anglePosition = AddANGLEPositionVaryingDeclaration(root, symbolTable, EvqVaryingIn);
const TType *vec2Type = StaticType::GetBasic<EbtFloat, 2>();
TIntermBinary *viewportRef = CreateDriverUniformRef(driverUniforms, kViewport);
// vec2 p = ((ANGLEPosition * 0.5) + 0.5) * viewport.zw + viewport.xy
TIntermSwizzle *viewportXY = CreateSwizzle(viewportRef->deepCopy(), 0, 1);
TIntermSwizzle *viewportZW = CreateSwizzle(viewportRef, 2, 3);
TIntermSymbol *position = new TIntermSymbol(anglePosition);
TIntermConstantUnion *oneHalf = CreateFloatNode(0.5f);
TIntermBinary *halfPosition = new TIntermBinary(EOpVectorTimesScalar, position, oneHalf);
TIntermBinary *offsetHalfPosition =
new TIntermBinary(EOpAdd, halfPosition, oneHalf->deepCopy());
TIntermBinary *scaledPosition = new TIntermBinary(EOpMul, offsetHalfPosition, viewportZW);
TIntermBinary *windowPosition = new TIntermBinary(EOpAdd, scaledPosition, viewportXY);
TVariable *p = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *pDecl = CreateTempInitDeclarationNode(p, windowPosition);
// vec2 d = dFdx(p) + dFdy(p)
TIntermUnary *dfdx = new TIntermUnary(EOpDFdx, new TIntermSymbol(p), nullptr);
TIntermUnary *dfdy = new TIntermUnary(EOpDFdy, new TIntermSymbol(p), nullptr);
TIntermBinary *dfsum = new TIntermBinary(EOpAdd, dfdx, dfdy);
TVariable *d = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *dDecl = CreateTempInitDeclarationNode(d, dfsum);
// vec2 f = gl_FragCoord.xy
const TVariable *fragCoord = BuiltInVariable::gl_FragCoord();
TIntermSwizzle *fragCoordXY = CreateSwizzle(new TIntermSymbol(fragCoord), 0, 1);
TVariable *f = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *fDecl = CreateTempInitDeclarationNode(f, fragCoordXY);
// vec2 p_ = p.yx
TIntermSwizzle *pyx = CreateSwizzle(new TIntermSymbol(p), 1, 0);
TVariable *p_ = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *p_decl = CreateTempInitDeclarationNode(p_, pyx);
// vec2 d_ = d.yx
TIntermSwizzle *dyx = CreateSwizzle(new TIntermSymbol(d), 1, 0);
TVariable *d_ = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *d_decl = CreateTempInitDeclarationNode(d_, dyx);
// vec2 f_ = f.yx
TIntermSwizzle *fyx = CreateSwizzle(new TIntermSymbol(f), 1, 0);
TVariable *f_ = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *f_decl = CreateTempInitDeclarationNode(f_, fyx);
// vec2 i = abs(p - f + (d/d_) * (f_ - p_))
TIntermBinary *dd = new TIntermBinary(EOpDiv, new TIntermSymbol(d), new TIntermSymbol(d_));
TIntermBinary *fp = new TIntermBinary(EOpSub, new TIntermSymbol(f_), new TIntermSymbol(p_));
TIntermBinary *ddfp = new TIntermBinary(EOpMul, dd, fp);
TIntermBinary *pf = new TIntermBinary(EOpSub, new TIntermSymbol(p), new TIntermSymbol(f));
TIntermBinary *expr = new TIntermBinary(EOpAdd, pf, ddfp);
TIntermUnary *absd = new TIntermUnary(EOpAbs, expr, nullptr);
TVariable *i = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *iDecl = CreateTempInitDeclarationNode(i, absd);
// Using a small epsilon value ensures that we don't suffer from numerical instability when
// lines are exactly vertical or horizontal.
static constexpr float kEpsilon = 0.0001f;
static constexpr float kThreshold = 0.5 + kEpsilon;
TIntermConstantUnion *threshold = CreateFloatNode(kThreshold);
// if (i.x > (0.5 + e) && i.y > (0.5 + e))
TIntermSwizzle *ix = CreateSwizzle(new TIntermSymbol(i), 0);
TIntermBinary *checkX = new TIntermBinary(EOpGreaterThan, ix, threshold);
TIntermSwizzle *iy = CreateSwizzle(new TIntermSymbol(i), 1);
TIntermBinary *checkY = new TIntermBinary(EOpGreaterThan, iy, threshold->deepCopy());
TIntermBinary *checkXY = new TIntermBinary(EOpLogicalAnd, checkX, checkY);
// discard
TIntermBranch *discard = new TIntermBranch(EOpKill, nullptr);
TIntermBlock *discardBlock = new TIntermBlock;
discardBlock->appendStatement(discard);
TIntermIfElse *ifStatement = new TIntermIfElse(checkXY, discardBlock, nullptr);
// Ensure the line raster code runs at the beginning of main().
TIntermFunctionDefinition *main = FindMain(root);
TIntermSequence *mainSequence = main->getBody()->getSequence();
ASSERT(mainSequence);
std::array<TIntermNode *, 9> nodes = {
{pDecl, dDecl, fDecl, p_decl, d_decl, f_decl, iDecl, ifStatement, GenerateEndIf()}};
mainSequence->insert(mainSequence->begin(), nodes.begin(), nodes.end());
// If the shader does not use frag coord, we should insert it inside the ifdef.
if (!usesFragCoord)
{
if (!InsertFragCoordCorrection(compiler, root, mainSequence, symbolTable, driverUniforms))
{
return false;
}
}
mainSequence->insert(mainSequence->begin(), GenerateLineRasterIfDef());
return compiler->validateAST(root);
}
} // anonymous namespace
TranslatorVulkan::TranslatorVulkan(sh::GLenum type, ShShaderSpec spec)
: TCompiler(type, spec, SH_GLSL_450_CORE_OUTPUT)
{}
bool TranslatorVulkan::translateImpl(TIntermBlock *root,
ShCompileOptions compileOptions,
PerformanceDiagnostics * /*perfDiagnostics*/,
const TVariable **driverUniformsOut,
TOutputVulkanGLSL *outputGLSL)
{
TInfoSinkBase &sink = getInfoSink().obj;
if (getShaderType() == GL_VERTEX_SHADER)
{
if (!ShaderBuiltinsWorkaround(this, root, &getSymbolTable(), compileOptions))
{
return false;
}
}
sink << "#version 450 core\n";
// Write out default uniforms into a uniform block assigned to a specific set/binding.
int defaultUniformCount = 0;
int aggregateTypesUsedForUniforms = 0;
int atomicCounterCount = 0;
for (const auto &uniform : getUniforms())
{
if (!uniform.isBuiltIn() && uniform.staticUse && !gl::IsOpaqueType(uniform.type))
{
++defaultUniformCount;
}
if (uniform.isStruct() || uniform.isArrayOfArrays())
{
++aggregateTypesUsedForUniforms;
}
if (gl::IsAtomicCounterType(uniform.type))
{
++atomicCounterCount;
}
}
// TODO(lucferron): Refactor this function to do fewer tree traversals.
// http://anglebug.com/2461
if (aggregateTypesUsedForUniforms > 0)
{
if (!NameEmbeddedStructUniforms(this, root, &getSymbolTable()))
{
return false;
}
bool rewriteStructSamplersResult;
int removedUniformsCount;
if (compileOptions & SH_USE_OLD_REWRITE_STRUCT_SAMPLERS)
{
rewriteStructSamplersResult =
RewriteStructSamplersOld(this, root, &getSymbolTable(), &removedUniformsCount);
}
else
{
rewriteStructSamplersResult =
RewriteStructSamplers(this, root, &getSymbolTable(), &removedUniformsCount);
}
if (!rewriteStructSamplersResult)
{
return false;
}
defaultUniformCount -= removedUniformsCount;
// We must declare the struct types before using them.
DeclareStructTypesTraverser structTypesTraverser(outputGLSL);
root->traverse(&structTypesTraverser);
if (!structTypesTraverser.updateTree(this, root))
{
return false;
}
}
// Rewrite samplerCubes as sampler2DArrays. This must be done after rewriting struct samplers
// as it doesn't expect that.
if (compileOptions & SH_EMULATE_SEAMFUL_CUBE_MAP_SAMPLING)
{
if (!RewriteCubeMapSamplersAs2DArray(this, root, &getSymbolTable(),
getShaderType() == GL_FRAGMENT_SHADER))
{
return false;
}
}
if (getShaderVersion() >= 300)
{
// Make sure every uniform buffer variable has a name. The following transformation relies
// on this.
if (!NameNamelessUniformBuffers(this, root, &getSymbolTable()))
{
return false;
}
// In GLES3+, matrices can be declared row- or column-major. Transform all to column-major
// as interface block field layout qualifiers are not allowed. This should be done after
// samplers are taken out of structs (as structs could be rewritten), but before uniforms
// are collected in a uniform buffer as they are handled especially.
if (!RewriteRowMajorMatrices(this, root, &getSymbolTable()))
{
return false;
}
}
if (defaultUniformCount > 0)
{
sink << "\n@@ LAYOUT-defaultUniforms(std140) @@ uniform defaultUniforms\n{\n";
DeclareDefaultUniformsTraverser defaultTraverser(&sink, getHashFunction(), &getNameMap());
root->traverse(&defaultTraverser);
if (!defaultTraverser.updateTree(this, root))
{
return false;
}
sink << "};\n";
}
const TVariable *driverUniforms;
if (getShaderType() == GL_COMPUTE_SHADER)
{
driverUniforms = AddComputeDriverUniformsToShader(root, &getSymbolTable());
}
else
{
driverUniforms = AddGraphicsDriverUniformsToShader(root, &getSymbolTable());
}
if (atomicCounterCount > 0)
{
// ANGLEUniforms.acbBufferOffsets
const TIntermBinary *acbBufferOffsets =
CreateDriverUniformRef(driverUniforms, kAcbBufferOffsets);
if (!RewriteAtomicCounters(this, root, &getSymbolTable(), acbBufferOffsets))
{
return false;
}
}
if (getShaderType() != GL_COMPUTE_SHADER)
{
if (!ReplaceGLDepthRangeWithDriverUniform(this, root, driverUniforms, &getSymbolTable()))
{
return false;
}
}
// Declare gl_FragColor and glFragData as webgl_FragColor and webgl_FragData
// if it's core profile shaders and they are used.
if (getShaderType() == GL_FRAGMENT_SHADER)
{
bool usesPointCoord = false;
bool usesFragCoord = false;
// Search for the gl_PointCoord usage, if its used, we need to flip the y coordinate.
for (const ShaderVariable &inputVarying : mInputVaryings)
{
if (!inputVarying.isBuiltIn())
{
continue;
}
if (inputVarying.name == "gl_PointCoord")
{
usesPointCoord = true;
break;
}
if (inputVarying.name == "gl_FragCoord")
{
usesFragCoord = true;
break;
}
}
if (!AddBresenhamEmulationFS(this, sink, root, &getSymbolTable(), driverUniforms,
usesFragCoord))
{
return false;
}
bool hasGLFragColor = false;
bool hasGLFragData = false;
for (const ShaderVariable &outputVar : mOutputVariables)
{
if (outputVar.name == "gl_FragColor")
{
ASSERT(!hasGLFragColor);
hasGLFragColor = true;
continue;
}
else if (outputVar.name == "gl_FragData")
{
ASSERT(!hasGLFragData);
hasGLFragData = true;
continue;
}
}
ASSERT(!(hasGLFragColor && hasGLFragData));
if (hasGLFragColor)
{
sink << "layout(location = 0) out vec4 webgl_FragColor;\n";
}
if (hasGLFragData)
{
sink << "layout(location = 0) out vec4 webgl_FragData[gl_MaxDrawBuffers];\n";
}
if (usesPointCoord)
{
TIntermBinary *viewportYScale =
CreateDriverUniformRef(driverUniforms, kNegViewportYScale);
TIntermConstantUnion *pivot = CreateFloatNode(0.5f);
if (!FlipBuiltinVariable(this, root, GetMainSequence(root), viewportYScale,
&getSymbolTable(), BuiltInVariable::gl_PointCoord(),
kFlippedPointCoordName, pivot))
{
return false;
}
}
if (usesFragCoord)
{
if (!InsertFragCoordCorrection(this, root, GetMainSequence(root), &getSymbolTable(),
driverUniforms))
{
return false;
}
}
{
TIntermBinary *viewportYScale = CreateDriverUniformRef(driverUniforms, kViewportYScale);
if (!RewriteDfdy(this, root, getSymbolTable(), getShaderVersion(), viewportYScale))
{
return false;
}
}
}
else if (getShaderType() == GL_VERTEX_SHADER)
{
if (!AddBresenhamEmulationVS(this, root, &getSymbolTable(), driverUniforms))
{
return false;
}
// Add a macro to declare transform feedback buffers.
sink << "@@ XFB-DECL @@\n\n";
// Append a macro for transform feedback substitution prior to modifying depth.
if (!AppendVertexShaderTransformFeedbackOutputToMain(this, root, &getSymbolTable()))
{
return false;
}
// Append depth range translation to main.
if (!transformDepthBeforeCorrection(root, driverUniforms))
{
return false;
}
if (!AppendVertexShaderDepthCorrectionToMain(this, root, &getSymbolTable()))
{
return false;
}
}
else if (getShaderType() == GL_GEOMETRY_SHADER)
{
WriteGeometryShaderLayoutQualifiers(
sink, getGeometryShaderInputPrimitiveType(), getGeometryShaderInvocations(),
getGeometryShaderOutputPrimitiveType(), getGeometryShaderMaxVertices());
}
else
{
ASSERT(getShaderType() == GL_COMPUTE_SHADER);
EmitWorkGroupSizeGLSL(*this, sink);
}
if (!validateAST(root))
{
return false;
}
if (driverUniformsOut)
{
*driverUniformsOut = driverUniforms;
}
return true;
}
bool TranslatorVulkan::translate(TIntermBlock *root,
ShCompileOptions compileOptions,
PerformanceDiagnostics *perfDiagnostics)
{
TInfoSinkBase &sink = getInfoSink().obj;
TOutputVulkanGLSL outputGLSL(sink, getArrayIndexClampingStrategy(), getHashFunction(),
getNameMap(), &getSymbolTable(), getShaderType(),
getShaderVersion(), getOutputType(), compileOptions);
if (!translateImpl(root, compileOptions, perfDiagnostics, nullptr, &outputGLSL))
{
return false;
}
// Write translated shader.
root->traverse(&outputGLSL);
return true;
}
bool TranslatorVulkan::shouldFlattenPragmaStdglInvariantAll()
{
// Not necessary.
return false;
}
TIntermBinary *TranslatorVulkan::getDriverUniformNegViewportYScaleRef(
const TVariable *driverUniforms) const
{
return CreateDriverUniformRef(driverUniforms, kNegViewportYScale);
}
TIntermBinary *TranslatorVulkan::getDriverUniformDepthRangeReservedFieldRef(
const TVariable *driverUniforms) const
{
TIntermBinary *depthRange = CreateDriverUniformRef(driverUniforms, kDepthRange);
return new TIntermBinary(EOpIndexDirectStruct, depthRange, CreateIndexNode(3));
}
} // namespace sh