Fix rotation artefacts by rendering curves in two passes, clipping at X

inflection points as necessary.
This commit is contained in:
Patrick Walton 2017-12-03 13:20:46 -08:00
parent 801c25305f
commit 9b59ce2443
7 changed files with 358 additions and 93 deletions

View File

@ -23,6 +23,7 @@ export interface ShaderMap<T> {
direct3DInterior: T;
ecaaLine: T;
ecaaCurve: T;
ecaaTransformedCurve: T;
mcaaCover: T;
mcaaLine: T;
mcaaCurve: T;
@ -32,6 +33,7 @@ export interface ShaderMap<T> {
xcaaMultiDirectCurve: T;
xcaaMultiDirectInterior: T;
xcaaMultiEdgeMaskCurve: T;
xcaaMultiEdgeMaskTransformedCurve: T;
xcaaMultiEdgeMaskLine: T;
xcaaMultiResolve: T;
}
@ -57,11 +59,13 @@ export const SHADER_NAMES: Array<keyof ShaderMap<void>> = [
'mcaaCurve',
'ecaaLine',
'ecaaCurve',
'ecaaTransformedCurve',
'xcaaMonoResolve',
'xcaaMonoSubpixelResolve',
'xcaaMultiDirectCurve',
'xcaaMultiDirectInterior',
'xcaaMultiEdgeMaskCurve',
'xcaaMultiEdgeMaskTransformedCurve',
'xcaaMultiEdgeMaskLine',
'xcaaMultiResolve',
'demo3DDistantGlyph',
@ -113,6 +117,10 @@ const SHADER_URLS: ShaderMap<ShaderProgramURLs> = {
fragment: "/glsl/gles2/xcaa-line.fs.glsl",
vertex: "/glsl/gles2/ecaa-line.vs.glsl",
},
ecaaTransformedCurve: {
fragment: "/glsl/gles2/xcaa-curve.fs.glsl",
vertex: "/glsl/gles2/ecaa-transformed-curve.vs.glsl",
},
mcaaCover: {
fragment: "/glsl/gles2/mcaa-cover.fs.glsl",
vertex: "/glsl/gles2/mcaa-cover.vs.glsl",
@ -153,6 +161,10 @@ const SHADER_URLS: ShaderMap<ShaderProgramURLs> = {
fragment: "/glsl/gles2/xcaa-multi-edge-mask-line.fs.glsl",
vertex: "/glsl/gles2/ecaa-multi-edge-mask-line.vs.glsl",
},
xcaaMultiEdgeMaskTransformedCurve: {
fragment: "/glsl/gles2/xcaa-multi-edge-mask-curve.fs.glsl",
vertex: "/glsl/gles2/ecaa-multi-edge-mask-transformed-curve.vs.glsl",
},
xcaaMultiResolve: {
fragment: "/glsl/gles2/xcaa-multi-resolve.fs.glsl",
vertex: "/glsl/gles2/xcaa-multi-resolve.vs.glsl",

View File

@ -781,9 +781,10 @@ export abstract class ECAAStrategy extends XCAAStrategy {
this.antialiasLinesOfObjectWithProgram(renderer,
objectIndex,
this.lineShaderProgramNames[0]);
this.antialiasCurvesOfObjectWithProgram(renderer,
this.antialiasCurvesOfObjectWithPrograms(renderer,
objectIndex,
this.curveShaderProgramNames[0]);
this.curveShaderProgramNames[0],
this.curveShaderProgramNames[1]);
}
protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): void {
@ -850,9 +851,24 @@ export abstract class ECAAStrategy extends XCAAStrategy {
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
}
protected antialiasCurvesOfObjectWithProgram(renderer: Renderer,
protected antialiasCurvesOfObjectWithPrograms(renderer: Renderer,
objectIndex: number,
programName: keyof ShaderMap<void>):
stProgram: keyof ShaderMap<void>,
transformedProgram: keyof ShaderMap<void>):
void {
if (renderer.usesSTTransform) {
this.antialiasCurvesOfObjectWithProgram(renderer, objectIndex, stProgram, 0);
return;
}
this.antialiasCurvesOfObjectWithProgram(renderer, objectIndex, transformedProgram, 0);
this.antialiasCurvesOfObjectWithProgram(renderer, objectIndex, transformedProgram, 1);
}
private antialiasCurvesOfObjectWithProgram(renderer: Renderer,
objectIndex: number,
programName: keyof ShaderMap<void>,
passIndex: number):
void {
if (renderer.meshData == null)
return;
@ -867,6 +883,7 @@ export abstract class ECAAStrategy extends XCAAStrategy {
gl.useProgram(curveProgram.program);
const uniforms = curveProgram.uniforms;
this.setAAUniforms(renderer, uniforms, objectIndex);
gl.uniform1i(uniforms.uPassIndex, passIndex);
const vao = this.curveVAOs[programName];
renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao);
@ -1061,7 +1078,7 @@ export class ECAAMonochromeStrategy extends ECAAStrategy {
}
protected get curveShaderProgramNames(): Array<keyof ShaderMap<void>> {
return ['ecaaCurve'];
return ['ecaaCurve', 'ecaaTransformedCurve'];
}
}
@ -1188,7 +1205,12 @@ export class ECAAMulticolorStrategy extends ECAAStrategy {
}
protected get curveShaderProgramNames(): Array<keyof ShaderMap<void>> {
return ['ecaaCurve', 'xcaaMultiEdgeMaskCurve'];
return [
'ecaaCurve',
'ecaaTransformedCurve',
'xcaaMultiEdgeMaskCurve',
'xcaaMultiEdgeMaskTransformedCurve',
];
}
private edgeMaskVAO: WebGLVertexArrayObject;
@ -1233,7 +1255,10 @@ export class ECAAMulticolorStrategy extends ECAAStrategy {
// Perform edge masking.
gl.colorMask(false, false, false, false);
this.antialiasLinesOfObjectWithProgram(renderer, objectIndex, 'xcaaMultiEdgeMaskLine');
this.antialiasCurvesOfObjectWithProgram(renderer, objectIndex, 'xcaaMultiEdgeMaskCurve');
this.antialiasCurvesOfObjectWithPrograms(renderer,
objectIndex,
'xcaaMultiEdgeMaskCurve',
'xcaaMultiEdgeMaskTransformedCurve');
gl.colorMask(true, true, true, true);
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);

View File

@ -126,17 +126,6 @@ vec2 computeXCAAClipSpaceQuadPosition(vec4 extents, vec2 quadPosition, ivec2 fra
return convertScreenToClipSpace(position, framebufferSize);
}
vec2 computeXCAAEdgeBoundedClipSpaceQuadPosition(vec2 leftPosition,
vec2 rightPosition,
vec2 quadPosition,
ivec2 framebufferSize) {
vec4 extents = vec4(leftPosition.x,
min(leftPosition.y, rightPosition.y),
rightPosition.x,
max(leftPosition.y, rightPosition.y));
return computeXCAAClipSpaceQuadPosition(extents, quadPosition, framebufferSize);
}
vec2 computeMCAAPosition(vec2 position,
vec4 hints,
vec4 localTransformST,
@ -172,10 +161,11 @@ bool computeMCAAQuadPosition(out vec2 outPosition,
return false;
}
outPosition = computeXCAAEdgeBoundedClipSpaceQuadPosition(leftPosition,
rightPosition,
quadPosition,
framebufferSize);
vec4 extents = vec4(leftPosition.x,
min(leftPosition.y, rightPosition.y),
rightPosition.x,
max(leftPosition.y, rightPosition.y));
outPosition = computeXCAAClipSpaceQuadPosition(extents, quadPosition, framebufferSize);
return true;
}
@ -228,6 +218,31 @@ float computeECAAWinding(inout vec2 leftPosition, inout vec2 rightPosition) {
return rightPosition.x - leftPosition.x > EPSILON ? winding : 0.0;
}
vec2 computeECAAQuadPositionFromTransformedPositions(vec2 leftPosition,
vec2 rightPosition,
vec2 quadPosition,
ivec2 framebufferSize,
vec4 localTransformST,
vec2 localTransformExt,
mat4 globalTransform,
vec4 bounds,
vec3 leftTopRightEdges) {
vec2 edgeBL = bounds.xy, edgeTL = bounds.xw, edgeTR = bounds.zw, edgeBR = bounds.zy;
edgeBL = transformECAAPosition(edgeBL, localTransformST, localTransformExt, globalTransform);
edgeBR = transformECAAPosition(edgeBR, localTransformST, localTransformExt, globalTransform);
edgeTL = transformECAAPosition(edgeTL, localTransformST, localTransformExt, globalTransform);
edgeTR = transformECAAPosition(edgeTR, localTransformST, localTransformExt, globalTransform);
// Find the bottom of the path, and convert to clip space.
//
// FIXME(pcwalton): Speed this up somehow?
float pathBottomY = max(max(edgeBL.y, edgeBR.y), max(edgeTL.y, edgeTR.y));
pathBottomY = (pathBottomY + 1.0) * 0.5 * float(framebufferSize.y);
vec4 extents = vec4(leftTopRightEdges, pathBottomY);
return computeXCAAClipSpaceQuadPosition(extents, quadPosition, framebufferSize);
}
// FIXME(pcwalton): Clean up this signature somehow?
bool computeECAAQuadPosition(out vec2 outPosition,
out float outWinding,
@ -259,12 +274,6 @@ bool computeECAAQuadPosition(out vec2 outPosition,
globalTransform,
framebufferSize);
vec2 edgeBL = bounds.xy, edgeTL = bounds.xw, edgeTR = bounds.zw, edgeBR = bounds.zy;
edgeBL = transformECAAPosition(edgeBL, localTransformST, localTransformExt, globalTransform);
edgeBR = transformECAAPosition(edgeBR, localTransformST, localTransformExt, globalTransform);
edgeTL = transformECAAPosition(edgeTL, localTransformST, localTransformExt, globalTransform);
edgeTR = transformECAAPosition(edgeTR, localTransformST, localTransformExt, globalTransform);
float winding = computeECAAWinding(leftPosition, rightPosition);
outWinding = winding;
if (winding == 0.0) {
@ -272,49 +281,54 @@ bool computeECAAQuadPosition(out vec2 outPosition,
return false;
}
// Find the bottom of the path, and convert to clip space.
//
// FIXME(pcwalton): Speed this up somehow?
float pathBottomY = max(max(edgeBL.y, edgeBR.y), max(edgeTL.y, edgeTR.y));
pathBottomY = (pathBottomY + 1.0) * 0.5 * float(framebufferSize.y);
vec4 extents = vec4(leftPosition.x,
vec3 leftTopRightEdges = vec3(leftPosition.x,
min(leftPosition.y, rightPosition.y),
rightPosition.x,
pathBottomY);
outPosition = computeXCAAClipSpaceQuadPosition(extents, quadPosition, framebufferSize);
rightPosition.x);
outPosition = computeECAAQuadPositionFromTransformedPositions(leftPosition,
rightPosition,
quadPosition,
framebufferSize,
localTransformST,
localTransformExt,
globalTransform,
bounds,
leftTopRightEdges);
return true;
}
bool computeECAAMultiEdgeMaskQuadPosition(out vec2 outPosition,
bool splitCurveAndComputeECAAWinding(out float outWinding,
out vec3 outLeftTopRightEdges,
inout vec2 leftPosition,
inout vec2 rightPosition,
vec2 quadPosition,
ivec2 framebufferSize,
vec4 localTransformST,
vec2 localTransformExt,
mat4 globalTransform) {
leftPosition = transformECAAPositionToScreenSpace(leftPosition,
localTransformST,
localTransformExt,
globalTransform,
framebufferSize);
rightPosition = transformECAAPositionToScreenSpace(rightPosition,
localTransformST,
localTransformExt,
globalTransform,
framebufferSize);
float winding = computeECAAWinding(leftPosition, rightPosition);
if (winding == 0.0) {
outPosition = vec2(0.0);
vec2 controlPointPosition,
int passIndex) {
// Split at the X inflection point if necessary.
float num = leftPosition.x - controlPointPosition.x;
float denom = leftPosition.x - 2.0 * controlPointPosition.x + rightPosition.x;
float inflectionT = num / denom;
if (inflectionT > EPSILON && inflectionT < 1.0 - EPSILON) {
vec2 newCP0 = mix(leftPosition, controlPointPosition, inflectionT);
vec2 newCP1 = mix(controlPointPosition, rightPosition, inflectionT);
vec2 inflectionPoint = mix(newCP0, newCP1, inflectionT);
if (passIndex == 0) {
controlPointPosition = newCP0;
rightPosition = inflectionPoint;
} else {
controlPointPosition = newCP1;
leftPosition = inflectionPoint;
}
} else if (passIndex != 0) {
return false;
}
outPosition = computeXCAAEdgeBoundedClipSpaceQuadPosition(leftPosition,
rightPosition,
quadPosition,
framebufferSize);
float winding = computeECAAWinding(leftPosition, rightPosition);
outWinding = winding;
if (winding == 0.0)
return false;
outLeftTopRightEdges = vec3(min(leftPosition.x, controlPointPosition.x),
min(min(leftPosition.y, controlPointPosition.y), rightPosition.y),
max(rightPosition.x, controlPointPosition.x));
return true;
}

View File

@ -16,6 +16,7 @@ uniform ivec2 uPathTransformSTDimensions;
uniform sampler2D uPathTransformST;
uniform ivec2 uPathTransformExtDimensions;
uniform sampler2D uPathTransformExt;
uniform int uPassIndex;
attribute vec2 aQuadPosition;
attribute vec2 aLeftPosition;
@ -40,23 +41,35 @@ void main() {
uPathTransformExtDimensions,
pathID);
// Transform the points, and compute the position of this vertex.
vec2 position;
if (computeECAAMultiEdgeMaskQuadPosition(position,
leftPosition,
rightPosition,
aQuadPosition,
uFramebufferSize,
// Transform the points.
leftPosition = transformECAAPositionToScreenSpace(leftPosition,
pathTransformST,
pathTransformExt,
uTransform)) {
uTransform,
uFramebufferSize);
rightPosition = transformECAAPositionToScreenSpace(rightPosition,
pathTransformST,
pathTransformExt,
uTransform,
uFramebufferSize);
controlPointPosition = transformECAAPositionToScreenSpace(controlPointPosition,
pathTransformST,
pathTransformExt,
uTransform,
uFramebufferSize);
float winding = computeECAAWinding(leftPosition, rightPosition);
if (winding == 0.0) {
gl_Position = vec4(0.0);
return;
}
vec4 extents = vec4(leftPosition.x,
min(leftPosition.y, rightPosition.y),
rightPosition.y,
max(leftPosition.y, rightPosition.y));
vec2 position = computeXCAAClipSpaceQuadPosition(extents, aQuadPosition, uFramebufferSize);
float depth = convertPathIndexToViewportDepthValue(pathID);
gl_Position = vec4(position, depth, 1.0);

View File

@ -38,16 +38,29 @@ void main() {
uPathTransformExtDimensions,
pathID);
// Transform the points, and compute the position of this vertex.
vec2 position;
computeECAAMultiEdgeMaskQuadPosition(position,
leftPosition,
rightPosition,
aQuadPosition,
uFramebufferSize,
// Transform the points.
leftPosition = transformECAAPositionToScreenSpace(leftPosition,
pathTransformST,
pathTransformExt,
uTransform);
uTransform,
uFramebufferSize);
rightPosition = transformECAAPositionToScreenSpace(rightPosition,
pathTransformST,
pathTransformExt,
uTransform,
uFramebufferSize);
float winding = computeECAAWinding(leftPosition, rightPosition);
if (winding == 0.0) {
gl_Position = vec4(0.0);
return;
}
vec4 extents = vec4(min(leftPosition.x, rightPosition.x),
min(leftPosition.y, rightPosition.y),
max(rightPosition.x, rightPosition.x),
max(leftPosition.y, rightPosition.y));
vec2 position = computeXCAAClipSpaceQuadPosition(extents, aQuadPosition, uFramebufferSize);
float depth = convertPathIndexToViewportDepthValue(pathID);

View File

@ -0,0 +1,83 @@
// pathfinder/shaders/gles2/ecaa-multi-edge-mask-transformed-curve.vs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
precision highp float;
uniform mat4 uTransform;
uniform ivec2 uFramebufferSize;
uniform ivec2 uPathTransformSTDimensions;
uniform sampler2D uPathTransformST;
uniform ivec2 uPathTransformExtDimensions;
uniform sampler2D uPathTransformExt;
uniform int uPassIndex;
attribute vec2 aQuadPosition;
attribute vec2 aLeftPosition;
attribute vec2 aControlPointPosition;
attribute vec2 aRightPosition;
attribute float aPathID;
varying vec4 vEndpoints;
varying vec2 vControlPoint;
void main() {
vec2 leftPosition = aLeftPosition;
vec2 controlPointPosition = aControlPointPosition;
vec2 rightPosition = aRightPosition;
int pathID = int(aPathID);
vec2 pathTransformExt;
vec4 pathTransformST = fetchPathAffineTransform(pathTransformExt,
uPathTransformST,
uPathTransformSTDimensions,
uPathTransformExt,
uPathTransformExtDimensions,
pathID);
// Transform the points.
leftPosition = transformECAAPositionToScreenSpace(leftPosition,
pathTransformST,
pathTransformExt,
uTransform,
uFramebufferSize);
rightPosition = transformECAAPositionToScreenSpace(rightPosition,
pathTransformST,
pathTransformExt,
uTransform,
uFramebufferSize);
controlPointPosition = transformECAAPositionToScreenSpace(controlPointPosition,
pathTransformST,
pathTransformExt,
uTransform,
uFramebufferSize);
float winding;
vec3 leftTopRightEdges;
if (!splitCurveAndComputeECAAWinding(winding,
leftTopRightEdges,
leftPosition,
rightPosition,
controlPointPosition,
uPassIndex)) {
gl_Position = vec4(0.0);
return;
}
vec4 extents = vec4(leftTopRightEdges,
max(max(leftPosition.y, rightPosition.y), controlPointPosition.y));
vec2 position = computeXCAAClipSpaceQuadPosition(extents, aQuadPosition, uFramebufferSize);
float depth = convertPathIndexToViewportDepthValue(pathID);
gl_Position = vec4(position, depth, 1.0);
vEndpoints = vec4(leftPosition, rightPosition);
vControlPoint = controlPointPosition;
}

View File

@ -0,0 +1,105 @@
// pathfinder/shaders/gles2/ecaa-transformed-curve.vs.glsl
//
// Copyright (c) 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
precision highp float;
uniform mat4 uTransform;
uniform vec4 uHints;
uniform ivec2 uFramebufferSize;
uniform ivec2 uPathTransformSTDimensions;
uniform sampler2D uPathTransformST;
uniform ivec2 uPathTransformExtDimensions;
uniform sampler2D uPathTransformExt;
uniform ivec2 uPathBoundsDimensions;
uniform sampler2D uPathBounds;
uniform vec2 uEmboldenAmount;
uniform int uPassIndex;
attribute vec2 aQuadPosition;
attribute vec2 aLeftPosition;
attribute vec2 aControlPointPosition;
attribute vec2 aRightPosition;
attribute float aPathID;
attribute vec3 aNormalAngles;
varying vec4 vEndpoints;
varying vec2 vControlPoint;
varying float vWinding;
void main() {
vec2 leftPosition = aLeftPosition;
vec2 controlPointPosition = aControlPointPosition;
vec2 rightPosition = aRightPosition;
int pathID = int(aPathID);
vec2 pathTransformExt;
vec4 pathTransformST = fetchPathAffineTransform(pathTransformExt,
uPathTransformST,
uPathTransformSTDimensions,
uPathTransformExt,
uPathTransformExtDimensions,
pathID);
vec4 bounds = fetchFloat4Data(uPathBounds, pathID, uPathBoundsDimensions);
// Transform the points.
leftPosition = computeECAAPosition(leftPosition,
aNormalAngles.x,
uEmboldenAmount,
uHints,
pathTransformST,
pathTransformExt,
uTransform,
uFramebufferSize);
rightPosition = computeECAAPosition(rightPosition,
aNormalAngles.z,
uEmboldenAmount,
uHints,
pathTransformST,
pathTransformExt,
uTransform,
uFramebufferSize);
controlPointPosition = computeECAAPosition(controlPointPosition,
aNormalAngles.y,
uEmboldenAmount,
uHints,
pathTransformST,
pathTransformExt,
uTransform,
uFramebufferSize);
float winding;
vec3 leftTopRightEdges;
if (!splitCurveAndComputeECAAWinding(winding,
leftTopRightEdges,
leftPosition,
rightPosition,
controlPointPosition,
uPassIndex)) {
gl_Position = vec4(0.0);
return;
}
vec2 position = computeECAAQuadPositionFromTransformedPositions(leftPosition,
rightPosition,
aQuadPosition,
uFramebufferSize,
pathTransformST,
pathTransformExt,
uTransform,
bounds,
leftTopRightEdges);
float depth = convertPathIndexToViewportDepthValue(pathID);
gl_Position = vec4(position, depth, 1.0);
vEndpoints = vec4(leftPosition, rightPosition);
vControlPoint = controlPointPosition;
vWinding = winding;
}