Vulkan: Improve Bresenham line emulation.

Clamps the vertex position to the subpixel grid before interpolation.
This will give more correct results on systems that have less than
8 bits of subpixel accuracy.

Also uses a more accurate formulation for the emulation filter in the
fragment shader using dfdx and dfdy.

Fixes line raster CTS tests on SwiftShader. Still does not produce spec
conformant lines. Updates the public docs to indicate this.

Bug: angleproject:2830
Change-Id: Ib9a268df3e7d986bd2b1348be664389fe8fc0ef2
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1826598
Commit-Queue: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Tim Van Patten <timvp@google.com>
This commit is contained in:
Jamie Madill 2019-11-09 16:24:50 -05:00 коммит произвёл Commit Bot
Родитель b9b1bae4ef
Коммит 3f647b1bf9
8 изменённых файлов: 196 добавлений и 128 удалений

Просмотреть файл

@ -504,6 +504,9 @@ struct ShBuiltInResources
int MaxGeometryShaderStorageBlocks;
int MaxGeometryShaderInvocations;
int MaxGeometryImageUniforms;
// Subpixel bits used in rasterization.
int SubPixelBits;
};
//

Просмотреть файл

@ -253,6 +253,8 @@ void InitBuiltInResources(ShBuiltInResources *resources)
resources->MaxGeometryShaderStorageBlocks = 0;
resources->MaxGeometryShaderInvocations = 32;
resources->MaxGeometryImageUniforms = 0;
resources->SubPixelBits = 8;
}
//

Просмотреть файл

@ -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<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);
// 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<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(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;
// <otherwise run fragment shader main>
// }
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<EbtFloat, 2>();
TIntermBinary *viewportRef = CreateDriverUniformRef(driverUniforms, kViewport);
const TType *vec2Type = StaticType::GetBasic<EbtFloat, 2>();
// Create a swizzle to "ANGLEUniforms.viewport.xy".
TIntermBinary *viewportRef = CreateDriverUniformRef(driverUniforms, kViewport);
TVector<int> swizzleOffsetXY = {0, 1};
TIntermSwizzle *viewportXY = new TIntermSwizzle(viewportRef->deepCopy(), swizzleOffsetXY);
// Create a swizzle to "ANGLEUniforms.viewport.zw".
TVector<int> 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<int> 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<int> 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<int> 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<int> 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<TIntermNode *, 6> nodes = {
{bDecl, baDecl, ba2Decl, bpDecl, ifStatement, GenerateEndIf()}};
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.
@ -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());

Просмотреть файл

@ -73,6 +73,23 @@ TIntermTyped *CreateBuiltInFunctionCallNode(const char *name,
const TSymbolTable &symbolTable,
int shaderVersion);
inline void GetSwizzleIndex(TVector<int> *indexOut) {}
template <typename T, typename... ArgsT>
void GetSwizzleIndex(TVector<int> *indexOut, T arg, ArgsT... args)
{
indexOut->push_back(arg);
GetSwizzleIndex(indexOut, args...);
}
template <typename... ArgsT>
TIntermSwizzle *CreateSwizzle(TIntermTyped *reference, ArgsT... args)
{
TVector<int> swizzleIndex;
GetSwizzleIndex(&swizzleIndex, args...);
return new TIntermSwizzle(reference, swizzleIndex);
}
} // namespace sh
#endif // COMPILER_TRANSLATOR_INTERMNODEUTIL_H_

Просмотреть файл

@ -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<int>(caps.subPixelBits);
}
Compiler::~Compiler()

Просмотреть файл

@ -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

Двоичные данные
src/libANGLE/renderer/vulkan/doc/img/LineRasterHoles.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.0 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 8.1 KiB

После

Ширина:  |  Высота:  |  Размер: 17 KiB