From a84b7c7cbde7383187838af8673c46fea7532411 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 5 Feb 2018 15:10:52 -0800 Subject: [PATCH] Replace ECAA with "Stencil AAA", a distance-based antialiasing technique similar to the new MCAA. This new technique simplifies the code significantly by unifying lines, curves, and transformed curves. Blog posts forthcoming. --- demo/client/src/3d-demo.ts | 2 + demo/client/src/benchmark.ts | 11 +- demo/client/src/mesh-debugger.ts | 62 +-- demo/client/src/meshes.ts | 100 ++--- demo/client/src/reference-test.ts | 13 +- demo/client/src/renderer.ts | 1 + demo/client/src/shader-loader.ts | 24 +- demo/client/src/svg-demo.ts | 2 + demo/client/src/svg-renderer.ts | 2 + demo/client/src/text-demo.ts | 1 - demo/client/src/text-renderer.ts | 15 +- demo/client/src/text.ts | 2 +- demo/client/src/xcaa-strategy.ts | 403 ++++++------------- demo/server/src/main.rs | 10 +- partitioner/src/lib.rs | 9 +- partitioner/src/mesh_library.rs | 207 +++------- path-utils/src/normals.rs | 153 ++++--- path-utils/src/segments.rs | 5 +- shaders/gles2/common.inc.glsl | 205 ++-------- shaders/gles2/conservative-interior.vs.glsl | 3 +- shaders/gles2/ecaa-curve.fs.glsl | 45 --- shaders/gles2/ecaa-curve.vs.glsl | 148 ------- shaders/gles2/ecaa-line.fs.glsl | 31 -- shaders/gles2/ecaa-line.vs.glsl | 130 ------ shaders/gles2/ecaa-transformed-curve.vs.glsl | 162 -------- shaders/gles2/mcaa.fs.glsl | 37 +- shaders/gles2/mcaa.vs.glsl | 6 +- shaders/gles2/stencil-aaa.fs.glsl | 53 +++ shaders/gles2/stencil-aaa.vs.glsl | 123 ++++++ utils/frontend/src/main.rs | 1 + 30 files changed, 589 insertions(+), 1377 deletions(-) delete mode 100644 shaders/gles2/ecaa-curve.fs.glsl delete mode 100644 shaders/gles2/ecaa-curve.vs.glsl delete mode 100644 shaders/gles2/ecaa-line.fs.glsl delete mode 100644 shaders/gles2/ecaa-line.vs.glsl delete mode 100644 shaders/gles2/ecaa-transformed-curve.vs.glsl create mode 100644 shaders/gles2/stencil-aaa.fs.glsl create mode 100644 shaders/gles2/stencil-aaa.vs.glsl diff --git a/demo/client/src/3d-demo.ts b/demo/client/src/3d-demo.ts index 4a5db9fd..9c82f67f 100644 --- a/demo/client/src/3d-demo.ts +++ b/demo/client/src/3d-demo.ts @@ -364,6 +364,8 @@ class ThreeDRenderer extends Renderer { camera: PerspectiveCamera; + needsStencil: boolean = false; + get isMulticolor(): boolean { return false; } diff --git a/demo/client/src/benchmark.ts b/demo/client/src/benchmark.ts index fe15a4a9..2709d2ec 100644 --- a/demo/client/src/benchmark.ts +++ b/demo/client/src/benchmark.ts @@ -28,7 +28,7 @@ import {BUILTIN_FONT_URI, ExpandedMeshData, GlyphStore, PathfinderFont, TextFram import {computeStemDarkeningAmount, TextRun} from "./text"; import {assert, lerp, PathfinderError, unwrapNull, unwrapUndef} from "./utils"; import {DemoView, Timings} from "./view"; -import {AdaptiveMonochromeXCAAStrategy} from './xcaa-strategy'; +import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy'; const STRING: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -44,7 +44,7 @@ const MAX_RUNTIME: number = 3000; const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { none: NoAAStrategy, ssaa: SSAAStrategy, - xcaa: AdaptiveMonochromeXCAAStrategy, + xcaa: AdaptiveStencilMeshAAAStrategy, }; interface BenchmarkModeMap { @@ -57,7 +57,7 @@ type BenchmarkMode = 'text' | 'svg'; interface AntialiasingStrategyTable { none: typeof NoAAStrategy; ssaa: typeof SSAAStrategy; - xcaa: typeof AdaptiveMonochromeXCAAStrategy; + xcaa: typeof AdaptiveStencilMeshAAAStrategy; } interface TestParameter { @@ -423,9 +423,8 @@ class BenchmarkTextRenderer extends Renderer { camera: OrthographicCamera; - get isMulticolor(): boolean { - return false; - } + needsStencil: boolean = false; + isMulticolor: boolean = false; get usesSTTransform(): boolean { return this.camera.usesSTTransform; diff --git a/demo/client/src/mesh-debugger.ts b/demo/client/src/mesh-debugger.ts index c793b3bb..113872b7 100644 --- a/demo/client/src/mesh-debugger.ts +++ b/demo/client/src/mesh-debugger.ts @@ -1,6 +1,6 @@ // pathfinder/client/src/mesh-debugger.ts // -// Copyright © 2017 The Pathfinder Project Developers. +// Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -339,20 +339,9 @@ class MeshDebuggerView extends PathfinderView { // Draw segments. if (this.drawVertices) { drawSegmentVertices(context, - new Float32Array(meshes.segmentLines), - new Float32Array(meshes.segmentLineNormals), - meshes.segmentLineCount, - [0, 1], - null, - 2, - invScaleFactor, - this.drawControl, - this.drawNormals, - this.drawSegments); - drawSegmentVertices(context, - new Float32Array(meshes.segmentCurves), - new Float32Array(meshes.segmentCurveNormals), - meshes.segmentCurveCount, + new Float32Array(meshes.stencilSegments), + new Float32Array(meshes.stencilNormals), + meshes.stencilSegmentCount, [0, 2], 1, 3, @@ -421,7 +410,7 @@ function drawSegmentVertices(context: CanvasRenderingContext2D, drawSegments: boolean) { for (let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex++) { const positionStartOffset = segmentSize * 2 * segmentIndex; - const normalStartOffset = segmentSize * segmentIndex; + const normalStartOffset = segmentSize * 2 * segmentIndex; const position0 = glmatrix.vec2.clone([segments[positionStartOffset + endpointOffsets[0] * 2 + 0], @@ -439,14 +428,21 @@ function drawSegmentVertices(context: CanvasRenderingContext2D, controlPoint = null; } - const normal0 = normals[normalStartOffset + endpointOffsets[0]]; - const normal1 = normals[normalStartOffset + endpointOffsets[1]]; + const normal0 = + glmatrix.vec2.clone([normals[normalStartOffset + endpointOffsets[0] * 2 + 0], + normals[normalStartOffset + endpointOffsets[0] * 2 + 1]]); + const normal1 = + glmatrix.vec2.clone([normals[normalStartOffset + endpointOffsets[1] * 2 + 0], + normals[normalStartOffset + endpointOffsets[1] * 2 + 1]]); - let normalControlPoint: number | null; - if (controlPointOffset != null) - normalControlPoint = normals[normalStartOffset + controlPointOffset]; - else + let normalControlPoint: glmatrix.vec2 | null; + if (controlPointOffset != null) { + normalControlPoint = + glmatrix.vec2.clone([normals[normalStartOffset + controlPointOffset * 2 + 0], + normals[normalStartOffset + controlPointOffset * 2 + 1]]); + } else { normalControlPoint = null; + } if (drawNormals) { drawNormal(context, position0, normal0, invScaleFactor, 'edge'); @@ -528,26 +524,34 @@ function drawSegmentControlPoint(context: CanvasRenderingContext2D, function drawNormal(context: CanvasRenderingContext2D, position: glmatrix.vec2, - normalAngle: number, + normalVector: glmatrix.vec2, invScaleFactor: number, normalType: NormalType) { const length = invScaleFactor * NORMAL_LENGTHS[normalType]; const arrowheadLength = invScaleFactor * NORMAL_ARROWHEAD_LENGTH; - const endpoint = glmatrix.vec2.clone([position[0] + length * Math.cos(normalAngle), - -position[1] + length * Math.sin(normalAngle)]); + const endpoint = glmatrix.vec2.clone([position[0] + length * normalVector[0], + -position[1] + length * -normalVector[1]]); context.save(); context.strokeStyle = NORMAL_STROKE_STYLES[normalType]; context.beginPath(); context.moveTo(position[0], -position[1]); context.lineTo(endpoint[0], endpoint[1]); - context.lineTo(endpoint[0] + arrowheadLength * Math.cos(NORMAL_ARROWHEAD_ANGLE + normalAngle), - endpoint[1] + arrowheadLength * Math.sin(NORMAL_ARROWHEAD_ANGLE + normalAngle)); + context.lineTo(endpoint[0] + arrowheadLength * + (Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[0] + + Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[1]), + endpoint[1] + arrowheadLength * + (Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[0] - + Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[1])); context.stroke(); context.beginPath(); context.moveTo(endpoint[0], endpoint[1]); - context.lineTo(endpoint[0] + arrowheadLength * Math.cos(normalAngle - NORMAL_ARROWHEAD_ANGLE), - endpoint[1] + arrowheadLength * Math.sin(normalAngle - NORMAL_ARROWHEAD_ANGLE)); + context.lineTo(endpoint[0] + arrowheadLength * + (Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[0] - + Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[1]), + endpoint[1] - arrowheadLength * + (Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[1] + + Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[0])); context.stroke(); context.restore(); } diff --git a/demo/client/src/meshes.ts b/demo/client/src/meshes.ts index 29f65c64..b2ac9aa9 100644 --- a/demo/client/src/meshes.ts +++ b/demo/client/src/meshes.ts @@ -78,13 +78,6 @@ const B_QUAD_FIELD_COUNT: number = B_QUAD_SIZE / UINT32_SIZE; const INDEX_SIZE: number = 4; const B_QUAD_VERTEX_POSITION_SIZE: number = 12 * 4; const B_VERTEX_POSITION_SIZE: number = 4 * 2; -const EDGE_BOUNDING_BOX_VERTEX_POSITION_SIZE: number = 4 * 4; -const EDGE_UPPER_LINE_VERTEX_POSITION_SIZE: number = 4 * 4; -const EDGE_LOWER_LINE_VERTEX_POSITION_SIZE: number = 4 * 4; -const EDGE_UPPER_CURVE_VERTEX_POSITION_SIZE: number = 4 * 6; -const EDGE_LOWER_CURVE_VERTEX_POSITION_SIZE: number = 4 * 6; -const SEGMENT_LINE_SIZE: number = 4 * 4; -const SEGMENT_CURVE_SIZE: number = 4 * 6; const MESH_TYPES: Meshes = { bBoxPathIDs: { type: 'Uint16', size: 1 }, @@ -92,12 +85,9 @@ const MESH_TYPES: Meshes = { bQuadVertexInteriorIndices: { type: 'Uint32', size: 1 }, bQuadVertexPositionPathIDs: { type: 'Uint16', size: 6 }, bQuadVertexPositions: { type: 'Float32', size: 12 }, - segmentCurveNormals: { type: 'Float32', size: 3 }, - segmentCurvePathIDs: { type: 'Uint16', size: 1 }, - segmentCurves: { type: 'Float32', size: 6 }, - segmentLineNormals: { type: 'Float32', size: 2 }, - segmentLinePathIDs: { type: 'Uint16', size: 1 }, - segmentLines: { type: 'Float32', size: 4 }, + stencilNormals: { type: 'Float32', size: 6 }, + stencilSegmentPathIDs: { type: 'Uint16', size: 1 }, + stencilSegments: { type: 'Float32', size: 6 }, }; const BUFFER_TYPES: Meshes = { @@ -106,12 +96,9 @@ const BUFFER_TYPES: Meshes = { bQuadVertexInteriorIndices: 'ELEMENT_ARRAY_BUFFER', bQuadVertexPositionPathIDs: 'ARRAY_BUFFER', bQuadVertexPositions: 'ARRAY_BUFFER', - segmentCurveNormals: 'ARRAY_BUFFER', - segmentCurvePathIDs: 'ARRAY_BUFFER', - segmentCurves: 'ARRAY_BUFFER', - segmentLineNormals: 'ARRAY_BUFFER', - segmentLinePathIDs: 'ARRAY_BUFFER', - segmentLines: 'ARRAY_BUFFER', + stencilNormals: 'ARRAY_BUFFER', + stencilSegmentPathIDs: 'ARRAY_BUFFER', + stencilSegments: 'ARRAY_BUFFER', }; const EDGE_BUFFER_NAMES = ['UpperLine', 'UpperCurve', 'LowerLine', 'LowerCurve']; @@ -125,10 +112,8 @@ const BUFFER_TYPE_FOURCCS: BufferTypeFourCCTable = { bbox: 'bBoxes', bqii: 'bQuadVertexInteriorIndices', bqvp: 'bQuadVertexPositions', - scur: 'segmentCurves', - slin: 'segmentLines', - sncu: 'segmentCurveNormals', - snli: 'segmentLineNormals', + snor: 'stencilNormals', + sseg: 'stencilSegments', }; // Must match the FourCCs in @@ -137,31 +122,27 @@ const PATH_RANGE_TYPE_FOURCCS: PathRangeTypeFourCCTable = { bbox: 'bBoxPathRanges', bqii: 'bQuadVertexInteriorIndexPathRanges', bqvp: 'bQuadVertexPositionPathRanges', - scur: 'segmentCurveRanges', - slin: 'segmentLineRanges', + sseg: 'stencilSegmentPathRanges', }; const RANGE_TO_COUNT_TABLE: RangeToCountTable = { bBoxPathRanges: 'bBoxCount', bQuadVertexInteriorIndexPathRanges: 'bQuadVertexInteriorIndexCount', bQuadVertexPositionPathRanges: 'bQuadVertexPositionCount', - segmentCurveRanges: 'segmentCurveCount', - segmentLineRanges: 'segmentLineCount', + stencilSegmentPathRanges: 'stencilSegmentCount', }; const RANGE_TO_RANGE_BUFFER_TABLE: RangeToRangeBufferTable = { bBoxPathRanges: 'bBoxPathIDs', bQuadVertexPositionPathRanges: 'bQuadVertexPositionPathIDs', - segmentCurveRanges: 'segmentCurvePathIDs', - segmentLineRanges: 'segmentLinePathIDs', + stencilSegmentPathRanges: 'stencilSegmentPathIDs', }; const RANGE_KEYS: Array = [ 'bQuadVertexPositionPathRanges', 'bQuadVertexInteriorIndexPathRanges', 'bBoxPathRanges', - 'segmentCurveRanges', - 'segmentLineRanges', + 'stencilSegmentPathRanges', ]; type BufferType = 'ARRAY_BUFFER' | 'ELEMENT_ARRAY_BUFFER'; @@ -170,31 +151,26 @@ export interface Meshes { readonly bQuadVertexPositions: T; readonly bQuadVertexInteriorIndices: T; readonly bBoxes: T; - readonly segmentLines: T; - readonly segmentCurves: T; - readonly segmentLineNormals: T; - readonly segmentCurveNormals: T; + readonly stencilSegments: T; + readonly stencilNormals: T; bQuadVertexPositionPathIDs: T; bBoxPathIDs: T; - segmentLinePathIDs: T; - segmentCurvePathIDs: T; + stencilSegmentPathIDs: T; } interface MeshDataCounts { readonly bQuadVertexPositionCount: number; readonly bQuadVertexInteriorIndexCount: number; readonly bBoxCount: number; - readonly segmentLineCount: number; - readonly segmentCurveCount: number; + readonly stencilSegmentCount: number; } interface PathRanges { bQuadVertexPositionPathRanges: Range[]; bQuadVertexInteriorIndexPathRanges: Range[]; bBoxPathRanges: Range[]; - segmentCurveRanges: Range[]; - segmentLineRanges: Range[]; + stencilSegmentPathRanges: Range[]; } export class PathfinderMeshData implements Meshes, MeshDataCounts, PathRanges { @@ -203,27 +179,22 @@ export class PathfinderMeshData implements Meshes, MeshDataCounts, readonly bBoxes: ArrayBuffer; readonly bBoxSigns: ArrayBuffer; readonly bBoxIndices: ArrayBuffer; - readonly segmentLines: ArrayBuffer; - readonly segmentCurves: ArrayBuffer; - readonly segmentLineNormals: ArrayBuffer; - readonly segmentCurveNormals: ArrayBuffer; + readonly stencilSegments: ArrayBuffer; + readonly stencilNormals: ArrayBuffer; readonly bQuadVertexPositionCount: number; readonly bQuadVertexInteriorIndexCount: number; readonly bBoxCount: number; - readonly segmentLineCount: number; - readonly segmentCurveCount: number; + readonly stencilSegmentCount: number; bQuadVertexPositionPathIDs: ArrayBuffer; bBoxPathIDs: ArrayBuffer; - segmentCurvePathIDs: ArrayBuffer; - segmentLinePathIDs: ArrayBuffer; + stencilSegmentPathIDs: ArrayBuffer; bQuadVertexPositionPathRanges: Range[]; bQuadVertexInteriorIndexPathRanges: Range[]; bBoxPathRanges: Range[]; - segmentCurveRanges: Range[]; - segmentLineRanges: Range[]; + stencilSegmentPathRanges: Range[]; constructor(meshes: ArrayBuffer | Meshes, optionalRanges?: PathRanges) { if (meshes instanceof ArrayBuffer) { @@ -261,8 +232,7 @@ export class PathfinderMeshData implements Meshes, MeshDataCounts, this.bQuadVertexInteriorIndexCount = this.bQuadVertexInteriorIndices.byteLength / INDEX_SIZE; this.bBoxCount = this.bBoxes.byteLength / (FLOAT32_SIZE * 6); - this.segmentCurveCount = this.segmentCurves.byteLength / SEGMENT_CURVE_SIZE; - this.segmentLineCount = this.segmentLines.byteLength / SEGMENT_LINE_SIZE; + this.stencilSegmentCount = this.stencilSegments.byteLength / (FLOAT32_SIZE * 6); this.rebuildPathIDBuffers(); } @@ -339,16 +309,8 @@ export class PathfinderMeshData implements Meshes, MeshDataCounts, const lastBBoxIndex = bBoxVertexCopyResult.originalEndIndex; // Copy over segments. - copySegments(['segmentLines', 'segmentLineNormals'], - 'segmentLineRanges', - expandedArrays, - expandedRanges, - originalBuffers, - originalRanges, - expandedPathID, - originalPathID); - copySegments(['segmentCurves', 'segmentCurveNormals'], - 'segmentCurveRanges', + copySegments(['stencilSegments', 'stencilNormals'], + 'stencilSegmentPathRanges', expandedArrays, expandedRanges, originalBuffers, @@ -428,18 +390,14 @@ export class PathfinderMeshBuffers implements Meshes, PathRanges { readonly bBoxSigns: WebGLBuffer; readonly bBoxIndices: WebGLBuffer; readonly bBoxPathIDs: WebGLBuffer; - readonly segmentLines: WebGLBuffer; - readonly segmentCurves: WebGLBuffer; - readonly segmentLinePathIDs: WebGLBuffer; - readonly segmentCurvePathIDs: WebGLBuffer; - readonly segmentLineNormals: WebGLBuffer; - readonly segmentCurveNormals: WebGLBuffer; + readonly stencilSegments: WebGLBuffer; + readonly stencilSegmentPathIDs: WebGLBuffer; + readonly stencilNormals: WebGLBuffer; readonly bQuadVertexPositionPathRanges: Range[]; readonly bQuadVertexInteriorIndexPathRanges: Range[]; readonly bBoxPathRanges: Range[]; - readonly segmentCurveRanges: Range[]; - readonly segmentLineRanges: Range[]; + readonly stencilSegmentPathRanges: Range[]; constructor(gl: WebGLRenderingContext, meshData: PathfinderMeshData) { for (const bufferName of Object.keys(BUFFER_TYPES) as Array>) { diff --git a/demo/client/src/reference-test.ts b/demo/client/src/reference-test.ts index 5f2f567b..242af873 100644 --- a/demo/client/src/reference-test.ts +++ b/demo/client/src/reference-test.ts @@ -30,7 +30,7 @@ import {Hint} from "./text"; import {PathfinderFont, TextFrame, TextRun} from "./text"; import {unwrapNull} from "./utils"; import {DemoView} from "./view"; -import {AdaptiveMonochromeXCAAStrategy} from './xcaa-strategy'; +import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy'; const FONT: string = 'open-sans'; const TEXT_COLOR: number[] = [0, 0, 0, 255]; @@ -38,7 +38,7 @@ const TEXT_COLOR: number[] = [0, 0, 0, 255]; const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { none: NoAAStrategy, ssaa: SSAAStrategy, - xcaa: AdaptiveMonochromeXCAAStrategy, + xcaa: AdaptiveStencilMeshAAAStrategy, }; const RENDER_REFERENCE_URIS: PerTestType = { @@ -91,7 +91,7 @@ type ReferenceRenderer = 'core-graphics' | 'freetype'; interface AntialiasingStrategyTable { none: typeof NoAAStrategy; ssaa: typeof SSAAStrategy; - xcaa: typeof AdaptiveMonochromeXCAAStrategy; + xcaa: typeof AdaptiveStencilMeshAAAStrategy; } class ReferenceTestAppController extends DemoAppController { @@ -659,6 +659,9 @@ class ReferenceTestTextRenderer extends Renderer { renderContext: ReferenceTestView; camera: OrthographicCamera; + needsStencil: boolean = false; + isMulticolor: boolean = false; + get usesSTTransform(): boolean { return this.camera.usesSTTransform; } @@ -667,10 +670,6 @@ class ReferenceTestTextRenderer extends Renderer { return null; } - get isMulticolor(): boolean { - return false; - } - get bgColor(): glmatrix.vec4 { return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]); } diff --git a/demo/client/src/renderer.ts b/demo/client/src/renderer.ts index e480f8dd..e91a8f5d 100644 --- a/demo/client/src/renderer.ts +++ b/demo/client/src/renderer.ts @@ -71,6 +71,7 @@ export abstract class Renderer { } abstract get isMulticolor(): boolean; + abstract get needsStencil(): boolean; abstract get destFramebuffer(): WebGLFramebuffer | null; abstract get destAllocatedSize(): glmatrix.vec2; diff --git a/demo/client/src/shader-loader.ts b/demo/client/src/shader-loader.ts index cf94adea..ccbde856 100644 --- a/demo/client/src/shader-loader.ts +++ b/demo/client/src/shader-loader.ts @@ -21,11 +21,9 @@ export interface ShaderMap { directInterior: T; direct3DCurve: T; direct3DInterior: T; - ecaaLine: T; - ecaaCurve: T; - ecaaTransformedCurve: T; mcaa: T; ssaaSubpixelResolve: T; + stencilAAA: T; xcaaMonoResolve: T; xcaaMonoSubpixelResolve: T; } @@ -47,9 +45,7 @@ export const SHADER_NAMES: Array> = [ 'direct3DInterior', 'ssaaSubpixelResolve', 'mcaa', - 'ecaaLine', - 'ecaaCurve', - 'ecaaTransformedCurve', + 'stencilAAA', 'xcaaMonoResolve', 'xcaaMonoSubpixelResolve', 'demo3DDistantGlyph', @@ -93,18 +89,6 @@ const SHADER_URLS: ShaderMap = { fragment: "/glsl/gles2/direct-interior.fs.glsl", vertex: "/glsl/gles2/direct-interior.vs.glsl", }, - ecaaCurve: { - fragment: "/glsl/gles2/ecaa-curve.fs.glsl", - vertex: "/glsl/gles2/ecaa-curve.vs.glsl", - }, - ecaaLine: { - fragment: "/glsl/gles2/ecaa-line.fs.glsl", - vertex: "/glsl/gles2/ecaa-line.vs.glsl", - }, - ecaaTransformedCurve: { - fragment: "/glsl/gles2/ecaa-curve.fs.glsl", - vertex: "/glsl/gles2/ecaa-transformed-curve.vs.glsl", - }, mcaa: { fragment: "/glsl/gles2/mcaa.fs.glsl", vertex: "/glsl/gles2/mcaa.vs.glsl", @@ -113,6 +97,10 @@ const SHADER_URLS: ShaderMap = { fragment: "/glsl/gles2/ssaa-subpixel-resolve.fs.glsl", vertex: "/glsl/gles2/blit.vs.glsl", }, + stencilAAA: { + fragment: "/glsl/gles2/stencil-aaa.fs.glsl", + vertex: "/glsl/gles2/stencil-aaa.vs.glsl", + }, xcaaMonoResolve: { fragment: "/glsl/gles2/xcaa-mono-resolve.fs.glsl", vertex: "/glsl/gles2/xcaa-mono-resolve.vs.glsl", diff --git a/demo/client/src/svg-demo.ts b/demo/client/src/svg-demo.ts index 597c5203..91200c47 100644 --- a/demo/client/src/svg-demo.ts +++ b/demo/client/src/svg-demo.ts @@ -95,6 +95,8 @@ class SVGDemoView extends DemoView { class SVGDemoRenderer extends SVGRenderer { renderContext: SVGDemoView; + needsStencil: boolean = false; + protected get loader(): SVGLoader { return this.renderContext.appController.loader; } diff --git a/demo/client/src/svg-renderer.ts b/demo/client/src/svg-renderer.ts index 44b8ff1e..796cf710 100644 --- a/demo/client/src/svg-renderer.ts +++ b/demo/client/src/svg-renderer.ts @@ -46,6 +46,8 @@ export abstract class SVGRenderer extends Renderer { camera: OrthographicCamera; + needsStencil: boolean = false; + private options: SVGRendererOptions; get isMulticolor(): boolean { diff --git a/demo/client/src/text-demo.ts b/demo/client/src/text-demo.ts index a7184913..8ed49f70 100644 --- a/demo/client/src/text-demo.ts +++ b/demo/client/src/text-demo.ts @@ -33,7 +33,6 @@ import {TextRenderContext, TextRenderer} from './text-renderer'; import {assert, expectNotNull, panic, PathfinderError, scaleRect, UINT32_SIZE} from './utils'; import {unwrapNull} from './utils'; import {DemoView, RenderContext, Timings, TIMINGS} from './view'; -import {AdaptiveMonochromeXCAAStrategy} from './xcaa-strategy'; const DEFAULT_TEXT: string = `’Twas brillig, and the slithy toves diff --git a/demo/client/src/text-renderer.ts b/demo/client/src/text-renderer.ts index 849d1461..de9602e4 100644 --- a/demo/client/src/text-renderer.ts +++ b/demo/client/src/text-renderer.ts @@ -1,6 +1,6 @@ // pathfinder/client/src/text-renderer.ts // -// Copyright © 2017 The Pathfinder Project Developers. +// Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -21,15 +21,16 @@ import {PathTransformBuffers, Renderer} from './renderer'; import {ShaderMap} from './shader-loader'; import SSAAStrategy from './ssaa-strategy'; import {calculatePixelRectForGlyph, computeStemDarkeningAmount, GlyphStore, Hint} from "./text"; -import {PathfinderFont, SimpleTextLayout, UnitMetrics} from "./text"; +import {MAX_STEM_DARKENING_PIXELS_PER_EM, PathfinderFont, SimpleTextLayout} from "./text"; +import {UnitMetrics} from "./text"; import {unwrapNull} from './utils'; import {RenderContext, Timings} from "./view"; -import {AdaptiveMonochromeXCAAStrategy} from './xcaa-strategy'; +import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy'; interface AntialiasingStrategyTable { none: typeof NoAAStrategy; ssaa: typeof SSAAStrategy; - xcaa: typeof AdaptiveMonochromeXCAAStrategy; + xcaa: typeof AdaptiveStencilMeshAAAStrategy; } const SQRT_1_2: number = 1.0 / Math.sqrt(2.0); @@ -40,7 +41,7 @@ const MAX_SCALE: number = 0.5; const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { none: NoAAStrategy, ssaa: SSAAStrategy, - xcaa: AdaptiveMonochromeXCAAStrategy, + xcaa: AdaptiveStencilMeshAAAStrategy, }; export interface TextRenderContext extends RenderContext { @@ -68,6 +69,10 @@ export abstract class TextRenderer extends Renderer { return false; } + get needsStencil(): boolean { + return this.renderContext.fontSize <= MAX_STEM_DARKENING_PIXELS_PER_EM; + } + get usesSTTransform(): boolean { return this.camera.usesSTTransform; } diff --git a/demo/client/src/text.ts b/demo/client/src/text.ts index 66ff6a30..2e3517d4 100644 --- a/demo/client/src/text.ts +++ b/demo/client/src/text.ts @@ -34,7 +34,7 @@ const STEM_DARKENING_FACTORS: glmatrix.vec2 = glmatrix.vec2.clone([ const MAX_STEM_DARKENING_AMOUNT: glmatrix.vec2 = glmatrix.vec2.clone([0.3 * SQRT_2, 0.3 * SQRT_2]); // This value is a subjective cutoff. Above this ppem value, no stem darkening is performed. -const MAX_STEM_DARKENING_PIXELS_PER_EM: number = 72.0; +export const MAX_STEM_DARKENING_PIXELS_PER_EM: number = 72.0; const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font"; diff --git a/demo/client/src/xcaa-strategy.ts b/demo/client/src/xcaa-strategy.ts index ee9aa29d..d123e85d 100644 --- a/demo/client/src/xcaa-strategy.ts +++ b/demo/client/src/xcaa-strategy.ts @@ -44,8 +44,6 @@ const PATCH_VERTICES: Float32Array = new Float32Array([ const MCAA_PATCH_INDICES: Uint8Array = new Uint8Array([0, 1, 2, 1, 3, 2]); -const ECAA_CURVE_PATCH_INDICES: Uint8Array = new Uint8Array([0, 1, 2, 0, 2, 3, 2, 5, 3]); - export type TransformType = 'dilation' | 'affine' | '3d'; export abstract class XCAAStrategy extends AntialiasingStrategy { @@ -175,7 +173,7 @@ export abstract class XCAAStrategy extends AntialiasingStrategy { if (resolveProgram == null) return; - // Set state for ECAA resolve. + // Set state for XCAA resolve. const usedSize = renderer.destUsedSize; gl.scissor(0, 0, usedSize[0], usedSize[1]); gl.enable(gl.SCISSOR_TEST); @@ -618,80 +616,53 @@ export class MCAAStrategy extends XCAAStrategy { } } -export class ECAAStrategy extends XCAAStrategy { - protected get patchIndices(): Uint8Array { - return ECAA_CURVE_PATCH_INDICES; - } +export class StencilAAAStrategy extends XCAAStrategy { + directRenderingMode: DirectRenderingMode = 'none'; - protected get lineShaderProgramNames(): Array> { - return ['ecaaLine']; - } + protected transformType: TransformType = 'affine'; + protected patchIndices: Uint8Array = MCAA_PATCH_INDICES; + protected mightUseAAFramebuffer: boolean = true; - protected get curveShaderProgramNames(): Array> { - return ['ecaaCurve', 'ecaaTransformedCurve']; - } - - protected get mightUseAAFramebuffer(): boolean { - return true; - } - - private lineVAOs: Partial>; - private curveVAOs: Partial>; - - protected get transformType(): TransformType { - return '3d'; - } - - get directRenderingMode(): DirectRenderingMode { - return 'none'; - } + private vao: WebGLVertexArrayObject; attachMeshes(renderer: Renderer): void { super.attachMeshes(renderer); - - this.createLineVAOs(renderer); - this.createCurveVAOs(renderer); + this.createVAO(renderer); } antialiasObject(renderer: Renderer, objectIndex: number): void { super.antialiasObject(renderer, objectIndex); + const renderContext = renderer.renderContext; + const gl = renderContext.gl; + + if (renderer.meshData == null) + return; + // Antialias. const shaderPrograms = renderer.renderContext.shaderPrograms; this.setAAState(renderer); this.setBlendModeForAA(renderer); - this.antialiasLinesOfObjectWithProgram(renderer, - objectIndex, - this.lineShaderProgramNames[0]); - this.antialiasCurvesOfObjectWithPrograms(renderer, - objectIndex, - this.curveShaderProgramNames[0], - this.curveShaderProgramNames[1]); - const renderContext = renderer.renderContext; - const gl = renderContext.gl; + const program = renderContext.shaderPrograms.stencilAAA; + gl.useProgram(program.program); + const uniforms = program.uniforms; + this.setAAUniforms(renderer, uniforms, objectIndex); - gl.disable(gl.CULL_FACE); + renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.vao); + + // FIXME(pcwalton): Only render the appropriate instances. + const count = renderer.meshData[0].stencilSegmentCount; + renderContext.instancedArraysExt + .drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count); + + renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); } - protected usesAAFramebuffer(): boolean { + protected usesAAFramebuffer(renderer: Renderer): boolean { return true; } - protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): - void { - super.setAAUniforms(renderer, uniforms, objectIndex); - renderer.setEmboldenAmountUniform(objectIndex, uniforms); - } - - protected getResolveProgram(renderer: Renderer): PathfinderShaderProgram { - const renderContext = renderer.renderContext; - - if (this.subpixelAA !== 'none') - return renderContext.shaderPrograms.xcaaMonoSubpixelResolve; - return renderContext.shaderPrograms.xcaaMonoResolve; - } - protected clearForAA(renderer: Renderer): void { const renderContext = renderer.renderContext; const gl = renderContext.gl; @@ -701,15 +672,26 @@ export class ECAAStrategy extends XCAAStrategy { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); } + protected getResolveProgram(renderer: Renderer): PathfinderShaderProgram | null { + const renderContext = renderer.renderContext; + + if (this.subpixelAA !== 'none') + return renderContext.shaderPrograms.xcaaMonoSubpixelResolve; + return renderContext.shaderPrograms.xcaaMonoResolve; + } + protected setAADepthState(renderer: Renderer): void { const renderContext = renderer.renderContext; const gl = renderContext.gl; gl.disable(gl.DEPTH_TEST); + gl.disable(gl.CULL_FACE); + } - gl.frontFace(gl.CCW); - gl.cullFace(gl.BACK); - gl.enable(gl.CULL_FACE); + protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): + void { + super.setAAUniforms(renderer, uniforms, objectIndex); + renderer.setEmboldenAmountUniform(objectIndex, uniforms); } protected clearForResolve(renderer: Renderer): void { @@ -720,76 +702,77 @@ export class ECAAStrategy extends XCAAStrategy { gl.clear(gl.COLOR_BUFFER_BIT); } - protected antialiasLinesOfObjectWithProgram(renderer: Renderer, - objectIndex: number, - programName: keyof ShaderMap): - void { - if (renderer.meshData == null) + private createVAO(renderer: Renderer): void { + if (renderer.meshes == null || renderer.meshData == null) return; const renderContext = renderer.renderContext; const gl = renderContext.gl; - const pathRange = renderer.pathRangeForObject(objectIndex); - const meshIndex = renderer.meshIndexForObject(objectIndex); + const program = renderContext.shaderPrograms.stencilAAA; + const attributes = program.attributes; - const lineProgram = renderContext.shaderPrograms[programName]; - gl.useProgram(lineProgram.program); - const uniforms = lineProgram.uniforms; - this.setAAUniforms(renderer, uniforms, objectIndex); + this.vao = renderContext.vertexArrayObjectExt.createVertexArrayOES(); + renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.vao); - const vao = this.lineVAOs[programName]; - renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao); + const vertexPositionsBuffer = renderer.meshes[0].stencilSegments; + const vertexNormalsBuffer = renderer.meshes[0].stencilNormals; + const pathIDsBuffer = renderer.meshes[0].stencilSegmentPathIDs; - // FIXME(pcwalton): Only render the appropriate instances. - const count = renderer.meshData[meshIndex].segmentLineCount; - renderContext.instancedArraysExt - .drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count); + gl.useProgram(program.program); + gl.bindBuffer(gl.ARRAY_BUFFER, renderContext.quadPositionsBuffer); + gl.vertexAttribPointer(attributes.aTessCoord, 2, gl.FLOAT, false, 0, 0); + gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionsBuffer); + gl.vertexAttribPointer(attributes.aFromPosition, 2, gl.FLOAT, false, FLOAT32_SIZE * 6, 0); + gl.vertexAttribPointer(attributes.aCtrlPosition, + 2, + gl.FLOAT, + false, + FLOAT32_SIZE * 6, + FLOAT32_SIZE * 2); + gl.vertexAttribPointer(attributes.aToPosition, + 2, + gl.FLOAT, + false, + FLOAT32_SIZE * 6, + FLOAT32_SIZE * 4); + gl.bindBuffer(gl.ARRAY_BUFFER, vertexNormalsBuffer); + gl.vertexAttribPointer(attributes.aFromNormal, 2, gl.FLOAT, false, FLOAT32_SIZE * 6, 0); + gl.vertexAttribPointer(attributes.aCtrlNormal, + 2, + gl.FLOAT, + false, + FLOAT32_SIZE * 6, + FLOAT32_SIZE * 2); + gl.vertexAttribPointer(attributes.aToNormal, + 2, + gl.FLOAT, + false, + FLOAT32_SIZE * 6, + FLOAT32_SIZE * 4); + gl.bindBuffer(gl.ARRAY_BUFFER, pathIDsBuffer); + gl.vertexAttribPointer(attributes.aPathID, 1, gl.UNSIGNED_SHORT, false, 0, 0); - renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); - } + gl.enableVertexAttribArray(attributes.aTessCoord); + gl.enableVertexAttribArray(attributes.aFromPosition); + gl.enableVertexAttribArray(attributes.aCtrlPosition); + gl.enableVertexAttribArray(attributes.aToPosition); + gl.enableVertexAttribArray(attributes.aFromNormal); + gl.enableVertexAttribArray(attributes.aCtrlNormal); + gl.enableVertexAttribArray(attributes.aToNormal); + gl.enableVertexAttribArray(attributes.aPathID); - protected antialiasCurvesOfObjectWithPrograms(renderer: Renderer, - objectIndex: number, - stProgram: keyof ShaderMap, - transformedProgram: keyof ShaderMap): - void { - if (renderer.usesSTTransform) { - this.antialiasCurvesOfObjectWithProgram(renderer, objectIndex, stProgram, 0); - return; - } + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aFromPosition, 1); + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aCtrlPosition, 1); + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aToPosition, 1); + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aFromNormal, 1); + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aCtrlNormal, 1); + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aToNormal, 1); + renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1); - this.antialiasCurvesOfObjectWithProgram(renderer, objectIndex, transformedProgram, 0); - this.antialiasCurvesOfObjectWithProgram(renderer, objectIndex, transformedProgram, 1); - } + // TODO(pcwalton): Normals. - private antialiasCurvesOfObjectWithProgram(renderer: Renderer, - objectIndex: number, - programName: keyof ShaderMap, - passIndex: number): - void { - if (renderer.meshData == null) - return; - - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - const pathRange = renderer.pathRangeForObject(objectIndex); - const meshIndex = renderer.meshIndexForObject(objectIndex); - - const curveProgram = renderContext.shaderPrograms[programName]; - 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); - - // FIXME(pcwalton): Only render the appropriate instances. - const count = renderer.meshData[meshIndex].segmentCurveCount; - renderContext.instancedArraysExt - .drawElementsInstancedANGLE(gl.TRIANGLES, 9, gl.UNSIGNED_BYTE, 0, count); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer); renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); } @@ -802,166 +785,15 @@ export class ECAAStrategy extends XCAAStrategy { 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.aTessCoord, 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.aTessCoord); - 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.aNormalAngles, - 2, - gl.FLOAT, - false, - FLOAT32_SIZE * 2, - 0); - gl.enableVertexAttribArray(attributes.aNormalAngles); - - renderContext.instancedArraysExt - .vertexAttribDivisorANGLE(attributes.aNormalAngles, 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, this.patchVertexBuffer); - gl.vertexAttribPointer(attributes.aTessCoord, 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.aTessCoord); - 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, this.patchIndexBuffer); - - renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); - - this.curveVAOs[programName] = vao; - } - } } -/// Switches between the mesh-based MCAA and ECAA depending on whether stem darkening is enabled. +/// Switches between mesh-based and stencil-based analytic antialiasing depending on whether stem +/// darkening is enabled. /// /// FIXME(pcwalton): Share textures and FBOs between the two strategies. -export class AdaptiveMonochromeXCAAStrategy implements AntialiasingStrategy { - private mcaaStrategy: MCAAStrategy; - private ecaaStrategy: ECAAStrategy; +export class AdaptiveStencilMeshAAAStrategy implements AntialiasingStrategy { + private meshStrategy: MCAAStrategy; + private stencilStrategy: StencilAAAStrategy; get directRenderingMode(): DirectRenderingMode { return 'none'; @@ -972,27 +804,27 @@ export class AdaptiveMonochromeXCAAStrategy implements AntialiasingStrategy { } constructor(level: number, subpixelAA: SubpixelAAType) { - this.mcaaStrategy = new MCAAStrategy(level, subpixelAA); - this.ecaaStrategy = new ECAAStrategy(level, subpixelAA); + this.meshStrategy = new MCAAStrategy(level, subpixelAA); + this.stencilStrategy = new StencilAAAStrategy(level, subpixelAA); } init(renderer: Renderer): void { - this.mcaaStrategy.init(renderer); - this.ecaaStrategy.init(renderer); + this.meshStrategy.init(renderer); + this.stencilStrategy.init(renderer); } attachMeshes(renderer: Renderer): void { - this.mcaaStrategy.attachMeshes(renderer); - this.ecaaStrategy.attachMeshes(renderer); + this.meshStrategy.attachMeshes(renderer); + this.stencilStrategy.attachMeshes(renderer); } setFramebufferSize(renderer: Renderer): void { - this.mcaaStrategy.setFramebufferSize(renderer); - this.ecaaStrategy.setFramebufferSize(renderer); + this.meshStrategy.setFramebufferSize(renderer); + this.stencilStrategy.setFramebufferSize(renderer); } get transform(): glmatrix.mat4 { - return this.mcaaStrategy.transform; + return this.meshStrategy.transform; } prepareForRendering(renderer: Renderer): void { @@ -1032,12 +864,7 @@ export class AdaptiveMonochromeXCAAStrategy implements AntialiasingStrategy { } private getAppropriateStrategy(renderer: Renderer): AntialiasingStrategy { - if (glmatrix.vec2.equals(renderer.emboldenAmount, [0.0, 0.0]) && - renderer.usesSTTransform) { - return this.mcaaStrategy; - } - - return this.ecaaStrategy; + return renderer.needsStencil ? this.stencilStrategy : this.meshStrategy; } } diff --git a/demo/server/src/main.rs b/demo/server/src/main.rs index d44fb6f2..ea22b679 100644 --- a/demo/server/src/main.rs +++ b/demo/server/src/main.rs @@ -1,6 +1,6 @@ // pathfinder/demo/server/main.rs // -// Copyright © 2017 The Pathfinder Project Developers. +// Copyright © 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -474,10 +474,10 @@ fn partition_font(request: Json) -> Result or the MIT license @@ -111,13 +111,6 @@ pub struct BQuadVertexPositions { pub lower_left_vertex_position: Point2D, } -#[derive(Clone, Copy, PartialEq, Debug)] -#[repr(u8)] -pub enum AntialiasingMode { - Msaa = 0, - Ecaa = 1, -} - #[derive(Clone, Copy, PartialEq, Debug)] #[repr(u8)] pub(crate) enum BVertexKind { diff --git a/partitioner/src/mesh_library.rs b/partitioner/src/mesh_library.rs index b65514f6..7e3f3192 100644 --- a/partitioner/src/mesh_library.rs +++ b/partitioner/src/mesh_library.rs @@ -13,7 +13,6 @@ use byteorder::{LittleEndian, WriteBytesExt}; use euclid::approxeq::ApproxEq; use euclid::{Point2D, Rect, Size2D, Vector2D}; use lyon_path::PathEvent; -use lyon_path::iterator::PathIterator; use pathfinder_path_utils::normals::PathNormals; use pathfinder_path_utils::segments::{self, SegmentIter}; use serde::Serialize; @@ -34,8 +33,8 @@ pub struct MeshLibrary { pub b_vertex_positions: Vec>, pub b_vertex_loop_blinn_data: Vec, pub b_boxes: Vec, - pub segments: MeshLibrarySegments, - pub segment_normals: MeshLibrarySegmentNormals, + pub stencil_segments: Vec, + pub stencil_normals: Vec, } impl MeshLibrary { @@ -49,8 +48,8 @@ impl MeshLibrary { b_vertex_positions: vec![], b_vertex_loop_blinn_data: vec![], b_boxes: vec![], - segments: MeshLibrarySegments::new(), - segment_normals: MeshLibrarySegmentNormals::new(), + stencil_segments: vec![], + stencil_normals: vec![], } } @@ -62,8 +61,8 @@ impl MeshLibrary { self.b_vertex_positions.clear(); self.b_vertex_loop_blinn_data.clear(); self.b_boxes.clear(); - self.segments.clear(); - self.segment_normals.clear(); + self.stencil_segments.clear(); + self.stencil_normals.clear(); } pub(crate) fn ensure_path_ranges(&mut self, path_id: u16) -> &mut PathRanges { @@ -256,93 +255,53 @@ impl MeshLibrary { } } - pub fn push_segments(&mut self, path_id: u16, stream: I) - where I: Iterator { - let first_line_index = self.segments.lines.len() as u32; - let first_curve_index = self.segments.curves.len() as u32; + pub fn push_stencil_segments(&mut self, path_id: u16, stream: I) + where I: Iterator { + let first_segment_index = self.stencil_segments.len() as u32; let segment_iter = SegmentIter::new(stream); for segment in segment_iter { match segment { segments::Segment::Line(line_segment) => { - self.segments.lines.push(LineSegment { - endpoint_0: line_segment.from, - endpoint_1: line_segment.to, + self.stencil_segments.push(StencilSegment { + from: line_segment.from, + ctrl: line_segment.from.lerp(line_segment.to, 0.5), + to: line_segment.to, }) } - segments::Segment::Quadratic(curve_segment) => { - self.segments.curves.push(CurveSegment { - endpoint_0: curve_segment.from, - control_point: curve_segment.ctrl, - endpoint_1: curve_segment.to, + segments::Segment::Quadratic(quadratic_segment) => { + self.stencil_segments.push(StencilSegment { + from: quadratic_segment.from, + ctrl: quadratic_segment.ctrl, + to: quadratic_segment.to, }) } + segments::Segment::Cubic(..) => { + panic!("push_stencil_segments(): Convert cubics to quadratics first!") + } segments::Segment::EndSubpath(..) => {} - segments::Segment::Cubic(..) => { - panic!("push_segments(): Convert cubics to quadratics first!") - } } } - let last_line_index = self.segments.lines.len() as u32; - let last_curve_index = self.segments.curves.len() as u32; + let last_segment_index = self.stencil_segments.len() as u32; let path_ranges = self.ensure_path_ranges(path_id); - path_ranges.segment_curves = first_curve_index..last_curve_index; - path_ranges.segment_lines = first_line_index..last_line_index; + path_ranges.stencil_segments = first_segment_index..last_segment_index; } - /// Computes vertex normals necessary for emboldening and/or stem darkening. - pub fn push_normals(&mut self, _path_id: u16, stream: I) where I: PathIterator { - let path_events: Vec<_> = stream.collect(); - + /// Computes vertex normals necessary for emboldening and/or stem darkening. This is intended + /// for stencil-and-cover. + pub fn push_stencil_normals(&mut self, _path_id: u16, stream: I) + where I: Iterator + Clone { let mut normals = PathNormals::new(); - normals.add_path(path_events.iter().cloned()); - let normals = normals.normals(); - - let mut current_point_normal_index = 0; - let mut next_normal_index = 0; - let mut first_normal_index_of_subpath = 0; - - for event in path_events { - match event { - PathEvent::MoveTo(..) => { - first_normal_index_of_subpath = next_normal_index; - current_point_normal_index = next_normal_index; - next_normal_index += 1; - } - PathEvent::LineTo(..) => { - self.segment_normals.line_normals.push(LineSegmentNormals { - endpoint_0: normal_angle(&normals[current_point_normal_index]), - endpoint_1: normal_angle(&normals[next_normal_index]), - }); - current_point_normal_index = next_normal_index; - next_normal_index += 1; - } - PathEvent::QuadraticTo(..) => { - self.segment_normals.curve_normals.push(CurveSegmentNormals { - endpoint_0: normal_angle(&normals[current_point_normal_index]), - control_point: normal_angle(&normals[next_normal_index + 0]), - endpoint_1: normal_angle(&normals[next_normal_index + 1]), - }); - current_point_normal_index = next_normal_index + 1; - next_normal_index += 2; - } - PathEvent::Close => { - self.segment_normals.line_normals.push(LineSegmentNormals { - endpoint_0: normal_angle(&normals[current_point_normal_index]), - endpoint_1: normal_angle(&normals[first_normal_index_of_subpath]), - }); - } - PathEvent::CubicTo(..) | PathEvent::Arc(..) => { - panic!("push_normals(): Convert cubics and arcs to quadratics first!") - } + normals.add_path(stream); + self.stencil_normals.extend(normals.normals().iter().map(|normals| { + StencilNormals { + from: normals.from, + ctrl: normals.ctrl, + to: normals.to, } - } - - fn normal_angle(vector: &Vector2D) -> f32 { - Vector2D::new(vector.x, -vector.y).angle_from_x_axis().get() - } + })) } /// Writes this mesh library to a RIFF file. @@ -363,10 +322,8 @@ impl MeshLibrary { try!(write_simple_chunk(writer, b"bqvp", &self.b_quad_vertex_positions)); try!(write_simple_chunk(writer, b"bqii", &self.b_quad_vertex_interior_indices)); try!(write_simple_chunk(writer, b"bbox", &self.b_boxes)); - try!(write_simple_chunk(writer, b"slin", &self.segments.lines)); - try!(write_simple_chunk(writer, b"scur", &self.segments.curves)); - try!(write_simple_chunk(writer, b"snli", &self.segment_normals.line_normals)); - try!(write_simple_chunk(writer, b"sncu", &self.segment_normals.curve_normals)); + try!(write_simple_chunk(writer, b"sseg", &self.stencil_segments)); + try!(write_simple_chunk(writer, b"snor", &self.stencil_normals)); let total_length = try!(writer.seek(SeekFrom::Current(0))); try!(writer.seek(SeekFrom::Start(4))); @@ -413,8 +370,10 @@ impl MeshLibrary { |ranges| &ranges.b_quad_vertex_interior_indices)); try!(write_path_range(writer, b"bver", path_ranges, |ranges| &ranges.b_vertices)); try!(write_path_range(writer, b"bbox", path_ranges, |ranges| &ranges.b_boxes)); - try!(write_path_range(writer, b"slin", path_ranges, |ranges| &ranges.segment_lines)); - try!(write_path_range(writer, b"scur", path_ranges, |ranges| &ranges.segment_curves)); + try!(write_path_range(writer, + b"sseg", + path_ranges, + |ranges| &ranges.stencil_segments)); Ok(()) } @@ -469,6 +428,7 @@ pub struct PathRanges { pub b_boxes: Range, pub segment_lines: Range, pub segment_curves: Range, + pub stencil_segments: Range, } impl PathRanges { @@ -481,6 +441,7 @@ impl PathRanges { b_boxes: 0..0, segment_lines: 0..0, segment_curves: 0..0, + stencil_segments: 0..0, } } @@ -496,72 +457,6 @@ impl PathRanges { } } -#[derive(Clone, Debug)] -pub struct MeshLibrarySegments { - pub lines: Vec, - pub curves: Vec, -} - -impl MeshLibrarySegments { - fn new() -> MeshLibrarySegments { - MeshLibrarySegments { - lines: vec![], - curves: vec![], - } - } - - fn clear(&mut self) { - self.lines.clear(); - self.curves.clear(); - } -} - -#[derive(Clone, Debug)] -pub struct MeshLibrarySegmentNormals { - pub line_normals: Vec, - pub curve_normals: Vec, -} - -impl MeshLibrarySegmentNormals { - fn new() -> MeshLibrarySegmentNormals { - MeshLibrarySegmentNormals { - line_normals: vec![], - curve_normals: vec![], - } - } - - fn clear(&mut self) { - self.line_normals.clear(); - self.curve_normals.clear(); - } -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -pub struct LineSegment { - pub endpoint_0: Point2D, - pub endpoint_1: Point2D, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -pub struct CurveSegment { - pub endpoint_0: Point2D, - pub control_point: Point2D, - pub endpoint_1: Point2D, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -pub struct LineSegmentNormals { - pub endpoint_0: f32, - pub endpoint_1: f32, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -pub struct CurveSegmentNormals { - pub endpoint_0: f32, - pub control_point: f32, - pub endpoint_1: f32, -} - #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct BBox { pub upper_left_position: Point2D, @@ -578,12 +473,18 @@ pub struct BBox { pub lower_mode: f32, } -#[derive(Clone, Copy, Debug)] -struct CornerPositions { - upper_left: Point2D, - upper_right: Point2D, - lower_left: Point2D, - lower_right: Point2D, +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct StencilSegment { + pub from: Point2D, + pub ctrl: Point2D, + pub to: Point2D, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct StencilNormals { + pub from: Vector2D, + pub ctrl: Vector2D, + pub to: Vector2D, } #[derive(Clone, Copy, Debug)] diff --git a/path-utils/src/normals.rs b/path-utils/src/normals.rs index 75a37de7..15449111 100644 --- a/path-utils/src/normals.rs +++ b/path-utils/src/normals.rs @@ -8,13 +8,19 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use euclid::Vector2D; -use euclid::approxeq::ApproxEq; +use euclid::{Point2D, Vector2D}; use lyon_path::PathEvent; +#[derive(Clone, Copy, Debug)] +pub struct SegmentNormals { + pub from: Vector2D, + pub ctrl: Vector2D, + pub to: Vector2D, +} + #[derive(Clone)] pub struct PathNormals { - normals: Vec>, + normals: Vec, } impl PathNormals { @@ -26,7 +32,7 @@ impl PathNormals { } #[inline] - pub fn normals(&self) -> &[Vector2D] { + pub fn normals(&self) -> &[SegmentNormals] { &self.normals } @@ -34,77 +40,96 @@ impl PathNormals { self.normals.clear() } - pub fn add_path(&mut self, path: I) where I: Iterator { - let mut path = path.peekable(); - while path.peek().is_some() { - let mut positions = vec![]; - loop { - match path.next() { - Some(PathEvent::MoveTo(to)) | Some(PathEvent::LineTo(to)) => { - positions.push(to); - } - Some(PathEvent::QuadraticTo(ctrl, to)) => { - positions.push(ctrl); - positions.push(to); - } - Some(PathEvent::CubicTo(ctrl1, ctrl2, to)) => { - positions.push(ctrl1); - positions.push(ctrl2); - positions.push(to); - } - Some(PathEvent::Arc(..)) => panic!("PathNormals: Arcs currently unsupported!"), - None | Some(PathEvent::Close) => break, + pub fn add_path(&mut self, mut stream: I) where I: Iterator + Clone { + let (mut path_stream, mut path_points) = (stream.clone(), vec![]); + while let Some(event) = stream.next() { + match event { + PathEvent::MoveTo(to) => path_points.push(to), + PathEvent::LineTo(to) => path_points.push(to), + PathEvent::QuadraticTo(ctrl, to) => path_points.extend_from_slice(&[ctrl, to]), + PathEvent::CubicTo(..) => { + panic!("PathNormals::add_path(): Convert cubics to quadratics first!") } - - if let Some(&PathEvent::MoveTo(..)) = path.peek() { - break + PathEvent::Arc(..) => { + panic!("PathNormals::add_path(): Convert arcs to quadratics first!") + } + PathEvent::Close => { + self.flush(path_stream, &mut path_points); + path_stream = stream.clone(); } } + } - self.normals.reserve(positions.len()); + self.flush(path_stream, &mut path_points); + } - for (this_index, this_position) in positions.iter().enumerate() { - let mut prev_index = this_index; - let mut prev_vector; - loop { - if prev_index > 0 { - prev_index -= 1 - } else { - prev_index = positions.len() - 1 - } - prev_vector = *this_position - positions[prev_index]; - if !prev_vector.square_length().approx_eq(&0.0) { - break - } + fn flush(&mut self, path_stream: I, path_points: &mut Vec>) + where I: Iterator + Clone { + match path_points.len() { + 0 | 1 => path_points.clear(), + 2 => { + self.normals.push(SegmentNormals { + from: path_points[1] - path_points[0], + ctrl: Vector2D::zero(), + to: path_points[0] - path_points[1], + }); + path_points.clear(); + } + _ => self.flush_slow(path_stream, path_points), + } + } + + fn flush_slow(&mut self, path_stream: I, path_points: &mut Vec>) + where I: Iterator + Clone { + let mut normals = vec![Vector2D::zero(); path_points.len()]; + *normals.last_mut().unwrap() = compute_normal(&path_points[path_points.len() - 2], + &path_points[path_points.len() - 1], + &path_points[0]); + normals[0] = compute_normal(&path_points[path_points.len() - 1], + &path_points[0], + &path_points[1]); + for (index, window) in path_points.windows(3).enumerate() { + normals[index + 1] = compute_normal(&window[0], &window[1], &window[2]); + } + + path_points.clear(); + + let mut next_normal_index = 0; + for event in path_stream { + match event { + PathEvent::MoveTo(_) => next_normal_index += 1, + PathEvent::LineTo(_) => { + next_normal_index += 1; + self.normals.push(SegmentNormals { + from: normals[next_normal_index - 2], + ctrl: Vector2D::zero(), + to: normals[next_normal_index - 1], + }); } - - let mut next_index = this_index; - let mut next_vector; - loop { - if next_index + 1 < positions.len() { - next_index += 1 - } else { - next_index = 0 - } - next_vector = positions[next_index] - *this_position; - if !next_vector.square_length().approx_eq(&0.0) { - break - } + PathEvent::QuadraticTo(..) => { + next_normal_index += 2; + self.normals.push(SegmentNormals { + from: normals[next_normal_index - 3], + ctrl: normals[next_normal_index - 2], + to: normals[next_normal_index - 1], + }) } - - let prev_normal = rotate(&prev_vector).normalize(); - let next_normal = rotate(&next_vector).normalize(); - let mut bisector = (prev_normal + next_normal) * 0.5; - if bisector.square_length().approx_eq(&0.0) { - bisector = Vector2D::new(next_vector.y, next_vector.x) + PathEvent::CubicTo(..) | PathEvent::Arc(..) => unreachable!(), + PathEvent::Close => { + self.normals.push(SegmentNormals { + from: normals[next_normal_index - 1], + ctrl: Vector2D::zero(), + to: normals[0], + }); + break; } - - self.normals.push(bisector.normalize()); } } } } -fn rotate(vector: &Vector2D) -> Vector2D { - Vector2D::new(-vector.y, vector.x) +fn compute_normal(prev: &Point2D, current: &Point2D, next: &Point2D) + -> Vector2D { + let vector = ((*current - *prev) + (*next - *current)).normalize(); + Vector2D::new(vector.y, -vector.x) } diff --git a/path-utils/src/segments.rs b/path-utils/src/segments.rs index 8c5ba541..c43a43c6 100644 --- a/path-utils/src/segments.rs +++ b/path-utils/src/segments.rs @@ -48,12 +48,9 @@ impl Iterator for SegmentIter where I: Iterator { Some(PathEvent::Close) => { self.was_just_closed = true; let state = self.inner.get_state(); - /*if state.first == current_point { - return Some(Segment::EndSubpath(true)) - }*/ self.stack.push(Segment::EndSubpath(true)); Some(Segment::Line(LineSegment { - from: state.current, + from: current_point, to: state.first, })) } diff --git a/shaders/gles2/common.inc.glsl b/shaders/gles2/common.inc.glsl index 116e2cdd..9277f292 100644 --- a/shaders/gles2/common.inc.glsl +++ b/shaders/gles2/common.inc.glsl @@ -38,6 +38,14 @@ int imod(int ia, int ib) { return int(floor(m + 0.5)); } +float fastSign(float x) { + return x > 0.0 ? 1.0 : -1.0; +} + +float det2(mat2 m) { + return m[0][0] * m[1][1] - m[0][1] * m[1][0]; +} + /// Returns the *2D* result of transforming the given 2D point with the given 4D transformation /// matrix. /// @@ -58,6 +66,11 @@ vec2 transformVertexPositionAffine(vec2 position, vec4 transformST, vec2 transfo return position * transformST.xy + position.yx * transformExt + transformST.zw; } +vec2 transformVertexPositionInverseLinear(vec2 position, mat2 transform) { + position = vec2(det2(mat2(position, transform[1])), det2(mat2(transform[0], position))); + return position / det2(transform); +} + /// Interpolates the given 2D position in the vertical direction using the given ultra-slight /// hints. /// @@ -110,170 +123,6 @@ vec2 dilatePosition(vec2 position, float normalAngle, vec2 amount) { return position + vec2(cos(normalAngle), -sin(normalAngle)) * amount; } -vec2 transformECAAPosition(vec2 position, - vec4 localTransformST, - vec2 localTransformExt, - mat4 globalTransform) { - position = transformVertexPositionAffine(position, localTransformST, localTransformExt); - return transformVertexPosition(position, globalTransform); -} - -vec2 transformECAAPositionToScreenSpace(vec2 position, - vec4 localTransformST, - vec2 localTransformExt, - mat4 globalTransform, - ivec2 framebufferSize) { - position = transformECAAPosition(position, - localTransformST, - localTransformExt, - globalTransform); - return convertClipToScreenSpace(position, framebufferSize); -} - -vec2 computeECAAPosition(vec2 position, - float normalAngle, - vec2 emboldenAmount, - vec4 hints, - vec4 localTransformST, - vec2 localTransformExt, - mat4 globalTransform, - ivec2 framebufferSize) { - position = dilatePosition(position, normalAngle, emboldenAmount); - position = hintPosition(position, hints); - position = transformECAAPositionToScreenSpace(position, - localTransformST, - localTransformExt, - globalTransform, - framebufferSize); - return position; -} - -float computeECAAWinding(inout vec2 leftPosition, inout vec2 rightPosition) { - float winding = sign(leftPosition.x - rightPosition.x); - if (winding > 0.0) { - vec2 tmp = leftPosition; - leftPosition = rightPosition; - rightPosition = tmp; - } - - 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); - vec2 position = mix(floor(extents.xy), ceil(extents.zw), quadPosition); - return convertScreenToClipSpace(position, framebufferSize); -} - -// FIXME(pcwalton): Clean up this signature somehow? -bool computeECAAQuadPosition(out vec2 outPosition, - out float outWinding, - inout vec2 leftPosition, - inout vec2 rightPosition, - vec2 quadPosition, - ivec2 framebufferSize, - vec4 localTransformST, - vec2 localTransformExt, - mat4 globalTransform, - vec4 hints, - vec4 bounds, - vec2 normalAngles, - vec2 emboldenAmount) { - leftPosition = computeECAAPosition(leftPosition, - normalAngles.x, - emboldenAmount, - hints, - localTransformST, - localTransformExt, - globalTransform, - framebufferSize); - rightPosition = computeECAAPosition(rightPosition, - normalAngles.y, - emboldenAmount, - hints, - localTransformST, - localTransformExt, - globalTransform, - framebufferSize); - - float winding = computeECAAWinding(leftPosition, rightPosition); - outWinding = winding; - if (winding == 0.0) { - outPosition = vec2(0.0); - return false; - } - - 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 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; - } - - 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; -} - /// Returns true if the slope of the line along the given vector is negative. bool slopeIsNegative(vec2 dp) { return dp.y < 0.0; @@ -415,10 +264,30 @@ vec4 fetchPathAffineTransform(out vec2 outPathTransformExt, return fetchFloat4Data(pathTransformSTTexture, pathID, pathTransformSTDimensions); } -float detMat2(mat2 m) { - return m[0][0] * m[1][1] - m[0][1] * m[1][0]; +// Are we inside the convex hull of the curve? (This will always be false if this is a line.) +bool insideCurve(vec3 uv) { + return uv.z != 0.0 && uv.x > 0.0 && uv.x < 1.0 && uv.y > 0.0 && uv.y < 1.0; } -mat2 invMat2(mat2 m) { - return mat2(m[1][1], -m[0][1], -m[1][0], m[0][0]) / detMat2(m); +float signedDistanceToCurve(vec2 uv, vec2 dUVDX, vec2 dUVDY, bool inCurve) { + // u^2 - v for curves inside uv square; u - v otherwise. + float g = uv.x; + vec2 dG = vec2(dUVDX.x, dUVDY.x); + if (inCurve) { + g *= uv.x; + dG *= 2.0 * uv.x; + } + g -= uv.y; + dG -= vec2(dUVDX.y, dUVDY.y); + return g / length(dG); +} + +// Cubic approximation to the square area coverage, accurate to about 4%. +float estimateArea(float dist) { + if (dist >= 0.707107) + return 0.5; + // Catch NaNs here. + if (!(dist > -0.707107)) + return -0.5; + return 1.14191 * dist - 0.83570 * dist * dist * dist; } diff --git a/shaders/gles2/conservative-interior.vs.glsl b/shaders/gles2/conservative-interior.vs.glsl index 84499e39..b78b8c1c 100644 --- a/shaders/gles2/conservative-interior.vs.glsl +++ b/shaders/gles2/conservative-interior.vs.glsl @@ -62,7 +62,8 @@ void main() { vec2 translation = uTransformST.zw + globalTransformLinear * transformST.zw; float onePixel = 2.0 / float(uFramebufferSize.y); - float dilation = length(invMat2(transformLinear) * vec2(0.0, onePixel)); + float dilation = length(transformVertexPositionInverseLinear(vec2(0.0, onePixel), + transformLinear)); vec2 position = aPosition + vec2(0.0, imod(vertexID, 6) < 3 ? dilation : -dilation); position = transformLinear * position + translation; diff --git a/shaders/gles2/ecaa-curve.fs.glsl b/shaders/gles2/ecaa-curve.fs.glsl deleted file mode 100644 index 4004030f..00000000 --- a/shaders/gles2/ecaa-curve.fs.glsl +++ /dev/null @@ -1,45 +0,0 @@ -// pathfinder/shaders/gles2/ecaa-curve.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. - -//! Performs analytic *coverage antialiasing* (XCAA) in order to render curves. -//! -//! Given endpoints P0 and P2 and control point P1, this shader expects that -//! P0.x <= P1.x <= P2.x. - -precision highp float; - -varying vec4 vEndpoints; -varying vec2 vControlPoint; -varying float vWinding; - -void main() { - // Unpack. - vec2 pixelCenter = gl_FragCoord.xy; - vec2 p0 = vEndpoints.xy, p1 = vEndpoints.zw; - vec2 cp = vControlPoint; - - // Compute pixel extents. - vec2 pixelColumnBounds = pixelCenter.xx + vec2(-0.5, 0.5); - - // Clip the curve to the left and right edges to create a line. - vec2 t = solveCurveT(p0.x, cp.x, p1.x, pixelColumnBounds); - - // Handle endpoints properly. These tests are negated to handle NaNs. - if (!(p0.x < pixelColumnBounds.x)) - t.x = 0.0; - if (!(p1.x > pixelColumnBounds.y)) - t.y = 1.0; - - vec2 clippedP0 = mix(mix(p0, cp, t.x), mix(cp, p1, t.x), t.x); - vec2 clippedDP = mix(mix(p0, cp, t.y), mix(cp, p1, t.y), t.y) - clippedP0; - - // Compute area. - gl_FragColor = vec4(computeCoverage(clippedP0, clippedDP, pixelCenter.y, vWinding)); -} diff --git a/shaders/gles2/ecaa-curve.vs.glsl b/shaders/gles2/ecaa-curve.vs.glsl deleted file mode 100644 index 98a750a1..00000000 --- a/shaders/gles2/ecaa-curve.vs.glsl +++ /dev/null @@ -1,148 +0,0 @@ -// pathfinder/shaders/gles2/ecaa-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. - -//! Implements *edge coverage antialiasing* (ECAA) for curved path segments. -//! -//! This shader expects to render to the red channel of a floating point color -//! buffer. Half precision floating point should be sufficient. -//! -//! Use this shader only when *all* of the following are true: -//! -//! 1. You are only rendering monochrome paths such as text. (Otherwise, -//! consider MCAA.) -//! -//! 2. The paths are relatively small, so overdraw is not a concern. -//! (Otherwise, consider MCAA.) -//! -//! 3. Your transform is only a scale and/or translation, not a perspective, -//! rotation, or skew. (Otherwise, consider `ecaa-transformed-curve`.) - -precision highp float; - -/// A 3D transform to be applied to all points. -uniform mat4 uTransform; -/// Vertical snapping positions. -uniform vec4 uHints; -/// The framebuffer size in pixels. -uniform ivec2 uFramebufferSize; -/// The size of the path bounds texture in texels. -uniform ivec2 uPathBoundsDimensions; -/// The path bounds texture, one rect per path ID. -uniform sampler2D uPathBounds; -/// The size of the path transform buffer texture in texels. -uniform ivec2 uPathTransformSTDimensions; -/// The path transform buffer texture, one path dilation per texel. -uniform sampler2D uPathTransformST; -/// The size of the extra path transform factors buffer texture in texels. -uniform ivec2 uPathTransformExtDimensions; -/// The extra path transform factors buffer texture, packed two path transforms per texel. -uniform sampler2D uPathTransformExt; -/// The amount of faux-bold to apply, in local path units. -uniform vec2 uEmboldenAmount; - -/// The abstract position within the quad: (0.0, 0.0) to (1.0, 1.0). -attribute vec2 aTessCoord; -/// The position of the left endpoint. -attribute vec2 aLeftPosition; -/// The position of the control point. -attribute vec2 aControlPointPosition; -/// The position of the right endpoint. -attribute vec2 aRightPosition; -/// The path ID (starting from 1). -attribute float aPathID; -/// The normal angles of the left endpoint, control point, and right endpoint, respectively. -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 leftRightNormalAngles = aNormalAngles.xz; - float controlPointNormalAngle = aNormalAngles.y; - - vec2 pathTransformExt; - vec4 pathTransformST = fetchPathAffineTransform(pathTransformExt, - uPathTransformST, - uPathTransformSTDimensions, - uPathTransformExt, - uPathTransformExtDimensions, - pathID); - vec4 bounds = fetchFloat4Data(uPathBounds, pathID, uPathBoundsDimensions); - - // Transform the points, and compute the position of this vertex. - 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 = computeECAAWinding(leftPosition, rightPosition); - if (winding == 0.0) { - gl_Position = vec4(0.0); - return; - } - - vec2 edgeBL = bounds.xy, edgeTL = bounds.xw, edgeTR = bounds.zw, edgeBR = bounds.zy; - edgeBL = transformECAAPosition(edgeBL, pathTransformST, pathTransformExt, uTransform); - edgeBR = transformECAAPosition(edgeBR, pathTransformST, pathTransformExt, uTransform); - edgeTL = transformECAAPosition(edgeTL, pathTransformST, pathTransformExt, uTransform); - edgeTR = transformECAAPosition(edgeTR, pathTransformST, pathTransformExt, uTransform); - - // 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(uFramebufferSize.y); - - vec2 position; - if (aTessCoord.x < 0.25) - position = vec2(floor(leftPosition.x), leftPosition.y); - else if (aTessCoord.x < 0.75) - position = controlPointPosition; - else - position = vec2(ceil(rightPosition.x), rightPosition.y); - - // FIXME(pcwalton): Only compute path bottom Y if necessary. - if (aTessCoord.y < 0.5) - position.y = floor(position.y - 1.0); - else - position.y = pathBottomY; - - position = convertScreenToClipSpace(position, uFramebufferSize); - float depth = convertPathIndexToViewportDepthValue(pathID); - - gl_Position = vec4(position, depth, 1.0); - vEndpoints = vec4(leftPosition, rightPosition); - vControlPoint = controlPointPosition; - vWinding = winding; -} diff --git a/shaders/gles2/ecaa-line.fs.glsl b/shaders/gles2/ecaa-line.fs.glsl deleted file mode 100644 index 0dd2407b..00000000 --- a/shaders/gles2/ecaa-line.fs.glsl +++ /dev/null @@ -1,31 +0,0 @@ -// pathfinder/shaders/gles2/ecaa-line.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. - -//! Performs analytic *coverage antialiasing* (XCAA) in order to render lines. -//! -//! This shader expects that P1 is to the right of P0. - -precision highp float; - -varying vec4 vEndpoints; -varying float vWinding; - -void main() { - // Unpack. - vec2 pixelCenter = gl_FragCoord.xy; - vec2 p0 = vEndpoints.xy, p1 = vEndpoints.zw; - - // Clip to left and right pixel boundaries. - vec2 dP = p1 - p0; - vec4 p0DPX = clipLineToPixelColumn(p0, dP, pixelCenter.x); - - // Compute area. - gl_FragColor = vec4(computeCoverage(p0DPX.xy, p0DPX.zw, pixelCenter.y, vWinding)); -} diff --git a/shaders/gles2/ecaa-line.vs.glsl b/shaders/gles2/ecaa-line.vs.glsl deleted file mode 100644 index 4334f239..00000000 --- a/shaders/gles2/ecaa-line.vs.glsl +++ /dev/null @@ -1,130 +0,0 @@ -// pathfinder/shaders/gles2/ecaa-line.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. - -//! Implements *edge coverage antialiasing* (ECAA) for straight-line path -//! segments. -//! -//! This shader expects to render to the red channel of a floating point color -//! buffer. Half precision floating point should be sufficient. -//! -//! Use this shader only when *both* of the following are true: -//! -//! 1. You are only rendering monochrome paths such as text. (Otherwise, -//! consider MCAA.) -//! -//! 2. The paths are relatively small, so overdraw is not a concern. -//! (Otherwise, consider MCAA.) - -precision highp float; - -/// A 3D transform to be applied to the object. -uniform mat4 uTransform; -/// Vertical snapping positions. -uniform vec4 uHints; -/// The framebuffer size in pixels. -uniform ivec2 uFramebufferSize; -/// The size of the path bounds texture in texels. -uniform ivec2 uPathBoundsDimensions; -/// The path bounds texture, one rect per path ID. -uniform sampler2D uPathBounds; -/// The size of the path transform buffer texture in texels. -uniform ivec2 uPathTransformSTDimensions; -/// The path transform buffer texture, one path dilation per texel. -uniform sampler2D uPathTransformST; -/// The size of the extra path transform factors buffer texture in texels. -uniform ivec2 uPathTransformExtDimensions; -/// The extra path transform factors buffer texture, packed two path transforms per texel. -uniform sampler2D uPathTransformExt; -/// The amount of faux-bold to apply, in local path units. -uniform vec2 uEmboldenAmount; - -/// The abstract quad position: (0.0, 0.0) to (1.0, 1.0). -attribute vec2 aTessCoord; -/// The position of the left endpoint. -attribute vec2 aLeftPosition; -/// The position of the right endpoint. -attribute vec2 aRightPosition; -/// The path ID (starting from 1). -attribute float aPathID; -/// The normal angles of the left endpoint and right endpoint, respectively. -attribute vec2 aNormalAngles; - -varying vec4 vEndpoints; -varying float vWinding; - -void main() { - vec2 leftPosition = aLeftPosition; - vec2 rightPosition = aRightPosition; - int pathID = int(aPathID); - vec2 leftRightNormalAngles = aNormalAngles; - - vec2 pathTransformExt; - vec4 pathTransformST = fetchPathAffineTransform(pathTransformExt, - uPathTransformST, - uPathTransformSTDimensions, - uPathTransformExt, - uPathTransformExtDimensions, - pathID); - vec4 bounds = fetchFloat4Data(uPathBounds, pathID, uPathBoundsDimensions); - - // Transform the points, and compute the position of this vertex. - leftPosition = computeECAAPosition(leftPosition, - aNormalAngles.x, - uEmboldenAmount, - uHints, - pathTransformST, - pathTransformExt, - uTransform, - uFramebufferSize); - rightPosition = computeECAAPosition(rightPosition, - aNormalAngles.y, - uEmboldenAmount, - uHints, - pathTransformST, - pathTransformExt, - uTransform, - uFramebufferSize); - float winding = computeECAAWinding(leftPosition, rightPosition); - if (winding == 0.0) { - gl_Position = vec4(0.0); - return; - } - - vec2 edgeBL = bounds.xy, edgeTL = bounds.xw, edgeTR = bounds.zw, edgeBR = bounds.zy; - edgeBL = transformECAAPosition(edgeBL, pathTransformST, pathTransformExt, uTransform); - edgeBR = transformECAAPosition(edgeBR, pathTransformST, pathTransformExt, uTransform); - edgeTL = transformECAAPosition(edgeTL, pathTransformST, pathTransformExt, uTransform); - edgeTR = transformECAAPosition(edgeTR, pathTransformST, pathTransformExt, uTransform); - - // 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(uFramebufferSize.y); - - vec2 position; - if (aTessCoord.x < 0.5) - position = vec2(floor(leftPosition.x), leftPosition.y); - else - position = vec2(ceil(rightPosition.x), rightPosition.y); - - // FIXME(pcwalton): Only compute path bottom Y if necessary. - if (aTessCoord.y < 0.5) - position.y = floor(position.y - 1.0); - else - position.y = pathBottomY; - - position = convertScreenToClipSpace(position, uFramebufferSize); - float depth = convertPathIndexToViewportDepthValue(pathID); - - gl_Position = vec4(position, depth, 1.0); - vEndpoints = vec4(leftPosition, rightPosition); - vWinding = winding; -} diff --git a/shaders/gles2/ecaa-transformed-curve.vs.glsl b/shaders/gles2/ecaa-transformed-curve.vs.glsl deleted file mode 100644 index 9a5f03e3..00000000 --- a/shaders/gles2/ecaa-transformed-curve.vs.glsl +++ /dev/null @@ -1,162 +0,0 @@ -// 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. - -//! Implements *edge coverage antialiasing* (ECAA) for curved path segments, -//! performing splitting as necessary. -//! -//! This shader expects to render to the red channel of a floating point color -//! buffer. Half precision floating point should be sufficient. -//! -//! This is a two-pass shader. It must be run twice, first with `uPassIndex` -//! equal to 0, and then with `uPassIndex` equal to 1. -//! -//! Use this shader only when *all* of the following are true: -//! -//! 1. You are only rendering monochrome paths such as text. (Otherwise, -//! consider MCAA.) -//! -//! 2. The paths are relatively small, so overdraw is not a concern. -//! (Otherwise, consider MCAA.) -//! -//! 3. Your transform contains perspective, rotation, or skew. (Otherwise, -//! consider `ecaa-curve`, which is faster and saves a pass.) - -precision highp float; - -/// A 3D transform to be applied to all points. -uniform mat4 uTransform; -/// Vertical snapping positions. -uniform vec4 uHints; -/// The framebuffer size in pixels. -uniform ivec2 uFramebufferSize; -/// The size of the path bounds texture in texels. -uniform ivec2 uPathBoundsDimensions; -/// The path bounds texture, one rect per path ID. -uniform sampler2D uPathBounds; -/// The size of the path transform buffer texture in texels. -uniform ivec2 uPathTransformSTDimensions; -/// The path transform buffer texture, one path dilation per texel. -uniform sampler2D uPathTransformST; -/// The size of the extra path transform factors buffer texture in texels. -uniform ivec2 uPathTransformExtDimensions; -/// The extra path transform factors buffer texture, packed two path transforms per texel. -uniform sampler2D uPathTransformExt; -/// The amount of faux-bold to apply, in local path units. -uniform vec2 uEmboldenAmount; -/// The pass index: 0 or 1. -/// -/// This is a multipass algorithm: first, issue a draw call with this value set to 0; then, issue -/// an identical draw call with this value set to 1. -uniform int uPassIndex; - -/// The abstract quad position: (0.0, 0.0) to (1.0, 1.0). -attribute vec2 aTessCoord; -/// The position of the left endpoint. -attribute vec2 aLeftPosition; -/// The position of the control point. -attribute vec2 aControlPointPosition; -/// The position of the right endpoint. -attribute vec2 aRightPosition; -/// The path ID (starting from 1). -attribute float aPathID; -/// The normal angles of the left endpoint, control point, and right endpoint, respectively. -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 edgeBL = bounds.xy, edgeTL = bounds.xw, edgeTR = bounds.zw, edgeBR = bounds.zy; - edgeBL = transformECAAPosition(edgeBL, pathTransformST, pathTransformExt, uTransform); - edgeBR = transformECAAPosition(edgeBR, pathTransformST, pathTransformExt, uTransform); - edgeTL = transformECAAPosition(edgeTL, pathTransformST, pathTransformExt, uTransform); - edgeTR = transformECAAPosition(edgeTR, pathTransformST, pathTransformExt, uTransform); - - // 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(uFramebufferSize.y); - - vec2 position; - if (aTessCoord.x < 0.25) - position = vec2(floor(leftPosition.x), leftPosition.y); - else if (aTessCoord.x < 0.75) - position = controlPointPosition; - else - position = vec2(ceil(rightPosition.x), rightPosition.y); - - // FIXME(pcwalton): Only compute path bottom Y if necessary. - if (aTessCoord.y < 0.5) - position.y = floor(position.y - 1.0); - else - position.y = pathBottomY; - - position = convertScreenToClipSpace(position, uFramebufferSize); - float depth = convertPathIndexToViewportDepthValue(pathID); - - gl_Position = vec4(position, depth, 1.0); - vEndpoints = vec4(leftPosition, rightPosition); - vControlPoint = controlPointPosition; - vWinding = winding; -} diff --git a/shaders/gles2/mcaa.fs.glsl b/shaders/gles2/mcaa.fs.glsl index 83b8baab..380c4c40 100644 --- a/shaders/gles2/mcaa.fs.glsl +++ b/shaders/gles2/mcaa.fs.glsl @@ -19,36 +19,13 @@ varying vec4 vColor; varying vec4 vUV; varying vec4 vSignMode; -// Cubic approximation to the square area coverage, accurate to about 4%. -float estimateArea(float dist) { - if (dist >= 0.707107) - return 1.0; - // Catch NaNs here. - if (!(dist > -0.707107)) - return 0.0; - return 0.5 + 1.14191 * dist - 0.83570 * dist * dist * dist; -} - -float computeAlpha(vec2 uv, float curveSign, float mode) { - vec2 dUVDX = dFdx(uv), dUVDY = dFdy(uv); - - // u^2 - v for curves inside uv square; u - v otherwise. - float g = uv.x; - vec2 dG = vec2(dUVDX.x, dUVDY.x); - if (mode > 0.0 && uv.x > 0.0 && uv.x < 1.0 && uv.y > 0.0 && uv.y < 1.0) { - g *= uv.x; - dG *= 2.0 * uv.x; - } - g -= uv.y; - dG -= vec2(dUVDX.y, dUVDY.y); - - float signedDistance = g / length(dG); - return estimateArea(signedDistance * curveSign); -} - void main() { - float alpha = 1.0; - alpha -= computeAlpha(vUV.xy, vSignMode.x, vSignMode.z); - alpha -= computeAlpha(vUV.zw, vSignMode.y, vSignMode.w); + bool inUpperCurve = insideCurve(vec3(vUV.xy, vSignMode.z > 0.0 ? 1.0 : 0.0)); + bool inLowerCurve = insideCurve(vec3(vUV.zw, vSignMode.w > 0.0 ? 1.0 : 0.0)); + + float upperDist = signedDistanceToCurve(vUV.xy, dFdx(vUV.xy), dFdy(vUV.xy), inUpperCurve); + float lowerDist = signedDistanceToCurve(vUV.zw, dFdx(vUV.zw), dFdy(vUV.zw), inLowerCurve); + + float alpha = -estimateArea(upperDist * vSignMode.x) - estimateArea(lowerDist * vSignMode.y); gl_FragColor = alpha * vColor; } diff --git a/shaders/gles2/mcaa.vs.glsl b/shaders/gles2/mcaa.vs.glsl index 34035d88..7b508294 100644 --- a/shaders/gles2/mcaa.vs.glsl +++ b/shaders/gles2/mcaa.vs.glsl @@ -1,6 +1,6 @@ // pathfinder/shaders/gles2/mcaa.vs.glsl // -// Copyright (c) 2017 The Pathfinder Project Developers. +// Copyright (c) 2018 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -57,6 +57,7 @@ attribute vec4 aRect; attribute vec4 aUV; attribute vec4 aDUVDX; attribute vec4 aDUVDY; +// TODO(pcwalton): This is redundant; sign 0 can be used to indicate lines. attribute vec4 aSignMode; attribute float aPathID; @@ -85,7 +86,8 @@ void main() { translation = uTransformST.zw + globalTransformLinear * translation; float onePixel = 2.0 / float(uFramebufferSize.y); - float dilation = length(invMat2(transformLinear) * vec2(0.0, onePixel)); + float dilation = length(transformVertexPositionInverseLinear(vec2(0.0, onePixel), + transformLinear)); tessCoord.y += tessCoord.y < 0.5 ? -dilation : dilation; vec2 position = transformLinear * tessCoord + translation; diff --git a/shaders/gles2/stencil-aaa.fs.glsl b/shaders/gles2/stencil-aaa.fs.glsl new file mode 100644 index 00000000..6402f037 --- /dev/null +++ b/shaders/gles2/stencil-aaa.fs.glsl @@ -0,0 +1,53 @@ +// pathfinder/shaders/gles2/stencil-aaa.fs.glsl +// +// Copyright (c) 2018 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. + +varying vec3 vUV; + +void main() { + // Unpack. + vec3 uv = vUV; + vec2 dUVDX = dFdx(uv.xy), dUVDY = dFdy(uv.xy); + + // Calculate the reciprocal of the Jacobian determinant. This will be useful for determining + // distance from endpoints. + // + // http://pcwalton.github.io/2018/02/14/determining-triangle-geometry-in-fragment-shaders.html + float recipJ = 1.0 / det2(mat2(dUVDX, dUVDY)); + + // Calculate X distances between endpoints. + float v02DX = dUVDY.y - dUVDY.x, v10DX = -dUVDY.y, v21DX = 2.0 * dUVDY.x - dUVDY.y; + float v02X = v02DX * recipJ, v10X = v10DX * recipJ; + + // Compute winding number and convexity. + bool inCurve = insideCurve(uv); + float openWinding = fastSign(-v02X); + float convex = uv.z != 0.0 ? uv.z : -fastSign(dUVDY.y) * openWinding; + + // Compute open rect area. + vec2 areas = clamp(vec2(det2(mat2(uv.xy, dUVDY))) * recipJ - vec2(0.0, v02X), -0.5, 0.5); + float openRectArea = openWinding * (areas.y - areas.x); + + // Compute closed rect area and winding, if necessary. + float closedRectArea = 0.0, closedWinding = 0.0; + if (inCurve && v10DX * v21DX < 0.0) { + closedRectArea = 0.5 - fastSign(v10X) * (v10X * openWinding < 0.0 ? areas.x : areas.y); + closedWinding = fastSign((dUVDX.y - dUVDX.x) * dUVDY.y); + } + + // Calculate approximate area of the curve covering this pixel square. + float curveArea = estimateArea(signedDistanceToCurve(uv.xy, dUVDX, dUVDY, inCurve)); + + // Calculate alpha. + vec2 alpha = vec2(openWinding, closedWinding) * 0.5 + convex * curveArea; + alpha *= vec2(openRectArea, closedRectArea); + + // Finish up. + gl_FragColor = vec4(alpha.x + alpha.y); +} diff --git a/shaders/gles2/stencil-aaa.vs.glsl b/shaders/gles2/stencil-aaa.vs.glsl new file mode 100644 index 00000000..1874d9ca --- /dev/null +++ b/shaders/gles2/stencil-aaa.vs.glsl @@ -0,0 +1,123 @@ +// pathfinder/shaders/gles2/stencil-aaa.vs.glsl +// +// Copyright (c) 2018 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. + +uniform vec4 uTransformST; +uniform vec2 uTransformExt; +uniform ivec2 uFramebufferSize; +/// Vertical snapping positions. +uniform vec4 uHints; +uniform vec2 uEmboldenAmount; +uniform ivec2 uPathBoundsDimensions; +uniform sampler2D uPathBounds; +uniform ivec2 uPathTransformSTDimensions; +uniform sampler2D uPathTransformST; +uniform ivec2 uPathTransformExtDimensions; +uniform sampler2D uPathTransformExt; + +attribute vec2 aTessCoord; +attribute vec2 aFromPosition; +attribute vec2 aCtrlPosition; +attribute vec2 aToPosition; +attribute vec2 aFromNormal; +attribute vec2 aCtrlNormal; +attribute vec2 aToNormal; +attribute float aPathID; + +varying vec3 vUV; + +void main() { + // Unpack. + vec2 emboldenAmount = uEmboldenAmount * 0.5; + int pathID = int(aPathID); + + // Hint positions. + vec2 fromPosition = hintPosition(aFromPosition, uHints); + vec2 ctrlPosition = hintPosition(aCtrlPosition, uHints); + vec2 toPosition = hintPosition(aToPosition, uHints); + + // Embolden as necessary. + fromPosition -= aFromNormal * emboldenAmount; + ctrlPosition -= aCtrlNormal * emboldenAmount; + toPosition -= aToNormal * emboldenAmount; + + // Fetch transform. + vec2 transformExt; + vec4 transformST = fetchPathAffineTransform(transformExt, + uPathTransformST, + uPathTransformSTDimensions, + uPathTransformExt, + uPathTransformExtDimensions, + pathID); + + // Concatenate transforms. + mat2 globalTransformLinear = mat2(uTransformST.x, uTransformExt, uTransformST.y); + mat2 localTransformLinear = mat2(transformST.x, -transformExt, transformST.y); + mat2 transformLinear = globalTransformLinear * localTransformLinear; + + // Perform the linear component of the transform (everything but translation). + fromPosition = transformLinear * fromPosition; + ctrlPosition = transformLinear * ctrlPosition; + toPosition = transformLinear * toPosition; + + // Choose correct quadrant for rotation. + vec4 bounds = fetchFloat4Data(uPathBounds, pathID, uPathBoundsDimensions); + vec2 fillVector = transformLinear * vec2(0.0, 1.0); + vec2 corner = transformLinear * vec2(fillVector.x < 0.0 ? bounds.z : bounds.x, + fillVector.y < 0.0 ? bounds.y : bounds.w); + + // Compute edge vectors. + vec2 v02 = toPosition - fromPosition; + vec2 v01 = ctrlPosition - fromPosition, v21 = ctrlPosition - toPosition; + + // Compute area of convex hull (w). Change from curve to line if appropriate. + float w = det2(mat2(v01, v02)); + float sqLen01 = dot(v01, v01), sqLen02 = dot(v02, v02), sqLen21 = dot(v21, v21); + float minCtrlSqLen = dot(v02, v02) * 0.0001; + float cosTheta = dot(v01, v21); + if (sqLen01 < minCtrlSqLen || sqLen21 < minCtrlSqLen || + cosTheta * cosTheta >= 0.95 * sqLen01 * sqLen21) { + w = 0.0; + v01 = vec2(0.5, abs(v02.y) >= 0.01 ? 0.0 : 0.5) * v02.xx; + } + + // Compute position and dilate. If too thin, discard to avoid artefacts. + vec2 dilation = vec2(0.0), position; + if (abs(v02.x) < 0.0001) { + position.x = 0.0; + } else if (aTessCoord.x < 0.5) { + position.x = min(min(fromPosition.x, toPosition.x), ctrlPosition.x); + dilation.x = -1.0; + } else { + position.x = max(max(fromPosition.x, toPosition.x), ctrlPosition.x); + dilation.x = 1.0; + } + if (aTessCoord.y < 0.5) { + position.y = min(min(fromPosition.y, toPosition.y), ctrlPosition.y); + dilation.y = -1.0; + } else { + position.y = corner.y; + } + position += 2.0 * dilation / vec2(uFramebufferSize); + + // Compute UV using Cramer's rule. + // https://gamedev.stackexchange.com/a/63203 + vec2 v03 = position - fromPosition; + vec3 uv = vec3(0.0, det2(mat2(v01, v03)), sign(w)); + uv.x = uv.y + 0.5 * det2(mat2(v03, v02)); + uv.xy /= det2(mat2(v01, v02)); + + // Compute final position and depth. + position += uTransformST.zw + globalTransformLinear * transformST.zw; + float depth = convertPathIndexToViewportDepthValue(pathID); + + // Finish up. + gl_Position = vec4(position, depth, 1.0); + vUV = uv; +} diff --git a/utils/frontend/src/main.rs b/utils/frontend/src/main.rs index 70bcfbce..e0e685b2 100644 --- a/utils/frontend/src/main.rs +++ b/utils/frontend/src/main.rs @@ -100,6 +100,7 @@ fn convert_font(font_path: &Path, output_path: &Path) -> Result<(), ()> { let path_index = (glyph_index + 1) as u16; partitioner.library_mut().push_segments(path_index, path.iter()); partitioner.library_mut().push_normals(path_index, path.iter()); + partitioner.library_mut().push_stencil_segments(path_index, path.iter()); paths.push((path_index, path.iter().collect())); }