From 9b59ce244308ba207bb370cc667424436954fda2 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sun, 3 Dec 2017 13:20:46 -0800 Subject: [PATCH] Fix rotation artefacts by rendering curves in two passes, clipping at X inflection points as necessary. --- demo/client/src/shader-loader.ts | 12 ++ demo/client/src/xcaa-strategy.ts | 45 ++++-- shaders/gles2/common.inc.glsl | 130 ++++++++++-------- .../gles2/ecaa-multi-edge-mask-curve.vs.glsl | 43 ++++-- .../gles2/ecaa-multi-edge-mask-line.vs.glsl | 33 +++-- ...-multi-edge-mask-transformed-curve.vs.glsl | 83 +++++++++++ shaders/gles2/ecaa-transformed-curve.vs.glsl | 105 ++++++++++++++ 7 files changed, 358 insertions(+), 93 deletions(-) create mode 100644 shaders/gles2/ecaa-multi-edge-mask-transformed-curve.vs.glsl create mode 100644 shaders/gles2/ecaa-transformed-curve.vs.glsl diff --git a/demo/client/src/shader-loader.ts b/demo/client/src/shader-loader.ts index 2014c06e..33717725 100644 --- a/demo/client/src/shader-loader.ts +++ b/demo/client/src/shader-loader.ts @@ -23,6 +23,7 @@ export interface ShaderMap { direct3DInterior: T; ecaaLine: T; ecaaCurve: T; + ecaaTransformedCurve: T; mcaaCover: T; mcaaLine: T; mcaaCurve: T; @@ -32,6 +33,7 @@ export interface ShaderMap { xcaaMultiDirectCurve: T; xcaaMultiDirectInterior: T; xcaaMultiEdgeMaskCurve: T; + xcaaMultiEdgeMaskTransformedCurve: T; xcaaMultiEdgeMaskLine: T; xcaaMultiResolve: T; } @@ -57,11 +59,13 @@ export const SHADER_NAMES: Array> = [ 'mcaaCurve', 'ecaaLine', 'ecaaCurve', + 'ecaaTransformedCurve', 'xcaaMonoResolve', 'xcaaMonoSubpixelResolve', 'xcaaMultiDirectCurve', 'xcaaMultiDirectInterior', 'xcaaMultiEdgeMaskCurve', + 'xcaaMultiEdgeMaskTransformedCurve', 'xcaaMultiEdgeMaskLine', 'xcaaMultiResolve', 'demo3DDistantGlyph', @@ -113,6 +117,10 @@ const SHADER_URLS: ShaderMap = { 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 = { 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", diff --git a/demo/client/src/xcaa-strategy.ts b/demo/client/src/xcaa-strategy.ts index 94a80cdc..3045d5d5 100644 --- a/demo/client/src/xcaa-strategy.ts +++ b/demo/client/src/xcaa-strategy.ts @@ -781,9 +781,10 @@ export abstract class ECAAStrategy extends XCAAStrategy { this.antialiasLinesOfObjectWithProgram(renderer, objectIndex, this.lineShaderProgramNames[0]); - this.antialiasCurvesOfObjectWithProgram(renderer, - objectIndex, - this.curveShaderProgramNames[0]); + this.antialiasCurvesOfObjectWithPrograms(renderer, + objectIndex, + this.curveShaderProgramNames[0], + this.curveShaderProgramNames[1]); } protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): void { @@ -850,10 +851,25 @@ export abstract class ECAAStrategy extends XCAAStrategy { renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); } - protected antialiasCurvesOfObjectWithProgram(renderer: Renderer, - objectIndex: number, - programName: keyof ShaderMap): - void { + protected antialiasCurvesOfObjectWithPrograms(renderer: Renderer, + objectIndex: number, + stProgram: keyof ShaderMap, + transformedProgram: keyof ShaderMap): + 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, + 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> { - return ['ecaaCurve']; + return ['ecaaCurve', 'ecaaTransformedCurve']; } } @@ -1188,7 +1205,12 @@ export class ECAAMulticolorStrategy extends ECAAStrategy { } protected get curveShaderProgramNames(): Array> { - 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); diff --git a/shaders/gles2/common.inc.glsl b/shaders/gles2/common.inc.glsl index 1e2fc3b0..cec2cd44 100644 --- a/shaders/gles2/common.inc.glsl +++ b/shaders/gles2/common.inc.glsl @@ -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, - min(leftPosition.y, rightPosition.y), - rightPosition.x, - pathBottomY); - outPosition = computeXCAAClipSpaceQuadPosition(extents, quadPosition, framebufferSize); + vec3 leftTopRightEdges = vec3(leftPosition.x, + min(leftPosition.y, rightPosition.y), + rightPosition.x); + outPosition = computeECAAQuadPositionFromTransformedPositions(leftPosition, + rightPosition, + quadPosition, + framebufferSize, + localTransformST, + localTransformExt, + globalTransform, + bounds, + leftTopRightEdges); return true; } -bool computeECAAMultiEdgeMaskQuadPosition(out vec2 outPosition, - 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); +bool splitCurveAndComputeECAAWinding(out float outWinding, + out vec3 outLeftTopRightEdges, + inout vec2 leftPosition, + inout vec2 rightPosition, + 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; } diff --git a/shaders/gles2/ecaa-multi-edge-mask-curve.vs.glsl b/shaders/gles2/ecaa-multi-edge-mask-curve.vs.glsl index 0f80573c..af0fc60c 100644 --- a/shaders/gles2/ecaa-multi-edge-mask-curve.vs.glsl +++ b/shaders/gles2/ecaa-multi-edge-mask-curve.vs.glsl @@ -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, - pathTransformST, - pathTransformExt, - uTransform)) { - controlPointPosition = transformECAAPositionToScreenSpace(controlPointPosition, - pathTransformST, - pathTransformExt, - uTransform, - uFramebufferSize); + // 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 = 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); diff --git a/shaders/gles2/ecaa-multi-edge-mask-line.vs.glsl b/shaders/gles2/ecaa-multi-edge-mask-line.vs.glsl index b6361fbf..72daf5b9 100644 --- a/shaders/gles2/ecaa-multi-edge-mask-line.vs.glsl +++ b/shaders/gles2/ecaa-multi-edge-mask-line.vs.glsl @@ -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, - pathTransformST, - pathTransformExt, - uTransform); + // Transform the points. + leftPosition = transformECAAPositionToScreenSpace(leftPosition, + pathTransformST, + pathTransformExt, + 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); diff --git a/shaders/gles2/ecaa-multi-edge-mask-transformed-curve.vs.glsl b/shaders/gles2/ecaa-multi-edge-mask-transformed-curve.vs.glsl new file mode 100644 index 00000000..0637aaca --- /dev/null +++ b/shaders/gles2/ecaa-multi-edge-mask-transformed-curve.vs.glsl @@ -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 or the MIT license +// , 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; +} + diff --git a/shaders/gles2/ecaa-transformed-curve.vs.glsl b/shaders/gles2/ecaa-transformed-curve.vs.glsl new file mode 100644 index 00000000..ec92c909 --- /dev/null +++ b/shaders/gles2/ecaa-transformed-curve.vs.glsl @@ -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 or the MIT license +// , 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; +}