From 6942bb51ca6c8b8a88ff505e2efaf9a0aed434d7 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 22 Nov 2017 21:10:51 -0800 Subject: [PATCH] Add support for full 3D transforms, including rotation, to SVG in XCAA mode. --- demo/client/src/renderer.ts | 18 +- demo/client/src/shader-loader.ts | 7 +- demo/client/src/svg-demo.ts | 25 +- demo/client/src/svg-loader.ts | 35 +- demo/client/src/xcaa-strategy.ts | 423 ++++++++++++++--------- demo/server/src/main.rs | 10 +- path-utils/src/lib.rs | 1 + path-utils/src/monotonic.rs | 1 + shaders/gles2/common.inc.glsl | 30 +- shaders/gles2/ecaa-curve.vs.glsl | 8 +- shaders/gles2/ecaa-line.vs.glsl | 4 +- shaders/gles2/xcaa-edge-detect.fs.glsl | 79 ----- shaders/gles2/xcaa-edge-detect.vs.glsl | 21 -- shaders/gles2/xcaa-multi-resolve.fs.glsl | 2 +- 14 files changed, 340 insertions(+), 324 deletions(-) delete mode 100644 shaders/gles2/xcaa-edge-detect.fs.glsl delete mode 100644 shaders/gles2/xcaa-edge-detect.vs.glsl diff --git a/demo/client/src/renderer.ts b/demo/client/src/renderer.ts index e363a7d5..67293992 100644 --- a/demo/client/src/renderer.ts +++ b/demo/client/src/renderer.ts @@ -21,7 +21,7 @@ import {CompositingOperation, RenderTaskType} from './render-task'; import {ShaderMap} from './shader-loader'; import {FLOAT32_SIZE, Range, UINT16_SIZE, UINT32_SIZE, unwrapNull, unwrapUndef} from './utils'; import {RenderContext, Timings} from "./view"; -import {MCAAMulticolorStrategy} from './xcaa-strategy'; +import {ECAAMulticolorStrategy} from './xcaa-strategy'; const MAX_PATHS: number = 65535; @@ -232,6 +232,12 @@ export abstract class Renderer { gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]); } + setTransformUniform(uniforms: UniformMap, objectIndex: number) { + const transform = glmatrix.mat4.clone(this.worldTransform); + glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex)); + this.renderContext.gl.uniformMatrix4fv(uniforms.uTransform, false, transform); + } + setTransformSTUniform(uniforms: UniformMap, objectIndex: number): void { // FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an ST matrix is ugly and fragile. // Refactor. @@ -444,7 +450,7 @@ export abstract class Renderer { this.pathTransformBufferTextures[meshIndex] .bind(gl, directInteriorProgram.uniforms, 1); if (renderingMode === 'color-depth') { - const strategy = antialiasingStrategy as MCAAMulticolorStrategy; + const strategy = antialiasingStrategy as ECAAMulticolorStrategy; strategy.bindEdgeDepthTexture(gl, directInteriorProgram.uniforms, 2); } const coverInteriorRange = getMeshIndexRange(meshes.coverInteriorIndexRanges, pathRange); @@ -486,7 +492,7 @@ export abstract class Renderer { this.setEmboldenAmountUniform(objectIndex, directCurveProgram.uniforms); this.pathTransformBufferTextures[meshIndex].bind(gl, directCurveProgram.uniforms, 1); if (renderingMode === 'color-depth') { - const strategy = antialiasingStrategy as MCAAMulticolorStrategy; + const strategy = antialiasingStrategy as ECAAMulticolorStrategy; strategy.bindEdgeDepthTexture(gl, directCurveProgram.uniforms, 2); } const coverCurveRange = getMeshIndexRange(meshes.coverCurveIndexRanges, pathRange); @@ -687,12 +693,6 @@ export abstract class Renderer { gl.bufferData(gl.ARRAY_BUFFER, pathIDs, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, null); } - - private setTransformUniform(uniforms: UniformMap, objectIndex: number) { - const transform = glmatrix.mat4.clone(this.worldTransform); - glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex)); - this.renderContext.gl.uniformMatrix4fv(uniforms.uTransform, false, transform); - } } function getMeshIndexRange(indexRanges: Range[], pathRange: Range): Range { diff --git a/demo/client/src/shader-loader.ts b/demo/client/src/shader-loader.ts index d4d19f48..61375704 100644 --- a/demo/client/src/shader-loader.ts +++ b/demo/client/src/shader-loader.ts @@ -147,11 +147,11 @@ const SHADER_URLS: ShaderMap = { }, xcaaMultiEdgeMaskCurve: { fragment: "/glsl/gles2/xcaa-multi-edge-mask-curve.fs.glsl", - vertex: "/glsl/gles2/mcaa-curve.vs.glsl", + vertex: "/glsl/gles2/ecaa-curve.vs.glsl", }, xcaaMultiEdgeMaskLine: { fragment: "/glsl/gles2/xcaa-multi-edge-mask-line.fs.glsl", - vertex: "/glsl/gles2/mcaa-line.vs.glsl", + vertex: "/glsl/gles2/ecaa-line.vs.glsl", }, xcaaMultiResolve: { fragment: "/glsl/gles2/xcaa-multi-resolve.fs.glsl", @@ -198,10 +198,13 @@ export class PathfinderShaderProgram { readonly uniforms: UniformMap; readonly attributes: AttributeMap; readonly program: WebGLProgram; + readonly programName: string; constructor(gl: WebGLRenderingContext, programName: string, unlinkedShaderProgram: UnlinkedShaderProgram) { + this.programName = programName; + this.program = expectNotNull(gl.createProgram(), "Failed to create shader program!"); for (const compiledShader of Object.values(unlinkedShaderProgram)) gl.attachShader(this.program, compiledShader); diff --git a/demo/client/src/svg-demo.ts b/demo/client/src/svg-demo.ts index d946fda9..af4564f4 100644 --- a/demo/client/src/svg-demo.ts +++ b/demo/client/src/svg-demo.ts @@ -25,7 +25,7 @@ import SSAAStrategy from "./ssaa-strategy"; import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader'; import {panic, Range, unwrapNull} from './utils'; import {DemoView, Timings} from './view'; -import {MCAAMulticolorStrategy, XCAAStrategy} from "./xcaa-strategy"; +import {ECAAMulticolorStrategy, XCAAStrategy} from "./xcaa-strategy"; const parseColor = require('parse-color'); @@ -36,7 +36,7 @@ const DEFAULT_FILE: string = 'tiger'; const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { none: NoAAStrategy, ssaa: SSAAStrategy, - xcaa: MCAAMulticolorStrategy, + xcaa: ECAAMulticolorStrategy, }; interface AntialiasingStrategyTable { @@ -82,7 +82,7 @@ class SVGDemoController extends DemoAppController { private meshesReceived(): void { this.view.then(view => { view.attachMeshes([this.meshes]); - view.initCameraBounds(this.loader.bounds); + view.initCameraBounds(this.loader.svgBounds); }); } } @@ -157,8 +157,11 @@ class SVGDemoRenderer extends Renderer { } pathBoundingRects(objectIndex: number): Float32Array { - // TODO - return new Float32Array(0); + const loader = this.renderContext.appController.loader; + const boundingRectsBuffer = new Float32Array((loader.pathBounds.length + 1) * 4); + for (let pathIndex = 0; pathIndex < loader.pathBounds.length; pathIndex++) + boundingRectsBuffer.set(loader.pathBounds[pathIndex], (pathIndex + 1) * 4); + return boundingRectsBuffer; } attachMeshes(meshes: PathfinderMeshData[]): void { @@ -203,13 +206,11 @@ class SVGDemoRenderer extends Renderer { glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]); glmatrix.mat4.scale(transform, transform, [2.0 / canvas.width, 2.0 / canvas.height, 1.0]); - if (!(this.antialiasingStrategy instanceof XCAAStrategy)) { - const centerPoint = glmatrix.vec3.clone([canvas.width * 0.5, canvas.height * 0.5, 0.0]); - glmatrix.mat4.translate(transform, transform, centerPoint); - glmatrix.mat4.rotateZ(transform, transform, this.camera.rotationAngle); - glmatrix.vec3.negate(centerPoint, centerPoint); - glmatrix.mat4.translate(transform, transform, centerPoint); - } + const centerPoint = glmatrix.vec3.clone([canvas.width * 0.5, canvas.height * 0.5, 0.0]); + glmatrix.mat4.translate(transform, transform, centerPoint); + glmatrix.mat4.rotateZ(transform, transform, this.camera.rotationAngle); + glmatrix.vec3.negate(centerPoint, centerPoint); + glmatrix.mat4.translate(transform, transform, centerPoint); const translation = this.camera.translation; glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]); diff --git a/demo/client/src/svg-loader.ts b/demo/client/src/svg-loader.ts index a37c26c5..5e8f0817 100644 --- a/demo/client/src/svg-loader.ts +++ b/demo/client/src/svg-loader.ts @@ -74,7 +74,8 @@ export class SVGLoader { renderTasks: RenderTask[]; pathInstances: SVGPath[]; scale: number; - bounds: glmatrix.vec4; + pathBounds: glmatrix.vec4[]; + svgBounds: glmatrix.vec4; private svg: SVGSVGElement; private fileData: ArrayBuffer; @@ -86,8 +87,8 @@ export class SVGLoader { this.scale = 1.0; this.renderTasks = []; this.pathInstances = []; - this.paths = []; - this.bounds = glmatrix.vec4.create(); + this.pathBounds = []; + this.svgBounds = glmatrix.vec4.create(); this.svg = unwrapNull(document.getElementById('pf-svg')) as Element as SVGSVGElement; } @@ -133,10 +134,10 @@ export class SVGLoader { this.scanElement(this.svg); this.popTopRenderTaskIfEmpty(); - let minX = 0, minY = 0, maxX = 0, maxY = 0; this.paths = []; - // Extract, normalize, and transform the path data. + const svgBottomLeft = glmatrix.vec2.create(), svgTopRight = glmatrix.vec2.create(); + for (const instance of this.pathInstances) { const element = instance.element; const svgCTM = element.getCTM(); @@ -146,15 +147,16 @@ export class SVGLoader { glmatrix.mat2d.scale(ctm, ctm, [1.0, -1.0]); glmatrix.mat2d.scale(ctm, ctm, [this.scale, this.scale]); + const bottomLeft = glmatrix.vec2.create(); + const topRight = glmatrix.vec2.create(); + const segments = element.getPathData({ normalize: true }).map(segment => { const newValues = _.flatMap(_.chunk(segment.values, 2), coords => { const point = glmatrix.vec2.create(); glmatrix.vec2.transformMat2d(point, coords, ctm); - minX = Math.min(point[0], minX); - minY = Math.min(point[1], minY); - maxX = Math.max(point[0], maxX); - maxY = Math.max(point[1], maxY); + glmatrix.vec2.min(bottomLeft, bottomLeft, point); + glmatrix.vec2.max(topRight, topRight, point); return [point[0], point[1]]; }); @@ -164,17 +166,30 @@ export class SVGLoader { }; }); + const pathBounds = glmatrix.vec4.clone([ + bottomLeft[0], bottomLeft[1], + topRight[0], topRight[1], + ]); + + glmatrix.vec2.min(svgBottomLeft, svgBottomLeft, bottomLeft); + glmatrix.vec2.max(svgTopRight, svgTopRight, topRight); + if (instance instanceof SVGFill) { this.paths.push({ segments: segments, kind: 'Fill' }); + this.pathBounds.push(pathBounds); } else if (instance instanceof SVGStroke) { this.paths.push({ kind: { Stroke: Math.max(HAIRLINE_STROKE_WIDTH, instance.width) }, segments: segments, }); + this.pathBounds.push(pathBounds); } } - this.bounds = glmatrix.vec4.clone([minX, minY, maxX, maxY]); + this.svgBounds = glmatrix.vec4.clone([ + svgBottomLeft[0], svgBottomLeft[1], + svgTopRight[0], svgTopRight[1], + ]); } private scanElement(element: Element): void { diff --git a/demo/client/src/xcaa-strategy.ts b/demo/client/src/xcaa-strategy.ts index f748d1a3..72e145d9 100644 --- a/demo/client/src/xcaa-strategy.ts +++ b/demo/client/src/xcaa-strategy.ts @@ -18,7 +18,7 @@ import {createFramebufferDepthTexture, setTextureParameters, UniformMap} from '. import {WebGLVertexArrayObject} from './gl-utils'; import {B_QUAD_LOWER_INDICES_OFFSET, B_QUAD_SIZE, B_QUAD_UPPER_INDICES_OFFSET} from './meshes'; import {Renderer} from './renderer'; -import {PathfinderShaderProgram} from './shader-loader'; +import {PathfinderShaderProgram, ShaderMap} from './shader-loader'; import {computeStemDarkeningAmount} from './text'; import {assert, FLOAT32_SIZE, lerp, Range, UINT16_SIZE, UINT32_SIZE, unwrapNull} from './utils'; import {unwrapUndef} from './utils'; @@ -36,6 +36,8 @@ const DIRECTIONS: Direction[] = ['upper', 'lower']; export abstract class XCAAStrategy extends AntialiasingStrategy { abstract readonly directRenderingMode: DirectRenderingMode; + protected abstract get usesDilationTransforms(): boolean; + protected pathBoundsBufferTexture: PathfinderBufferTexture; protected supersampledFramebufferSize: glmatrix.vec2; @@ -225,7 +227,11 @@ export abstract class XCAAStrategy extends AntialiasingStrategy { const renderContext = renderer.renderContext; const gl = renderContext.gl; - renderer.setTransformSTUniform(uniforms, 0); + if (this.usesDilationTransforms) + renderer.setTransformSTUniform(uniforms, 0); + else + renderer.setTransformUniform(uniforms, 0); + gl.uniform2i(uniforms.uFramebufferSize, this.supersampledFramebufferSize[0], this.supersampledFramebufferSize[1]); @@ -746,9 +752,12 @@ export abstract class MCAAStrategy extends XCAAStrategy { } } -export class ECAAStrategy extends XCAAStrategy { - private lineVAO: WebGLVertexArrayObject; - private curveVAO: WebGLVertexArrayObject; +export abstract class ECAAStrategy extends XCAAStrategy { + protected abstract get lineShaderProgramNames(): Array>; + protected abstract get curveShaderProgramNames(): Array>; + + private lineVAOs: Partial>; + private curveVAOs: Partial>; get directRenderingMode(): DirectRenderingMode { return 'none'; @@ -757,16 +766,23 @@ export class ECAAStrategy extends XCAAStrategy { attachMeshes(renderer: Renderer): void { super.attachMeshes(renderer); - this.createLineVAO(renderer); - this.createCurveVAO(renderer); + this.createLineVAOs(renderer); + this.createCurveVAOs(renderer); } antialiasObject(renderer: Renderer, objectIndex: number): void { super.antialiasObject(renderer, objectIndex); // Antialias. - this.antialiasLinesOfObject(renderer, objectIndex); - this.antialiasCurvesOfObject(renderer, objectIndex); + const shaderPrograms = renderer.renderContext.shaderPrograms; + this.setAAState(renderer); + this.setBlendModeForAA(renderer); + this.antialiasLinesOfObjectWithProgram(renderer, + objectIndex, + this.lineShaderProgramNames[0]); + this.antialiasCurvesOfObjectWithProgram(renderer, + objectIndex, + this.curveShaderProgramNames[0]); } protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): void { @@ -804,140 +820,10 @@ export class ECAAStrategy extends XCAAStrategy { gl.clear(gl.COLOR_BUFFER_BIT); } - private setBlendModeForAA(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - gl.blendEquation(gl.FUNC_ADD); - gl.blendFunc(gl.ONE, gl.ONE); - gl.enable(gl.BLEND); - } - - private createLineVAO(renderer: Renderer): void { - if (renderer.meshes == null) - return; - - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - const lineProgram = renderContext.shaderPrograms.ecaaLine; - const attributes = lineProgram.attributes; - - const vao = renderContext.vertexArrayObjectExt.createVertexArrayOES(); - renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao); - - const lineVertexPositionsBuffer = renderer.meshes[0].segmentLines; - const linePathIDsBuffer = renderer.meshes[0].segmentLinePathIDs; - const lineNormalsBuffer = renderer.meshes[0].segmentLineNormals; - - gl.useProgram(lineProgram.program); - gl.bindBuffer(gl.ARRAY_BUFFER, renderContext.quadPositionsBuffer); - gl.vertexAttribPointer(attributes.aQuadPosition, 2, gl.FLOAT, false, 0, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexPositionsBuffer); - gl.vertexAttribPointer(attributes.aLeftPosition, 2, gl.FLOAT, false, FLOAT32_SIZE * 4, 0); - gl.vertexAttribPointer(attributes.aRightPosition, - 2, - gl.FLOAT, - false, - FLOAT32_SIZE * 4, - FLOAT32_SIZE * 2); - gl.bindBuffer(gl.ARRAY_BUFFER, linePathIDsBuffer); - gl.vertexAttribPointer(attributes.aPathID, 1, gl.UNSIGNED_SHORT, false, 0, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, lineNormalsBuffer); - gl.vertexAttribPointer(attributes.aLeftNormalAngle, - 1, - gl.FLOAT, - false, - FLOAT32_SIZE * 2, - 0); - gl.vertexAttribPointer(attributes.aRightNormalAngle, - 1, - gl.FLOAT, - false, - FLOAT32_SIZE * 2, - FLOAT32_SIZE); - - gl.enableVertexAttribArray(attributes.aQuadPosition); - gl.enableVertexAttribArray(attributes.aLeftPosition); - gl.enableVertexAttribArray(attributes.aRightPosition); - gl.enableVertexAttribArray(attributes.aPathID); - gl.enableVertexAttribArray(attributes.aLeftNormalAngle); - gl.enableVertexAttribArray(attributes.aRightNormalAngle); - - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aLeftPosition, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aRightPosition, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aLeftNormalAngle, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aRightNormalAngle, 1); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer); - - renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); - - this.lineVAO = vao; - } - - private createCurveVAO(renderer: Renderer): void { - if (renderer.meshes == null) - return; - - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - const curveProgram = renderContext.shaderPrograms.ecaaCurve; - const attributes = curveProgram.attributes; - - const vao = renderContext.vertexArrayObjectExt.createVertexArrayOES(); - renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao); - - const curveVertexPositionsBuffer = renderer.meshes[0].segmentCurves; - const curvePathIDsBuffer = renderer.meshes[0].segmentCurvePathIDs; - const curveNormalsBuffer = renderer.meshes[0].segmentCurveNormals; - - gl.useProgram(curveProgram.program); - gl.bindBuffer(gl.ARRAY_BUFFER, renderContext.quadPositionsBuffer); - gl.vertexAttribPointer(attributes.aQuadPosition, 2, gl.FLOAT, false, 0, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, curveVertexPositionsBuffer); - gl.vertexAttribPointer(attributes.aLeftPosition, 2, gl.FLOAT, false, FLOAT32_SIZE * 6, 0); - gl.vertexAttribPointer(attributes.aControlPointPosition, - 2, - gl.FLOAT, - false, - FLOAT32_SIZE * 6, - FLOAT32_SIZE * 2); - gl.vertexAttribPointer(attributes.aRightPosition, - 2, - gl.FLOAT, - false, - FLOAT32_SIZE * 6, - FLOAT32_SIZE * 4); - gl.bindBuffer(gl.ARRAY_BUFFER, curvePathIDsBuffer); - gl.vertexAttribPointer(attributes.aPathID, 1, gl.UNSIGNED_SHORT, false, 0, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, curveNormalsBuffer); - gl.vertexAttribPointer(attributes.aNormalAngles, 3, gl.FLOAT, false, FLOAT32_SIZE * 3, 0); - - gl.enableVertexAttribArray(attributes.aQuadPosition); - gl.enableVertexAttribArray(attributes.aLeftPosition); - gl.enableVertexAttribArray(attributes.aControlPointPosition); - gl.enableVertexAttribArray(attributes.aRightPosition); - gl.enableVertexAttribArray(attributes.aPathID); - gl.enableVertexAttribArray(attributes.aNormalAngles); - - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aLeftPosition, 1); - renderContext.instancedArraysExt - .vertexAttribDivisorANGLE(attributes.aControlPointPosition, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aRightPosition, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aNormalAngles, 1); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer); - - renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); - - this.curveVAO = vao; - } - - private antialiasLinesOfObject(renderer: Renderer, objectIndex: number): void { + protected antialiasLinesOfObjectWithProgram(renderer: Renderer, + objectIndex: number, + programName: keyof ShaderMap): + void { if (renderer.meshData == null) return; @@ -947,19 +833,14 @@ export class ECAAStrategy extends XCAAStrategy { const pathRange = renderer.pathRangeForObject(objectIndex); const meshIndex = renderer.meshIndexForObject(objectIndex); - this.setAAState(renderer); - - const lineProgram = renderContext.shaderPrograms.ecaaLine; - + const lineProgram = renderContext.shaderPrograms[programName]; gl.useProgram(lineProgram.program); const uniforms = lineProgram.uniforms; this.setAAUniforms(renderer, uniforms, objectIndex); - const vao = this.lineVAO; + const vao = this.lineVAOs[programName]; renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao); - this.setBlendModeForAA(renderer); - // FIXME(pcwalton): Only render the appropriate instances. const count = renderer.meshData[meshIndex].segmentLineCount; renderContext.instancedArraysExt @@ -968,7 +849,10 @@ export class ECAAStrategy extends XCAAStrategy { renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); } - private antialiasCurvesOfObject(renderer: Renderer, objectIndex: number): void { + protected antialiasCurvesOfObjectWithProgram(renderer: Renderer, + objectIndex: number, + programName: keyof ShaderMap): + void { if (renderer.meshData == null) return; @@ -978,19 +862,14 @@ export class ECAAStrategy extends XCAAStrategy { const pathRange = renderer.pathRangeForObject(objectIndex); const meshIndex = renderer.meshIndexForObject(objectIndex); - this.setAAState(renderer); - - const curveProgram = renderContext.shaderPrograms.ecaaCurve; - + const curveProgram = renderContext.shaderPrograms[programName]; gl.useProgram(curveProgram.program); const uniforms = curveProgram.uniforms; this.setAAUniforms(renderer, uniforms, objectIndex); - const vao = this.curveVAO; + const vao = this.curveVAOs[programName]; renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao); - this.setBlendModeForAA(renderer); - // FIXME(pcwalton): Only render the appropriate instances. const count = renderer.meshData[meshIndex].segmentCurveCount; renderContext.instancedArraysExt @@ -998,9 +877,198 @@ export class ECAAStrategy extends XCAAStrategy { renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); } + + private setBlendModeForAA(renderer: Renderer): void { + const renderContext = renderer.renderContext; + const gl = renderContext.gl; + + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.ONE, gl.ONE); + gl.enable(gl.BLEND); + } + + private createLineVAOs(renderer: Renderer): void { + if (renderer.meshes == null || renderer.meshData == null) + return; + + const renderContext = renderer.renderContext; + const gl = renderContext.gl; + + this.lineVAOs = {}; + + for (const programName of this.lineShaderProgramNames) { + const lineProgram = renderContext.shaderPrograms[programName]; + const attributes = lineProgram.attributes; + + const vao = renderContext.vertexArrayObjectExt.createVertexArrayOES(); + renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao); + + const lineVertexPositionsBuffer = renderer.meshes[0].segmentLines; + const linePathIDsBuffer = renderer.meshes[0].segmentLinePathIDs; + const lineNormalsBuffer = renderer.meshes[0].segmentLineNormals; + + gl.useProgram(lineProgram.program); + gl.bindBuffer(gl.ARRAY_BUFFER, renderContext.quadPositionsBuffer); + gl.vertexAttribPointer(attributes.aQuadPosition, 2, gl.FLOAT, false, 0, 0); + gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexPositionsBuffer); + gl.vertexAttribPointer(attributes.aLeftPosition, + 2, + gl.FLOAT, + false, + FLOAT32_SIZE * 4, + 0); + gl.vertexAttribPointer(attributes.aRightPosition, + 2, + gl.FLOAT, + false, + FLOAT32_SIZE * 4, + FLOAT32_SIZE * 2); + gl.bindBuffer(gl.ARRAY_BUFFER, linePathIDsBuffer); + gl.vertexAttribPointer(attributes.aPathID, 1, gl.UNSIGNED_SHORT, false, 0, 0); + + gl.enableVertexAttribArray(attributes.aQuadPosition); + gl.enableVertexAttribArray(attributes.aLeftPosition); + gl.enableVertexAttribArray(attributes.aRightPosition); + gl.enableVertexAttribArray(attributes.aPathID); + + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aLeftPosition, 1); + renderContext.instancedArraysExt + .vertexAttribDivisorANGLE(attributes.aRightPosition, 1); + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1); + + if (renderer.meshData[0].segmentLineNormals.byteLength > 0) { + gl.bindBuffer(gl.ARRAY_BUFFER, lineNormalsBuffer); + gl.vertexAttribPointer(attributes.aLeftNormalAngle, + 1, + gl.FLOAT, + false, + FLOAT32_SIZE * 2, + 0); + gl.vertexAttribPointer(attributes.aRightNormalAngle, + 1, + gl.FLOAT, + false, + FLOAT32_SIZE * 2, + FLOAT32_SIZE); + + gl.enableVertexAttribArray(attributes.aLeftNormalAngle); + gl.enableVertexAttribArray(attributes.aRightNormalAngle); + + renderContext.instancedArraysExt + .vertexAttribDivisorANGLE(attributes.aLeftNormalAngle, 1); + renderContext.instancedArraysExt + .vertexAttribDivisorANGLE(attributes.aRightNormalAngle, 1); + } + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer); + + renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); + + this.lineVAOs[programName] = vao; + } + } + + private createCurveVAOs(renderer: Renderer): void { + if (renderer.meshes == null || renderer.meshData == null) + return; + + const renderContext = renderer.renderContext; + const gl = renderContext.gl; + + this.curveVAOs = {}; + + for (const programName of this.curveShaderProgramNames) { + const curveProgram = renderContext.shaderPrograms[programName]; + const attributes = curveProgram.attributes; + + const vao = renderContext.vertexArrayObjectExt.createVertexArrayOES(); + renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao); + + const curveVertexPositionsBuffer = renderer.meshes[0].segmentCurves; + const curvePathIDsBuffer = renderer.meshes[0].segmentCurvePathIDs; + const curveNormalsBuffer = renderer.meshes[0].segmentCurveNormals; + + gl.useProgram(curveProgram.program); + gl.bindBuffer(gl.ARRAY_BUFFER, renderContext.quadPositionsBuffer); + gl.vertexAttribPointer(attributes.aQuadPosition, 2, gl.FLOAT, false, 0, 0); + gl.bindBuffer(gl.ARRAY_BUFFER, curveVertexPositionsBuffer); + gl.vertexAttribPointer(attributes.aLeftPosition, + 2, + gl.FLOAT, + false, + FLOAT32_SIZE * 6, + 0); + gl.vertexAttribPointer(attributes.aControlPointPosition, + 2, + gl.FLOAT, + false, + FLOAT32_SIZE * 6, + FLOAT32_SIZE * 2); + gl.vertexAttribPointer(attributes.aRightPosition, + 2, + gl.FLOAT, + false, + FLOAT32_SIZE * 6, + FLOAT32_SIZE * 4); + gl.bindBuffer(gl.ARRAY_BUFFER, curvePathIDsBuffer); + gl.vertexAttribPointer(attributes.aPathID, 1, gl.UNSIGNED_SHORT, false, 0, 0); + + gl.enableVertexAttribArray(attributes.aQuadPosition); + gl.enableVertexAttribArray(attributes.aLeftPosition); + gl.enableVertexAttribArray(attributes.aControlPointPosition); + gl.enableVertexAttribArray(attributes.aRightPosition); + gl.enableVertexAttribArray(attributes.aPathID); + + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aLeftPosition, 1); + renderContext.instancedArraysExt + .vertexAttribDivisorANGLE(attributes.aControlPointPosition, 1); + renderContext.instancedArraysExt + .vertexAttribDivisorANGLE(attributes.aRightPosition, 1); + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1); + + if (renderer.meshData[0].segmentCurveNormals.byteLength > 0) { + gl.bindBuffer(gl.ARRAY_BUFFER, curveNormalsBuffer); + gl.vertexAttribPointer(attributes.aNormalAngles, + 3, + gl.FLOAT, + false, + FLOAT32_SIZE * 3, + 0); + + gl.enableVertexAttribArray(attributes.aNormalAngles); + + renderContext.instancedArraysExt + .vertexAttribDivisorANGLE(attributes.aNormalAngles, 1); + } + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer); + + renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); + + this.curveVAOs[programName] = vao; + } + } +} + +export class ECAAMonochromeStrategy extends ECAAStrategy { + protected get usesDilationTransforms(): boolean { + return false; + } + + protected get lineShaderProgramNames(): Array> { + return ['ecaaLine']; + } + + protected get curveShaderProgramNames(): Array> { + return ['ecaaCurve']; + } } export class MCAAMonochromeStrategy extends MCAAStrategy { + protected get usesDilationTransforms(): boolean { + return true; + } + protected getResolveProgram(renderContext: RenderContext): PathfinderShaderProgram { if (this.subpixelAA !== 'none') return renderContext.shaderPrograms.xcaaMonoSubpixelResolve; @@ -1049,7 +1117,7 @@ export class AdaptiveMonochromeXCAAStrategy implements AntialiasingStrategy { constructor(level: number, subpixelAA: SubpixelAAType) { this.mcaaStrategy = new MCAAMonochromeStrategy(level, subpixelAA); - this.ecaaStrategy = new ECAAStrategy(level, subpixelAA); + this.ecaaStrategy = new ECAAMonochromeStrategy(level, subpixelAA); } init(renderer: Renderer): void { @@ -1106,7 +1174,19 @@ export class AdaptiveMonochromeXCAAStrategy implements AntialiasingStrategy { } } -export class MCAAMulticolorStrategy extends MCAAStrategy { +export class ECAAMulticolorStrategy extends ECAAStrategy { + protected get usesDilationTransforms(): boolean { + return false; + } + + protected get lineShaderProgramNames(): Array> { + return ['ecaaLine', 'xcaaMultiEdgeMaskLine']; + } + + protected get curveShaderProgramNames(): Array> { + return ['ecaaCurve', 'xcaaMultiEdgeMaskCurve']; + } + private edgeMaskVAO: WebGLVertexArrayObject; bindEdgeDepthTexture(gl: WebGLRenderingContext, uniforms: UniformMap, textureUnit: number): @@ -1136,7 +1216,7 @@ export class MCAAMulticolorStrategy extends MCAAStrategy { this.supersampledFramebufferSize[0], this.supersampledFramebufferSize[1]); - gl.colorMask(false, false, false, false); + gl.colorMask(true, true, true, true); gl.depthMask(true); gl.depthFunc(gl.GREATER); gl.enable(gl.DEPTH_TEST); @@ -1147,12 +1227,9 @@ export class MCAAMulticolorStrategy extends MCAAStrategy { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Perform edge masking. - const edgeMaskLineProgram = renderer.renderContext.shaderPrograms.xcaaMultiEdgeMaskLine; - gl.useProgram(edgeMaskLineProgram.program); - this.antialiasLinesOfObjectWithProgram(renderer, objectIndex, edgeMaskLineProgram); - const edgeMaskCurveProgram = renderer.renderContext.shaderPrograms.xcaaMultiEdgeMaskCurve; - gl.useProgram(edgeMaskCurveProgram.program); - this.antialiasCurvesOfObjectWithProgram(renderer, objectIndex, edgeMaskCurveProgram); + gl.colorMask(false, false, false, false); + this.antialiasLinesOfObjectWithProgram(renderer, objectIndex, 'xcaaMultiEdgeMaskLine'); + this.antialiasCurvesOfObjectWithProgram(renderer, objectIndex, 'xcaaMultiEdgeMaskCurve'); gl.colorMask(true, true, true, true); renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); @@ -1160,17 +1237,19 @@ export class MCAAMulticolorStrategy extends MCAAStrategy { protected setCoverDepthState(renderer: Renderer): void { const renderContext = renderer.renderContext; - renderContext.gl.depthMask(false); - renderContext.gl.depthFunc(renderContext.gl.EQUAL); - renderContext.gl.enable(renderContext.gl.DEPTH_TEST); + const gl = renderContext.gl; + + gl.depthMask(false); + gl.depthFunc(gl.EQUAL); + gl.enable(gl.DEPTH_TEST); } protected clearForAA(renderer: Renderer): void { - const renderContext = renderer.renderContext; + /*const renderContext = renderer.renderContext; const gl = renderContext.gl; gl.clearColor(0.0, 0.0, 0.0, 0.0); - gl.clear(gl.COLOR_BUFFER_BIT); + gl.clear(gl.COLOR_BUFFER_BIT);*/ } protected setAADepthState(renderer: Renderer): void { diff --git a/demo/server/src/main.rs b/demo/server/src/main.rs index 3f71f50d..e46db93c 100644 --- a/demo/server/src/main.rs +++ b/demo/server/src/main.rs @@ -425,8 +425,9 @@ fn partition_svg_paths(request: Json) let mut path_buffer = PathBuffer::new(); let mut paths = vec![]; let mut last_point = Point2D::zero(); + let mut library = MeshLibrary::new(); - for path in &request.paths { + for (path_index, path) in request.paths.iter().enumerate() { let mut stream = vec![]; let first_subpath_index = path_buffer.subpaths.len() as u32; @@ -463,7 +464,9 @@ fn partition_svg_paths(request: Json) match path.kind { PartitionSvgPathKind::Fill => { - path_buffer.add_stream(MonotonicPathCommandStream::new(stream.into_iter())) + let stream = MonotonicPathCommandStream::new(stream.into_iter()); + library.push_segments((path_index + 1) as u16, stream.clone()); + path_buffer.add_stream(stream) } PartitionSvgPathKind::Stroke(stroke_width) => { let mut temp_path_buffer = PathBuffer::new(); @@ -471,6 +474,7 @@ fn partition_svg_paths(request: Json) let stream = PathBufferStream::new(&temp_path_buffer); let stream = MonotonicPathCommandStream::new(stream); + library.push_segments((path_index + 1) as u16, stream.clone()); path_buffer.add_stream(stream) } } @@ -481,7 +485,7 @@ fn partition_svg_paths(request: Json) } // Partition the paths. - let mut partitioner = Partitioner::new(MeshLibrary::new()); + let mut partitioner = Partitioner::new(library); partitioner.init_with_path_buffer(&path_buffer); let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner, &paths); diff --git a/path-utils/src/lib.rs b/path-utils/src/lib.rs index b4314d7d..913fd6e4 100644 --- a/path-utils/src/lib.rs +++ b/path-utils/src/lib.rs @@ -120,6 +120,7 @@ impl PathBuffer { } } +#[derive(Clone)] pub struct PathBufferStream<'a> { path_buffer: &'a PathBuffer, endpoint_index: u32, diff --git a/path-utils/src/monotonic.rs b/path-utils/src/monotonic.rs index c1e1b4d7..405cd9bd 100644 --- a/path-utils/src/monotonic.rs +++ b/path-utils/src/monotonic.rs @@ -15,6 +15,7 @@ use std::mem; use PathCommand; use curve::Curve; +#[derive(Clone)] pub struct MonotonicPathCommandStream { inner: I, queue: ArrayVec<[PathCommand; 2]>, diff --git a/shaders/gles2/common.inc.glsl b/shaders/gles2/common.inc.glsl index 2c045069..a5004a12 100644 --- a/shaders/gles2/common.inc.glsl +++ b/shaders/gles2/common.inc.glsl @@ -160,48 +160,60 @@ bool computeECAAQuadPosition(out vec2 outPosition, vec2 quadPosition, ivec2 framebufferSize, vec4 localTransformST, - vec4 globalTransformST, + mat4 globalTransform, vec4 hints, vec4 bounds, float leftNormalAngle, float rightNormalAngle, vec2 emboldenAmount) { + vec2 edgeBL = bounds.xy, edgeTL = bounds.xw, edgeTR = bounds.zw, edgeBR = bounds.zy; + leftPosition = dilatePosition(leftPosition, leftNormalAngle, emboldenAmount); rightPosition = dilatePosition(rightPosition, rightNormalAngle, emboldenAmount); leftPosition = hintPosition(leftPosition, hints); rightPosition = hintPosition(rightPosition, hints); - vec2 edgePosition = hintPosition(bounds.zw, hints); leftPosition = transformVertexPositionST(leftPosition, localTransformST); rightPosition = transformVertexPositionST(rightPosition, localTransformST); - edgePosition = transformVertexPositionST(edgePosition, localTransformST); + edgeBL = transformVertexPositionST(edgeBL, localTransformST); + edgeTL = transformVertexPositionST(edgeTL, localTransformST); + edgeBR = transformVertexPositionST(edgeBR, localTransformST); + edgeTR = transformVertexPositionST(edgeTR, localTransformST); - leftPosition = transformVertexPositionST(leftPosition, globalTransformST); - rightPosition = transformVertexPositionST(rightPosition, globalTransformST); - edgePosition = transformVertexPositionST(edgePosition, globalTransformST); + leftPosition = transformVertexPosition(leftPosition, globalTransform); + rightPosition = transformVertexPosition(rightPosition, globalTransform); + edgeBL = transformVertexPosition(edgeBL, globalTransform); + edgeTL = transformVertexPosition(edgeTL, globalTransform); + edgeBR = transformVertexPosition(edgeBR, globalTransform); + edgeTR = transformVertexPosition(edgeTR, globalTransform); leftPosition = convertClipToScreenSpace(leftPosition, framebufferSize); rightPosition = convertClipToScreenSpace(rightPosition, framebufferSize); - edgePosition = convertClipToScreenSpace(edgePosition, framebufferSize); float winding = sign(leftPosition.x - rightPosition.x); + outWinding = winding; if (winding > 0.0) { vec2 tmp = leftPosition; leftPosition = rightPosition; rightPosition = tmp; } - outWinding = winding; if (rightPosition.x - leftPosition.x <= EPSILON) { outPosition = vec2(0.0); 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 roundedExtents = vec4(floor(leftPosition.x), floor(min(leftPosition.y, rightPosition.y)), ceil(rightPosition.x), - ceil(edgePosition.y)); + ceil(pathBottomY)); vec2 position = mix(roundedExtents.xy, roundedExtents.zw, quadPosition); outPosition = convertScreenToClipSpace(position, framebufferSize); diff --git a/shaders/gles2/ecaa-curve.vs.glsl b/shaders/gles2/ecaa-curve.vs.glsl index bae69f1a..cf5e8d78 100644 --- a/shaders/gles2/ecaa-curve.vs.glsl +++ b/shaders/gles2/ecaa-curve.vs.glsl @@ -10,7 +10,7 @@ precision highp float; -uniform vec4 uTransformST; +uniform mat4 uTransform; uniform vec4 uHints; uniform ivec2 uFramebufferSize; uniform ivec2 uPathTransformDimensions; @@ -23,8 +23,8 @@ attribute vec2 aQuadPosition; attribute vec2 aLeftPosition; attribute vec2 aControlPointPosition; attribute vec2 aRightPosition; -attribute vec3 aNormalAngles; attribute float aPathID; +attribute vec3 aNormalAngles; varying vec4 vEndpoints; varying vec2 vControlPoint; @@ -52,7 +52,7 @@ void main() { aQuadPosition, uFramebufferSize, transform, - uTransformST, + uTransform, uHints, bounds, leftNormalAngle, @@ -63,7 +63,7 @@ void main() { uEmboldenAmount); controlPointPosition = hintPosition(controlPointPosition, uHints); controlPointPosition = transformVertexPositionST(controlPointPosition, transform); - controlPointPosition = transformVertexPositionST(controlPointPosition, uTransformST); + controlPointPosition = transformVertexPosition(controlPointPosition, uTransform); controlPointPosition = convertClipToScreenSpace(controlPointPosition, uFramebufferSize); } diff --git a/shaders/gles2/ecaa-line.vs.glsl b/shaders/gles2/ecaa-line.vs.glsl index d1f3c2cf..a63a820a 100644 --- a/shaders/gles2/ecaa-line.vs.glsl +++ b/shaders/gles2/ecaa-line.vs.glsl @@ -10,7 +10,7 @@ precision highp float; -uniform vec4 uTransformST; +uniform mat4 uTransform; uniform vec4 uHints; uniform ivec2 uFramebufferSize; uniform ivec2 uPathTransformDimensions; @@ -49,7 +49,7 @@ void main() { aQuadPosition, uFramebufferSize, transform, - uTransformST, + uTransform, uHints, bounds, leftNormalAngle, diff --git a/shaders/gles2/xcaa-edge-detect.fs.glsl b/shaders/gles2/xcaa-edge-detect.fs.glsl deleted file mode 100644 index 1753133d..00000000 --- a/shaders/gles2/xcaa-edge-detect.fs.glsl +++ /dev/null @@ -1,79 +0,0 @@ -// pathfinder/shaders/gles2/xcaa-edge-detect.fs.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 ivec2 uFramebufferSize; -uniform ivec2 uPathColorsDimensions; -uniform sampler2D uPathID; -uniform sampler2D uPathColors; - -varying vec2 vTexCoord; - -void checkFG(out vec2 fgPosition, out int fgPathID, vec2 queryPosition, int queryPathID) { - if (queryPathID > fgPathID) { - fgPosition = queryPosition; - fgPathID = queryPathID; - } -} - -void updateMinMaxInt(inout ivec2 minMax, int value) { - if (value < minMax.x) - minMax.x = value; - if (value > minMax.y) - minMax.y = value; -} - -ivec2 minMaxIVec4(ivec4 values) { - ivec2 minMax = ivec2(values.x); - updateMinMaxInt(minMax, values.y); - updateMinMaxInt(minMax, values.z); - updateMinMaxInt(minMax, values.w); - return minMax; -} - -void main() { - // Unpack. - vec2 position = vTexCoord; - - // Compute positions. - vec2 onePixel = 1.0 / vec2(uFramebufferSize); - vec2 positionL = position + vec2(-onePixel.x, 0.0); - vec2 positionR = position + vec2( onePixel.x, 0.0); - vec2 positionB = position + vec2(0.0, -onePixel.y); - vec2 positionT = position + vec2(0.0, onePixel.y); - - // Determine the topmost and bottommost paths. - int centerPathID = unpackPathID(texture2D(uPathID, position).rg); - ivec4 neighborPathIDs = ivec4(unpackPathID(texture2D(uPathID, positionL).rg), - unpackPathID(texture2D(uPathID, positionR).rg), - unpackPathID(texture2D(uPathID, positionB).rg), - unpackPathID(texture2D(uPathID, positionT).rg)); - ivec2 pathIDsBGFG = minMaxIVec4(neighborPathIDs); - - // Determine the depth. - // - // If all colors are the same, avoid touching this pixel in any further passes. - float outDepth; - if (pathIDsBGFG.x == pathIDsBGFG.y) - outDepth = 1.0; - else - outDepth = convertPathIndexToWindowDepthValue(pathIDsBGFG.y); - - // FIXME(pcwalton): Fetch the background color. - // FIXME(pcwalton): Output path ID for debugging. Switch to BG color. - //vec2 color = pathIDsBGFG.x == pathIDsBGFG.y ? vec2(1.0) : packPathID(pathIDsBGFG.y); - //vec4 color = fetchFloat4Data(uPathColors, pathIDsBGFG.x, uPathColorsDimensions); - - // Output results. - //gl_FragColor = vec4(packPathID(pathIDsBGFG.x), 0.0, 1.0); - gl_FragColor = vec4(packPathID(pathIDsBGFG.x), packPathID(pathIDsBGFG.y)); - gl_FragDepthEXT = outDepth; -} diff --git a/shaders/gles2/xcaa-edge-detect.vs.glsl b/shaders/gles2/xcaa-edge-detect.vs.glsl deleted file mode 100644 index b3e528c1..00000000 --- a/shaders/gles2/xcaa-edge-detect.vs.glsl +++ /dev/null @@ -1,21 +0,0 @@ -// pathfinder/shaders/gles2/xcaa-edge-detect.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; - -attribute vec2 aPosition; -attribute vec2 aTexCoord; - -varying vec2 vTexCoord; - -void main() { - gl_Position = vec4(mix(vec2(-1.0), vec2(1.0), aPosition), 0.0, 1.0); - vTexCoord = aTexCoord; -} diff --git a/shaders/gles2/xcaa-multi-resolve.fs.glsl b/shaders/gles2/xcaa-multi-resolve.fs.glsl index cd46bded..e2a64c46 100644 --- a/shaders/gles2/xcaa-multi-resolve.fs.glsl +++ b/shaders/gles2/xcaa-multi-resolve.fs.glsl @@ -21,7 +21,7 @@ void main() { float edgeDepth = texture2D(uAADepth, vTexCoord).r; int edgePathID = convertWindowDepthValueToPathIndex(edgeDepth); vec4 edgeColor = fetchFloat4Data(uPathColors, edgePathID, uPathColorsDimensions); - float edgeAlpha = texture2D(uAAAlpha, vTexCoord).r; + float edgeAlpha = abs(texture2D(uAAAlpha, vTexCoord).r); gl_FragColor = vec4(edgeColor.rgb, edgeColor.a * edgeAlpha); gl_FragDepthEXT = edgeDepth; }