diff --git a/include/GLSLANG/ShaderLang.h b/include/GLSLANG/ShaderLang.h index ce696cf13..964e9378d 100644 --- a/include/GLSLANG/ShaderLang.h +++ b/include/GLSLANG/ShaderLang.h @@ -504,6 +504,9 @@ struct ShBuiltInResources int MaxGeometryShaderStorageBlocks; int MaxGeometryShaderInvocations; int MaxGeometryImageUniforms; + + // Subpixel bits used in rasterization. + int SubPixelBits; }; // diff --git a/src/compiler/translator/ShaderLang.cpp b/src/compiler/translator/ShaderLang.cpp index b82a2fda7..f4b7b9d5d 100644 --- a/src/compiler/translator/ShaderLang.cpp +++ b/src/compiler/translator/ShaderLang.cpp @@ -253,6 +253,8 @@ void InitBuiltInResources(ShBuiltInResources *resources) resources->MaxGeometryShaderStorageBlocks = 0; resources->MaxGeometryShaderInvocations = 32; resources->MaxGeometryImageUniforms = 0; + + resources->SubPixelBits = 8; } // diff --git a/src/compiler/translator/TranslatorVulkan.cpp b/src/compiler/translator/TranslatorVulkan.cpp index 0eaa26fbb..84e7fdd9f 100644 --- a/src/compiler/translator/TranslatorVulkan.cpp +++ b/src/compiler/translator/TranslatorVulkan.cpp @@ -438,7 +438,7 @@ TVariable *AddANGLEPositionVaryingDeclaration(TIntermBlock *root, insertSequence->push_back(GenerateLineRasterIfDef()); // Define a driver varying vec2 "ANGLEPosition". - TType *varyingType = new TType(EbtFloat, EbpMedium, qualifier, 4); + TType *varyingType = new TType(EbtFloat, EbpMedium, qualifier, 2); TVariable *varyingVar = new TVariable(symbolTable, ImmutableString("ANGLEPosition"), varyingType, SymbolType::AngleInternal); TIntermSymbol *varyingDeclarator = new TIntermSymbol(varyingVar); @@ -455,22 +455,72 @@ TVariable *AddANGLEPositionVaryingDeclaration(TIntermBlock *root, return varyingVar; } -void AddANGLEPositionVarying(TIntermBlock *root, TSymbolTable *symbolTable) +ANGLE_NO_DISCARD bool AddBresenhamEmulationVS(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + const TVariable *driverUniforms) { TVariable *anglePosition = AddANGLEPositionVaryingDeclaration(root, symbolTable, EvqVaryingOut); - // Create an assignment "ANGLEPosition = gl_Position". - const TVariable *position = BuiltInVariable::gl_Position(); - TIntermSymbol *varyingRef = new TIntermSymbol(anglePosition); - TIntermBinary *assignment = - new TIntermBinary(EOpAssign, varyingRef, new TIntermSymbol(position)); + // 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(); + 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); - // Ensure the assignment runs at the end of the main() function. + // 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(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(assignment); + 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, @@ -498,133 +548,105 @@ ANGLE_NO_DISCARD bool InsertFragCoordCorrection(TCompiler *compiler, // 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 code is similar to the following pseudocode: +// The implementation of the test is similar to the following pseudocode: // // void main() // { -// vec2 b = (((position.xy / position.w) * 0.5) + 0.5) * gl_Viewport.zw + gl_Viewport.xy; -// vec2 ba = abs(b - gl_FragCoord.xy); -// vec2 ba2 = 2.0 * (ba * ba); -// vec2 bp = ba2 + ba2.yx - ba; -// if (bp.x > epsilon && bp.y > epsilon) -// discard; +// 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; // // } -ANGLE_NO_DISCARD bool AddLineSegmentRasterizationEmulation(TCompiler *compiler, - TInfoSinkBase &sink, - TIntermBlock *root, - TSymbolTable *symbolTable, - const TVariable *driverUniforms, - bool usesFragCoord) +// +// 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(); + TIntermBinary *viewportRef = CreateDriverUniformRef(driverUniforms, kViewport); - const TType *vec2Type = StaticType::GetBasic(); - - // Create a swizzle to "ANGLEUniforms.viewport.xy". - TIntermBinary *viewportRef = CreateDriverUniformRef(driverUniforms, kViewport); - TVector swizzleOffsetXY = {0, 1}; - TIntermSwizzle *viewportXY = new TIntermSwizzle(viewportRef->deepCopy(), swizzleOffsetXY); - - // Create a swizzle to "ANGLEUniforms.viewport.zw". - TVector swizzleOffsetZW = {2, 3}; - TIntermSwizzle *viewportZW = new TIntermSwizzle(viewportRef, swizzleOffsetZW); - - // ANGLEPosition.xy / ANGLEPosition.w - TIntermSymbol *position = new TIntermSymbol(anglePosition); - TIntermSwizzle *positionXY = new TIntermSwizzle(position, swizzleOffsetXY); - TVector swizzleOffsetW = {3}; - TIntermSwizzle *positionW = new TIntermSwizzle(position->deepCopy(), swizzleOffsetW); - TIntermBinary *positionNDC = new TIntermBinary(EOpDiv, positionXY, positionW); - - // ANGLEPosition * 0.5 + // 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, positionNDC, oneHalf); - - // (ANGLEPosition * 0.5) + 0.5 + TIntermBinary *halfPosition = new TIntermBinary(EOpVectorTimesScalar, position, oneHalf); TIntermBinary *offsetHalfPosition = new TIntermBinary(EOpAdd, halfPosition, oneHalf->deepCopy()); - - // ((ANGLEPosition * 0.5) + 0.5) * ANGLEUniforms.viewport.zw TIntermBinary *scaledPosition = new TIntermBinary(EOpMul, offsetHalfPosition, viewportZW); - - // ((ANGLEPosition * 0.5) + 0.5) * ANGLEUniforms.viewport + ANGLEUniforms.viewport.xy TIntermBinary *windowPosition = new TIntermBinary(EOpAdd, scaledPosition, viewportXY); + TVariable *p = CreateTempVariable(symbolTable, vec2Type); + TIntermDeclaration *pDecl = CreateTempInitDeclarationNode(p, windowPosition); - // Assign to a temporary "b". - TVariable *bTemp = CreateTempVariable(symbolTable, vec2Type); - TIntermDeclaration *bDecl = CreateTempInitDeclarationNode(bTemp, 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); - // gl_FragCoord.xy + // vec2 f = gl_FragCoord.xy const TVariable *fragCoord = BuiltInVariable::gl_FragCoord(); - TIntermSymbol *fragCoordRef = new TIntermSymbol(fragCoord); - TIntermSwizzle *fragCoordXY = new TIntermSwizzle(fragCoordRef, swizzleOffsetXY); + TIntermSwizzle *fragCoordXY = CreateSwizzle(new TIntermSymbol(fragCoord), 0, 1); + TVariable *f = CreateTempVariable(symbolTable, vec2Type); + TIntermDeclaration *fDecl = CreateTempInitDeclarationNode(f, fragCoordXY); - // b - gl_FragCoord.xy - TIntermSymbol *bRef = CreateTempSymbolNode(bTemp); - TIntermBinary *differenceExpr = new TIntermBinary(EOpSub, bRef, fragCoordXY); + // vec2 p_ = p.yx + TIntermSwizzle *pyx = CreateSwizzle(new TIntermSymbol(p), 1, 0); + TVariable *p_ = CreateTempVariable(symbolTable, vec2Type); + TIntermDeclaration *p_decl = CreateTempInitDeclarationNode(p_, pyx); - // abs(b - gl_FragCoord.xy) - TIntermUnary *baAbs = new TIntermUnary(EOpAbs, differenceExpr, nullptr); + // vec2 d_ = d.yx + TIntermSwizzle *dyx = CreateSwizzle(new TIntermSymbol(d), 1, 0); + TVariable *d_ = CreateTempVariable(symbolTable, vec2Type); + TIntermDeclaration *d_decl = CreateTempInitDeclarationNode(d_, dyx); - // Assign to a temporary "ba". - TVariable *baTemp = CreateTempVariable(symbolTable, vec2Type); - TIntermDeclaration *baDecl = CreateTempInitDeclarationNode(baTemp, baAbs); - TIntermSymbol *ba = CreateTempSymbolNode(baTemp); + // vec2 f_ = f.yx + TIntermSwizzle *fyx = CreateSwizzle(new TIntermSymbol(f), 1, 0); + TVariable *f_ = CreateTempVariable(symbolTable, vec2Type); + TIntermDeclaration *f_decl = CreateTempInitDeclarationNode(f_, fyx); - // ba * ba - TIntermBinary *baSq = new TIntermBinary(EOpMul, ba, ba->deepCopy()); - - // 2.0 * ba * ba - TIntermTyped *two = CreateFloatNode(2.0f); - TIntermBinary *twoBaSq = new TIntermBinary(EOpVectorTimesScalar, baSq, two); - - // Assign to a temporary "ba2". - TVariable *ba2Temp = CreateTempVariable(symbolTable, vec2Type); - TIntermDeclaration *ba2Decl = CreateTempInitDeclarationNode(ba2Temp, twoBaSq); - - // Create a swizzle to "ba2.yx". - TVector swizzleOffsetYX = {1, 0}; - TIntermSymbol *ba2 = CreateTempSymbolNode(ba2Temp); - TIntermSwizzle *ba2YX = new TIntermSwizzle(ba2, swizzleOffsetYX); - - // ba2 + ba2.yx - ba - TIntermBinary *ba2PlusBaYX2 = new TIntermBinary(EOpAdd, ba2->deepCopy(), ba2YX); - TIntermBinary *bpInit = new TIntermBinary(EOpSub, ba2PlusBaYX2, ba->deepCopy()); - - // Assign to a temporary "bp". - TVariable *bpTemp = CreateTempVariable(symbolTable, vec2Type); - TIntermDeclaration *bpDecl = CreateTempInitDeclarationNode(bpTemp, bpInit); - TIntermSymbol *bp = CreateTempSymbolNode(bpTemp); - - // Create a swizzle to "bp.x". - TVector swizzleOffsetX = {0}; - TIntermSwizzle *bpX = new TIntermSwizzle(bp, swizzleOffsetX); + // 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 kEpisilon = 0.00001f; - TIntermConstantUnion *epsilon = CreateFloatNode(kEpisilon); + static constexpr float kEpsilon = 0.0001f; + static constexpr float kThreshold = 0.5 + kEpsilon; + TIntermConstantUnion *threshold = CreateFloatNode(kThreshold); - // bp.x > epsilon - TIntermBinary *checkX = new TIntermBinary(EOpGreaterThan, bpX, epsilon); - - // Create a swizzle to "bp.y". - TVector swizzleOffsetY = {1}; - TIntermSwizzle *bpY = new TIntermSwizzle(bp->deepCopy(), swizzleOffsetY); - - // bp.y > epsilon - TIntermBinary *checkY = new TIntermBinary(EOpGreaterThan, bpY, epsilon->deepCopy()); - - // (bp.x > epsilon) && (bp.y > epsilon) + // 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); - - // if ((bp.x > epsilon) && (bp.y > epsilon)) discard; TIntermIfElse *ifStatement = new TIntermIfElse(checkXY, discardBlock, nullptr); // Ensure the line raster code runs at the beginning of main(). @@ -632,8 +654,8 @@ ANGLE_NO_DISCARD bool AddLineSegmentRasterizationEmulation(TCompiler *compiler, TIntermSequence *mainSequence = main->getBody()->getSequence(); ASSERT(mainSequence); - std::array nodes = { - {bDecl, baDecl, ba2Decl, bpDecl, ifStatement, GenerateEndIf()}}; + std::array 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. @@ -646,7 +668,6 @@ ANGLE_NO_DISCARD bool AddLineSegmentRasterizationEmulation(TCompiler *compiler, } mainSequence->insert(mainSequence->begin(), GenerateLineRasterIfDef()); - return compiler->validateAST(root); } @@ -836,8 +857,8 @@ bool TranslatorVulkan::translateImpl(TIntermBlock *root, } } - if (!AddLineSegmentRasterizationEmulation(this, sink, root, &getSymbolTable(), - driverUniforms, usesFragCoord)) + if (!AddBresenhamEmulationFS(this, sink, root, &getSymbolTable(), driverUniforms, + usesFragCoord)) { return false; } @@ -902,7 +923,10 @@ bool TranslatorVulkan::translateImpl(TIntermBlock *root, } else if (getShaderType() == GL_VERTEX_SHADER) { - AddANGLEPositionVarying(root, &getSymbolTable()); + if (!AddBresenhamEmulationVS(this, root, &getSymbolTable(), driverUniforms)) + { + return false; + } // Add a macro to declare transform feedback buffers. sink << "@@ XFB-DECL @@\n\n"; @@ -921,8 +945,6 @@ bool TranslatorVulkan::translateImpl(TIntermBlock *root, } else if (getShaderType() == GL_GEOMETRY_SHADER) { - AddANGLEPositionVarying(root, &getSymbolTable()); - WriteGeometryShaderLayoutQualifiers( sink, getGeometryShaderInputPrimitiveType(), getGeometryShaderInvocations(), getGeometryShaderOutputPrimitiveType(), getGeometryShaderMaxVertices()); diff --git a/src/compiler/translator/tree_util/IntermNode_util.h b/src/compiler/translator/tree_util/IntermNode_util.h index 2f563f2de..433b7cdc0 100644 --- a/src/compiler/translator/tree_util/IntermNode_util.h +++ b/src/compiler/translator/tree_util/IntermNode_util.h @@ -73,6 +73,23 @@ TIntermTyped *CreateBuiltInFunctionCallNode(const char *name, const TSymbolTable &symbolTable, int shaderVersion); +inline void GetSwizzleIndex(TVector *indexOut) {} + +template +void GetSwizzleIndex(TVector *indexOut, T arg, ArgsT... args) +{ + indexOut->push_back(arg); + GetSwizzleIndex(indexOut, args...); +} + +template +TIntermSwizzle *CreateSwizzle(TIntermTyped *reference, ArgsT... args) +{ + TVector swizzleIndex; + GetSwizzleIndex(&swizzleIndex, args...); + return new TIntermSwizzle(reference, swizzleIndex); +} + } // namespace sh #endif // COMPILER_TRANSLATOR_INTERMNODEUTIL_H_ diff --git a/src/libANGLE/Compiler.cpp b/src/libANGLE/Compiler.cpp index 383f2f720..7aca1929f 100644 --- a/src/libANGLE/Compiler.cpp +++ b/src/libANGLE/Compiler.cpp @@ -187,6 +187,9 @@ Compiler::Compiler(rx::GLImplFactory *implFactory, const State &state) mResources.MaxGeometryShaderStorageBlocks = caps.maxShaderStorageBlocks[ShaderType::Geometry]; mResources.MaxGeometryShaderInvocations = caps.maxGeometryShaderInvocations; mResources.MaxGeometryImageUniforms = caps.maxShaderImageUniforms[ShaderType::Geometry]; + + // Subpixel bits. + mResources.SubPixelBits = static_cast(caps.subPixelBits); } Compiler::~Compiler() diff --git a/src/libANGLE/renderer/vulkan/doc/OpenGLLineSegmentRasterization.md b/src/libANGLE/renderer/vulkan/doc/OpenGLLineSegmentRasterization.md index cfa0a05ac..5afa27242 100644 --- a/src/libANGLE/renderer/vulkan/doc/OpenGLLineSegmentRasterization.md +++ b/src/libANGLE/renderer/vulkan/doc/OpenGLLineSegmentRasterization.md @@ -23,22 +23,29 @@ more info. See the below diagram for an illustration of the diamond rule: ![OpenGL Diamond Rule Example][DiamondRule] -We can implement the OpenGL test by checking the intersection of the line and the medial axes of the -pixel `p`. If the length of the line segment between intersections `p` and the point center is -greater than a half-pixel for all possible `p` then the pixel is not on the segment. To solve for -`p` we use the pixel center `a` given by `gl_FragCoord` and the projection of `a` onto the line -segment `b` given by the interpolated `gl_Position`. Since `gl_Position` is not available in the -fragment shader we must add an internal position varying when drawing lines. +The diamond rule can be implemented in the fragment shader by computing the +intersection between the line segment and the grid that crosses the pixel +center. If the distance between an intersection and the pixel center is less +than half a pixel then the line enters and exits the diamond. `f` is the pixel +center in the diagram. The green circle indicates a diamond exit and the red +circles indicate intersections that do not exit the diamond. We detect +non-Bresenham fragments when both intersections are outside the diamond. -The full code derivation is omitted for brevity. It reduces to the following shader snippet: +The full code derivation is omitted for brevity. It produces the following +fragment shader patch implementation: -```vec2 position = PositionVarying.xy / PositionVarying.w; -vec2 b = ((position * 0.5) + 0.5) * gl_Viewport.zw + gl_Viewport.xy; -vec2 ba = abs(b - gl_FragCoord.xy); -vec2 ba2 = 2.0 * (ba * ba); -vec2 bp = ba2 + ba2.yx - ba; -if (bp.x > epsilon && bp.y > epsilon) - discard; +``` +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.500001 && i.y > 0.500001) + discard; ``` Note that we must also pass the viewport size as an internal uniform. We use a small epsilon value @@ -46,8 +53,22 @@ to correct for cases when the line segment is perfectly parallel or perpendicula code please see [TranslatorVulkan.cpp][TranslatorVulkan.cpp] under `AddLineSegmentRasterizationEmulation`. +## Limitations + +Although this emulation passes all current GLES CTS tests it is not guaranteed +to produce conformant lines. In particular lines that very nearly intersect +the junction of four pixels render with holes. For example: + +![Holes in the emulated Bresenham line][Holes] + +Therefore for a complete implementation we require the Bresenham line +rasterization feature from +[VK_EXT_line_rasterization][VK_EXT_line_rasterization]. + [Bresenham]: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm [DiamondRule]: img/LineRasterPixelExample.png +[Holes]: img/LineRasterHoles.jpg [TranslatorVulkan.cpp]: https://chromium.googlesource.com/angle/angle/+/refs/heads/master/src/compiler/translator/TranslatorVulkan.cpp +[VK_EXT_line_rasterization]: https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VK_EXT_line_rasterization.html [VulkanLineRaster]: https://www.khronos.org/registry/vulkan/specs/1.1/html/chap24.html#primsrast-lines-basic [VulkanVsGLLineRaster]: img/LineRasterComparison.gif diff --git a/src/libANGLE/renderer/vulkan/doc/img/LineRasterHoles.jpg b/src/libANGLE/renderer/vulkan/doc/img/LineRasterHoles.jpg new file mode 100644 index 000000000..9ff2ac6bd Binary files /dev/null and b/src/libANGLE/renderer/vulkan/doc/img/LineRasterHoles.jpg differ diff --git a/src/libANGLE/renderer/vulkan/doc/img/LineRasterPixelExample.png b/src/libANGLE/renderer/vulkan/doc/img/LineRasterPixelExample.png index 29923fb00..20505a1f5 100644 Binary files a/src/libANGLE/renderer/vulkan/doc/img/LineRasterPixelExample.png and b/src/libANGLE/renderer/vulkan/doc/img/LineRasterPixelExample.png differ