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.
This commit is contained in:
Patrick Walton 2018-02-05 15:10:52 -08:00
parent 87eb3038eb
commit a84b7c7cbd
30 changed files with 589 additions and 1377 deletions

View File

@ -364,6 +364,8 @@ class ThreeDRenderer extends Renderer {
camera: PerspectiveCamera;
needsStencil: boolean = false;
get isMulticolor(): boolean {
return false;
}

View File

@ -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<T> {
@ -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;

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-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();
}

View File

@ -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<MeshBufferTypeDescriptor> = {
bBoxPathIDs: { type: 'Uint16', size: 1 },
@ -92,12 +85,9 @@ const MESH_TYPES: Meshes<MeshBufferTypeDescriptor> = {
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<BufferType> = {
@ -106,12 +96,9 @@ const BUFFER_TYPES: Meshes<BufferType> = {
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<keyof PathRanges> = [
'bQuadVertexPositionPathRanges',
'bQuadVertexInteriorIndexPathRanges',
'bBoxPathRanges',
'segmentCurveRanges',
'segmentLineRanges',
'stencilSegmentPathRanges',
];
type BufferType = 'ARRAY_BUFFER' | 'ELEMENT_ARRAY_BUFFER';
@ -170,31 +151,26 @@ export interface Meshes<T> {
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<ArrayBuffer>, MeshDataCounts, PathRanges {
@ -203,27 +179,22 @@ export class PathfinderMeshData implements Meshes<ArrayBuffer>, 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<ArrayBuffer>, optionalRanges?: PathRanges) {
if (meshes instanceof ArrayBuffer) {
@ -261,8 +232,7 @@ export class PathfinderMeshData implements Meshes<ArrayBuffer>, 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<ArrayBuffer>, 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<WebGLBuffer>, 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<keyof Meshes<void>>) {

View File

@ -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<string> = {
@ -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<ReferenceTestView> {
@ -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]);
}

View File

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

View File

@ -21,11 +21,9 @@ export interface ShaderMap<T> {
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<keyof ShaderMap<void>> = [
'direct3DInterior',
'ssaaSubpixelResolve',
'mcaa',
'ecaaLine',
'ecaaCurve',
'ecaaTransformedCurve',
'stencilAAA',
'xcaaMonoResolve',
'xcaaMonoSubpixelResolve',
'demo3DDistantGlyph',
@ -93,18 +89,6 @@ const SHADER_URLS: ShaderMap<ShaderProgramURLs> = {
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<ShaderProgramURLs> = {
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",

View File

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

View File

@ -46,6 +46,8 @@ export abstract class SVGRenderer extends Renderer {
camera: OrthographicCamera;
needsStencil: boolean = false;
private options: SVGRendererOptions;
get isMulticolor(): boolean {

View File

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

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-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;
}

View File

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

View File

@ -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<keyof ShaderMap<void>> {
return ['ecaaLine'];
}
protected transformType: TransformType = 'affine';
protected patchIndices: Uint8Array = MCAA_PATCH_INDICES;
protected mightUseAAFramebuffer: boolean = true;
protected get curveShaderProgramNames(): Array<keyof ShaderMap<void>> {
return ['ecaaCurve', 'ecaaTransformedCurve'];
}
protected get mightUseAAFramebuffer(): boolean {
return true;
}
private lineVAOs: Partial<ShaderMap<WebGLVertexArrayObject>>;
private curveVAOs: Partial<ShaderMap<WebGLVertexArrayObject>>;
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>):
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<void>,
transformedProgram: keyof ShaderMap<void>):
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<void>,
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;
}
}

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
@ -474,10 +474,10 @@ fn partition_font(request: Json<PartitionFontRequest>) -> Result<PartitionRespon
// Partition the decoded glyph outlines.
let mut library = MeshLibrary::new();
for (stored_path_index, path_descriptor) in path_descriptors.iter().enumerate() {
library.push_segments((path_descriptor.path_index + 1) as u16,
PathIter::new(paths[stored_path_index].iter().cloned()));
library.push_normals((path_descriptor.path_index + 1) as u16,
PathIter::new(paths[stored_path_index].iter().cloned()));
library.push_stencil_segments((path_descriptor.path_index + 1) as u16,
PathIter::new(paths[stored_path_index].iter().cloned()));
library.push_stencil_normals((path_descriptor.path_index + 1) as u16,
paths[stored_path_index].iter().cloned());
}
let mut partitioner = Partitioner::new(library);

View File

@ -1,6 +1,6 @@
// pathfinder/partitioner/src/lib.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
// Copyright © 2018 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
@ -111,13 +111,6 @@ pub struct BQuadVertexPositions {
pub lower_left_vertex_position: Point2D<f32>,
}
#[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 {

View File

@ -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<Point2D<f32>>,
pub b_vertex_loop_blinn_data: Vec<BVertexLoopBlinnData>,
pub b_boxes: Vec<BBox>,
pub segments: MeshLibrarySegments,
pub segment_normals: MeshLibrarySegmentNormals,
pub stencil_segments: Vec<StencilSegment>,
pub stencil_normals: Vec<StencilNormals>,
}
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<I>(&mut self, path_id: u16, stream: I)
where I: Iterator<Item = PathEvent> {
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<I>(&mut self, path_id: u16, stream: I)
where I: Iterator<Item = PathEvent> {
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<I>(&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<I>(&mut self, _path_id: u16, stream: I)
where I: Iterator<Item = PathEvent> + 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>) -> 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<u32>,
pub segment_lines: Range<u32>,
pub segment_curves: Range<u32>,
pub stencil_segments: Range<u32>,
}
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<LineSegment>,
pub curves: Vec<CurveSegment>,
}
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<LineSegmentNormals>,
pub curve_normals: Vec<CurveSegmentNormals>,
}
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<f32>,
pub endpoint_1: Point2D<f32>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct CurveSegment {
pub endpoint_0: Point2D<f32>,
pub control_point: Point2D<f32>,
pub endpoint_1: Point2D<f32>,
}
#[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<f32>,
@ -578,12 +473,18 @@ pub struct BBox {
pub lower_mode: f32,
}
#[derive(Clone, Copy, Debug)]
struct CornerPositions {
upper_left: Point2D<f32>,
upper_right: Point2D<f32>,
lower_left: Point2D<f32>,
lower_right: Point2D<f32>,
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct StencilSegment {
pub from: Point2D<f32>,
pub ctrl: Point2D<f32>,
pub to: Point2D<f32>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct StencilNormals {
pub from: Vector2D<f32>,
pub ctrl: Vector2D<f32>,
pub to: Vector2D<f32>,
}
#[derive(Clone, Copy, Debug)]

View File

@ -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<f32>,
pub ctrl: Vector2D<f32>,
pub to: Vector2D<f32>,
}
#[derive(Clone)]
pub struct PathNormals {
normals: Vec<Vector2D<f32>>,
normals: Vec<SegmentNormals>,
}
impl PathNormals {
@ -26,7 +32,7 @@ impl PathNormals {
}
#[inline]
pub fn normals(&self) -> &[Vector2D<f32>] {
pub fn normals(&self) -> &[SegmentNormals] {
&self.normals
}
@ -34,77 +40,96 @@ impl PathNormals {
self.normals.clear()
}
pub fn add_path<I>(&mut self, path: I) where I: Iterator<Item = PathEvent> {
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<I>(&mut self, mut stream: I) where I: Iterator<Item = PathEvent> + 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<I>(&mut self, path_stream: I, path_points: &mut Vec<Point2D<f32>>)
where I: Iterator<Item = PathEvent> + 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<I>(&mut self, path_stream: I, path_points: &mut Vec<Point2D<f32>>)
where I: Iterator<Item = PathEvent> + 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<f32>) -> Vector2D<f32> {
Vector2D::new(-vector.y, vector.x)
fn compute_normal(prev: &Point2D<f32>, current: &Point2D<f32>, next: &Point2D<f32>)
-> Vector2D<f32> {
let vector = ((*current - *prev) + (*next - *current)).normalize();
Vector2D::new(vector.y, -vector.x)
}

View File

@ -48,12 +48,9 @@ impl<I> Iterator for SegmentIter<I> where I: Iterator<Item = PathEvent> {
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,
}))
}

View File

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

View File

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

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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));
}

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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;
}

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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));
}

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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;
}

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! 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;
}

View File

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

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-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;

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
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);
}

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
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;
}

View File

@ -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()));
}