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:
parent
87eb3038eb
commit
a84b7c7cbd
|
@ -364,6 +364,8 @@ class ThreeDRenderer extends Renderer {
|
||||||
|
|
||||||
camera: PerspectiveCamera;
|
camera: PerspectiveCamera;
|
||||||
|
|
||||||
|
needsStencil: boolean = false;
|
||||||
|
|
||||||
get isMulticolor(): boolean {
|
get isMulticolor(): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ import {BUILTIN_FONT_URI, ExpandedMeshData, GlyphStore, PathfinderFont, TextFram
|
||||||
import {computeStemDarkeningAmount, TextRun} from "./text";
|
import {computeStemDarkeningAmount, TextRun} from "./text";
|
||||||
import {assert, lerp, PathfinderError, unwrapNull, unwrapUndef} from "./utils";
|
import {assert, lerp, PathfinderError, unwrapNull, unwrapUndef} from "./utils";
|
||||||
import {DemoView, Timings} from "./view";
|
import {DemoView, Timings} from "./view";
|
||||||
import {AdaptiveMonochromeXCAAStrategy} from './xcaa-strategy';
|
import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy';
|
||||||
|
|
||||||
const STRING: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
const STRING: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ const MAX_RUNTIME: number = 3000;
|
||||||
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
||||||
none: NoAAStrategy,
|
none: NoAAStrategy,
|
||||||
ssaa: SSAAStrategy,
|
ssaa: SSAAStrategy,
|
||||||
xcaa: AdaptiveMonochromeXCAAStrategy,
|
xcaa: AdaptiveStencilMeshAAAStrategy,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface BenchmarkModeMap<T> {
|
interface BenchmarkModeMap<T> {
|
||||||
|
@ -57,7 +57,7 @@ type BenchmarkMode = 'text' | 'svg';
|
||||||
interface AntialiasingStrategyTable {
|
interface AntialiasingStrategyTable {
|
||||||
none: typeof NoAAStrategy;
|
none: typeof NoAAStrategy;
|
||||||
ssaa: typeof SSAAStrategy;
|
ssaa: typeof SSAAStrategy;
|
||||||
xcaa: typeof AdaptiveMonochromeXCAAStrategy;
|
xcaa: typeof AdaptiveStencilMeshAAAStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TestParameter {
|
interface TestParameter {
|
||||||
|
@ -423,9 +423,8 @@ class BenchmarkTextRenderer extends Renderer {
|
||||||
|
|
||||||
camera: OrthographicCamera;
|
camera: OrthographicCamera;
|
||||||
|
|
||||||
get isMulticolor(): boolean {
|
needsStencil: boolean = false;
|
||||||
return false;
|
isMulticolor: boolean = false;
|
||||||
}
|
|
||||||
|
|
||||||
get usesSTTransform(): boolean {
|
get usesSTTransform(): boolean {
|
||||||
return this.camera.usesSTTransform;
|
return this.camera.usesSTTransform;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// pathfinder/client/src/mesh-debugger.ts
|
// 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
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
@ -339,20 +339,9 @@ class MeshDebuggerView extends PathfinderView {
|
||||||
// Draw segments.
|
// Draw segments.
|
||||||
if (this.drawVertices) {
|
if (this.drawVertices) {
|
||||||
drawSegmentVertices(context,
|
drawSegmentVertices(context,
|
||||||
new Float32Array(meshes.segmentLines),
|
new Float32Array(meshes.stencilSegments),
|
||||||
new Float32Array(meshes.segmentLineNormals),
|
new Float32Array(meshes.stencilNormals),
|
||||||
meshes.segmentLineCount,
|
meshes.stencilSegmentCount,
|
||||||
[0, 1],
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
invScaleFactor,
|
|
||||||
this.drawControl,
|
|
||||||
this.drawNormals,
|
|
||||||
this.drawSegments);
|
|
||||||
drawSegmentVertices(context,
|
|
||||||
new Float32Array(meshes.segmentCurves),
|
|
||||||
new Float32Array(meshes.segmentCurveNormals),
|
|
||||||
meshes.segmentCurveCount,
|
|
||||||
[0, 2],
|
[0, 2],
|
||||||
1,
|
1,
|
||||||
3,
|
3,
|
||||||
|
@ -421,7 +410,7 @@ function drawSegmentVertices(context: CanvasRenderingContext2D,
|
||||||
drawSegments: boolean) {
|
drawSegments: boolean) {
|
||||||
for (let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex++) {
|
for (let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex++) {
|
||||||
const positionStartOffset = segmentSize * 2 * segmentIndex;
|
const positionStartOffset = segmentSize * 2 * segmentIndex;
|
||||||
const normalStartOffset = segmentSize * segmentIndex;
|
const normalStartOffset = segmentSize * 2 * segmentIndex;
|
||||||
|
|
||||||
const position0 =
|
const position0 =
|
||||||
glmatrix.vec2.clone([segments[positionStartOffset + endpointOffsets[0] * 2 + 0],
|
glmatrix.vec2.clone([segments[positionStartOffset + endpointOffsets[0] * 2 + 0],
|
||||||
|
@ -439,14 +428,21 @@ function drawSegmentVertices(context: CanvasRenderingContext2D,
|
||||||
controlPoint = null;
|
controlPoint = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const normal0 = normals[normalStartOffset + endpointOffsets[0]];
|
const normal0 =
|
||||||
const normal1 = normals[normalStartOffset + endpointOffsets[1]];
|
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;
|
let normalControlPoint: glmatrix.vec2 | null;
|
||||||
if (controlPointOffset != null)
|
if (controlPointOffset != null) {
|
||||||
normalControlPoint = normals[normalStartOffset + controlPointOffset];
|
normalControlPoint =
|
||||||
else
|
glmatrix.vec2.clone([normals[normalStartOffset + controlPointOffset * 2 + 0],
|
||||||
|
normals[normalStartOffset + controlPointOffset * 2 + 1]]);
|
||||||
|
} else {
|
||||||
normalControlPoint = null;
|
normalControlPoint = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (drawNormals) {
|
if (drawNormals) {
|
||||||
drawNormal(context, position0, normal0, invScaleFactor, 'edge');
|
drawNormal(context, position0, normal0, invScaleFactor, 'edge');
|
||||||
|
@ -528,26 +524,34 @@ function drawSegmentControlPoint(context: CanvasRenderingContext2D,
|
||||||
|
|
||||||
function drawNormal(context: CanvasRenderingContext2D,
|
function drawNormal(context: CanvasRenderingContext2D,
|
||||||
position: glmatrix.vec2,
|
position: glmatrix.vec2,
|
||||||
normalAngle: number,
|
normalVector: glmatrix.vec2,
|
||||||
invScaleFactor: number,
|
invScaleFactor: number,
|
||||||
normalType: NormalType) {
|
normalType: NormalType) {
|
||||||
const length = invScaleFactor * NORMAL_LENGTHS[normalType];
|
const length = invScaleFactor * NORMAL_LENGTHS[normalType];
|
||||||
const arrowheadLength = invScaleFactor * NORMAL_ARROWHEAD_LENGTH;
|
const arrowheadLength = invScaleFactor * NORMAL_ARROWHEAD_LENGTH;
|
||||||
const endpoint = glmatrix.vec2.clone([position[0] + length * Math.cos(normalAngle),
|
const endpoint = glmatrix.vec2.clone([position[0] + length * normalVector[0],
|
||||||
-position[1] + length * Math.sin(normalAngle)]);
|
-position[1] + length * -normalVector[1]]);
|
||||||
|
|
||||||
context.save();
|
context.save();
|
||||||
context.strokeStyle = NORMAL_STROKE_STYLES[normalType];
|
context.strokeStyle = NORMAL_STROKE_STYLES[normalType];
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.moveTo(position[0], -position[1]);
|
context.moveTo(position[0], -position[1]);
|
||||||
context.lineTo(endpoint[0], endpoint[1]);
|
context.lineTo(endpoint[0], endpoint[1]);
|
||||||
context.lineTo(endpoint[0] + arrowheadLength * Math.cos(NORMAL_ARROWHEAD_ANGLE + normalAngle),
|
context.lineTo(endpoint[0] + arrowheadLength *
|
||||||
endpoint[1] + arrowheadLength * Math.sin(NORMAL_ARROWHEAD_ANGLE + normalAngle));
|
(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.stroke();
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.moveTo(endpoint[0], endpoint[1]);
|
context.moveTo(endpoint[0], endpoint[1]);
|
||||||
context.lineTo(endpoint[0] + arrowheadLength * Math.cos(normalAngle - NORMAL_ARROWHEAD_ANGLE),
|
context.lineTo(endpoint[0] + arrowheadLength *
|
||||||
endpoint[1] + arrowheadLength * Math.sin(normalAngle - NORMAL_ARROWHEAD_ANGLE));
|
(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.stroke();
|
||||||
context.restore();
|
context.restore();
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,13 +78,6 @@ const B_QUAD_FIELD_COUNT: number = B_QUAD_SIZE / UINT32_SIZE;
|
||||||
const INDEX_SIZE: number = 4;
|
const INDEX_SIZE: number = 4;
|
||||||
const B_QUAD_VERTEX_POSITION_SIZE: number = 12 * 4;
|
const B_QUAD_VERTEX_POSITION_SIZE: number = 12 * 4;
|
||||||
const B_VERTEX_POSITION_SIZE: number = 4 * 2;
|
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> = {
|
const MESH_TYPES: Meshes<MeshBufferTypeDescriptor> = {
|
||||||
bBoxPathIDs: { type: 'Uint16', size: 1 },
|
bBoxPathIDs: { type: 'Uint16', size: 1 },
|
||||||
|
@ -92,12 +85,9 @@ const MESH_TYPES: Meshes<MeshBufferTypeDescriptor> = {
|
||||||
bQuadVertexInteriorIndices: { type: 'Uint32', size: 1 },
|
bQuadVertexInteriorIndices: { type: 'Uint32', size: 1 },
|
||||||
bQuadVertexPositionPathIDs: { type: 'Uint16', size: 6 },
|
bQuadVertexPositionPathIDs: { type: 'Uint16', size: 6 },
|
||||||
bQuadVertexPositions: { type: 'Float32', size: 12 },
|
bQuadVertexPositions: { type: 'Float32', size: 12 },
|
||||||
segmentCurveNormals: { type: 'Float32', size: 3 },
|
stencilNormals: { type: 'Float32', size: 6 },
|
||||||
segmentCurvePathIDs: { type: 'Uint16', size: 1 },
|
stencilSegmentPathIDs: { type: 'Uint16', size: 1 },
|
||||||
segmentCurves: { type: 'Float32', size: 6 },
|
stencilSegments: { type: 'Float32', size: 6 },
|
||||||
segmentLineNormals: { type: 'Float32', size: 2 },
|
|
||||||
segmentLinePathIDs: { type: 'Uint16', size: 1 },
|
|
||||||
segmentLines: { type: 'Float32', size: 4 },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const BUFFER_TYPES: Meshes<BufferType> = {
|
const BUFFER_TYPES: Meshes<BufferType> = {
|
||||||
|
@ -106,12 +96,9 @@ const BUFFER_TYPES: Meshes<BufferType> = {
|
||||||
bQuadVertexInteriorIndices: 'ELEMENT_ARRAY_BUFFER',
|
bQuadVertexInteriorIndices: 'ELEMENT_ARRAY_BUFFER',
|
||||||
bQuadVertexPositionPathIDs: 'ARRAY_BUFFER',
|
bQuadVertexPositionPathIDs: 'ARRAY_BUFFER',
|
||||||
bQuadVertexPositions: 'ARRAY_BUFFER',
|
bQuadVertexPositions: 'ARRAY_BUFFER',
|
||||||
segmentCurveNormals: 'ARRAY_BUFFER',
|
stencilNormals: 'ARRAY_BUFFER',
|
||||||
segmentCurvePathIDs: 'ARRAY_BUFFER',
|
stencilSegmentPathIDs: 'ARRAY_BUFFER',
|
||||||
segmentCurves: 'ARRAY_BUFFER',
|
stencilSegments: 'ARRAY_BUFFER',
|
||||||
segmentLineNormals: 'ARRAY_BUFFER',
|
|
||||||
segmentLinePathIDs: 'ARRAY_BUFFER',
|
|
||||||
segmentLines: 'ARRAY_BUFFER',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const EDGE_BUFFER_NAMES = ['UpperLine', 'UpperCurve', 'LowerLine', 'LowerCurve'];
|
const EDGE_BUFFER_NAMES = ['UpperLine', 'UpperCurve', 'LowerLine', 'LowerCurve'];
|
||||||
|
@ -125,10 +112,8 @@ const BUFFER_TYPE_FOURCCS: BufferTypeFourCCTable = {
|
||||||
bbox: 'bBoxes',
|
bbox: 'bBoxes',
|
||||||
bqii: 'bQuadVertexInteriorIndices',
|
bqii: 'bQuadVertexInteriorIndices',
|
||||||
bqvp: 'bQuadVertexPositions',
|
bqvp: 'bQuadVertexPositions',
|
||||||
scur: 'segmentCurves',
|
snor: 'stencilNormals',
|
||||||
slin: 'segmentLines',
|
sseg: 'stencilSegments',
|
||||||
sncu: 'segmentCurveNormals',
|
|
||||||
snli: 'segmentLineNormals',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Must match the FourCCs in
|
// Must match the FourCCs in
|
||||||
|
@ -137,31 +122,27 @@ const PATH_RANGE_TYPE_FOURCCS: PathRangeTypeFourCCTable = {
|
||||||
bbox: 'bBoxPathRanges',
|
bbox: 'bBoxPathRanges',
|
||||||
bqii: 'bQuadVertexInteriorIndexPathRanges',
|
bqii: 'bQuadVertexInteriorIndexPathRanges',
|
||||||
bqvp: 'bQuadVertexPositionPathRanges',
|
bqvp: 'bQuadVertexPositionPathRanges',
|
||||||
scur: 'segmentCurveRanges',
|
sseg: 'stencilSegmentPathRanges',
|
||||||
slin: 'segmentLineRanges',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const RANGE_TO_COUNT_TABLE: RangeToCountTable = {
|
const RANGE_TO_COUNT_TABLE: RangeToCountTable = {
|
||||||
bBoxPathRanges: 'bBoxCount',
|
bBoxPathRanges: 'bBoxCount',
|
||||||
bQuadVertexInteriorIndexPathRanges: 'bQuadVertexInteriorIndexCount',
|
bQuadVertexInteriorIndexPathRanges: 'bQuadVertexInteriorIndexCount',
|
||||||
bQuadVertexPositionPathRanges: 'bQuadVertexPositionCount',
|
bQuadVertexPositionPathRanges: 'bQuadVertexPositionCount',
|
||||||
segmentCurveRanges: 'segmentCurveCount',
|
stencilSegmentPathRanges: 'stencilSegmentCount',
|
||||||
segmentLineRanges: 'segmentLineCount',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const RANGE_TO_RANGE_BUFFER_TABLE: RangeToRangeBufferTable = {
|
const RANGE_TO_RANGE_BUFFER_TABLE: RangeToRangeBufferTable = {
|
||||||
bBoxPathRanges: 'bBoxPathIDs',
|
bBoxPathRanges: 'bBoxPathIDs',
|
||||||
bQuadVertexPositionPathRanges: 'bQuadVertexPositionPathIDs',
|
bQuadVertexPositionPathRanges: 'bQuadVertexPositionPathIDs',
|
||||||
segmentCurveRanges: 'segmentCurvePathIDs',
|
stencilSegmentPathRanges: 'stencilSegmentPathIDs',
|
||||||
segmentLineRanges: 'segmentLinePathIDs',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const RANGE_KEYS: Array<keyof PathRanges> = [
|
const RANGE_KEYS: Array<keyof PathRanges> = [
|
||||||
'bQuadVertexPositionPathRanges',
|
'bQuadVertexPositionPathRanges',
|
||||||
'bQuadVertexInteriorIndexPathRanges',
|
'bQuadVertexInteriorIndexPathRanges',
|
||||||
'bBoxPathRanges',
|
'bBoxPathRanges',
|
||||||
'segmentCurveRanges',
|
'stencilSegmentPathRanges',
|
||||||
'segmentLineRanges',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
type BufferType = 'ARRAY_BUFFER' | 'ELEMENT_ARRAY_BUFFER';
|
type BufferType = 'ARRAY_BUFFER' | 'ELEMENT_ARRAY_BUFFER';
|
||||||
|
@ -170,31 +151,26 @@ export interface Meshes<T> {
|
||||||
readonly bQuadVertexPositions: T;
|
readonly bQuadVertexPositions: T;
|
||||||
readonly bQuadVertexInteriorIndices: T;
|
readonly bQuadVertexInteriorIndices: T;
|
||||||
readonly bBoxes: T;
|
readonly bBoxes: T;
|
||||||
readonly segmentLines: T;
|
readonly stencilSegments: T;
|
||||||
readonly segmentCurves: T;
|
readonly stencilNormals: T;
|
||||||
readonly segmentLineNormals: T;
|
|
||||||
readonly segmentCurveNormals: T;
|
|
||||||
|
|
||||||
bQuadVertexPositionPathIDs: T;
|
bQuadVertexPositionPathIDs: T;
|
||||||
bBoxPathIDs: T;
|
bBoxPathIDs: T;
|
||||||
segmentLinePathIDs: T;
|
stencilSegmentPathIDs: T;
|
||||||
segmentCurvePathIDs: T;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MeshDataCounts {
|
interface MeshDataCounts {
|
||||||
readonly bQuadVertexPositionCount: number;
|
readonly bQuadVertexPositionCount: number;
|
||||||
readonly bQuadVertexInteriorIndexCount: number;
|
readonly bQuadVertexInteriorIndexCount: number;
|
||||||
readonly bBoxCount: number;
|
readonly bBoxCount: number;
|
||||||
readonly segmentLineCount: number;
|
readonly stencilSegmentCount: number;
|
||||||
readonly segmentCurveCount: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PathRanges {
|
interface PathRanges {
|
||||||
bQuadVertexPositionPathRanges: Range[];
|
bQuadVertexPositionPathRanges: Range[];
|
||||||
bQuadVertexInteriorIndexPathRanges: Range[];
|
bQuadVertexInteriorIndexPathRanges: Range[];
|
||||||
bBoxPathRanges: Range[];
|
bBoxPathRanges: Range[];
|
||||||
segmentCurveRanges: Range[];
|
stencilSegmentPathRanges: Range[];
|
||||||
segmentLineRanges: Range[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PathfinderMeshData implements Meshes<ArrayBuffer>, MeshDataCounts, PathRanges {
|
export class PathfinderMeshData implements Meshes<ArrayBuffer>, MeshDataCounts, PathRanges {
|
||||||
|
@ -203,27 +179,22 @@ export class PathfinderMeshData implements Meshes<ArrayBuffer>, MeshDataCounts,
|
||||||
readonly bBoxes: ArrayBuffer;
|
readonly bBoxes: ArrayBuffer;
|
||||||
readonly bBoxSigns: ArrayBuffer;
|
readonly bBoxSigns: ArrayBuffer;
|
||||||
readonly bBoxIndices: ArrayBuffer;
|
readonly bBoxIndices: ArrayBuffer;
|
||||||
readonly segmentLines: ArrayBuffer;
|
readonly stencilSegments: ArrayBuffer;
|
||||||
readonly segmentCurves: ArrayBuffer;
|
readonly stencilNormals: ArrayBuffer;
|
||||||
readonly segmentLineNormals: ArrayBuffer;
|
|
||||||
readonly segmentCurveNormals: ArrayBuffer;
|
|
||||||
|
|
||||||
readonly bQuadVertexPositionCount: number;
|
readonly bQuadVertexPositionCount: number;
|
||||||
readonly bQuadVertexInteriorIndexCount: number;
|
readonly bQuadVertexInteriorIndexCount: number;
|
||||||
readonly bBoxCount: number;
|
readonly bBoxCount: number;
|
||||||
readonly segmentLineCount: number;
|
readonly stencilSegmentCount: number;
|
||||||
readonly segmentCurveCount: number;
|
|
||||||
|
|
||||||
bQuadVertexPositionPathIDs: ArrayBuffer;
|
bQuadVertexPositionPathIDs: ArrayBuffer;
|
||||||
bBoxPathIDs: ArrayBuffer;
|
bBoxPathIDs: ArrayBuffer;
|
||||||
segmentCurvePathIDs: ArrayBuffer;
|
stencilSegmentPathIDs: ArrayBuffer;
|
||||||
segmentLinePathIDs: ArrayBuffer;
|
|
||||||
|
|
||||||
bQuadVertexPositionPathRanges: Range[];
|
bQuadVertexPositionPathRanges: Range[];
|
||||||
bQuadVertexInteriorIndexPathRanges: Range[];
|
bQuadVertexInteriorIndexPathRanges: Range[];
|
||||||
bBoxPathRanges: Range[];
|
bBoxPathRanges: Range[];
|
||||||
segmentCurveRanges: Range[];
|
stencilSegmentPathRanges: Range[];
|
||||||
segmentLineRanges: Range[];
|
|
||||||
|
|
||||||
constructor(meshes: ArrayBuffer | Meshes<ArrayBuffer>, optionalRanges?: PathRanges) {
|
constructor(meshes: ArrayBuffer | Meshes<ArrayBuffer>, optionalRanges?: PathRanges) {
|
||||||
if (meshes instanceof ArrayBuffer) {
|
if (meshes instanceof ArrayBuffer) {
|
||||||
|
@ -261,8 +232,7 @@ export class PathfinderMeshData implements Meshes<ArrayBuffer>, MeshDataCounts,
|
||||||
this.bQuadVertexInteriorIndexCount = this.bQuadVertexInteriorIndices.byteLength /
|
this.bQuadVertexInteriorIndexCount = this.bQuadVertexInteriorIndices.byteLength /
|
||||||
INDEX_SIZE;
|
INDEX_SIZE;
|
||||||
this.bBoxCount = this.bBoxes.byteLength / (FLOAT32_SIZE * 6);
|
this.bBoxCount = this.bBoxes.byteLength / (FLOAT32_SIZE * 6);
|
||||||
this.segmentCurveCount = this.segmentCurves.byteLength / SEGMENT_CURVE_SIZE;
|
this.stencilSegmentCount = this.stencilSegments.byteLength / (FLOAT32_SIZE * 6);
|
||||||
this.segmentLineCount = this.segmentLines.byteLength / SEGMENT_LINE_SIZE;
|
|
||||||
|
|
||||||
this.rebuildPathIDBuffers();
|
this.rebuildPathIDBuffers();
|
||||||
}
|
}
|
||||||
|
@ -339,16 +309,8 @@ export class PathfinderMeshData implements Meshes<ArrayBuffer>, MeshDataCounts,
|
||||||
const lastBBoxIndex = bBoxVertexCopyResult.originalEndIndex;
|
const lastBBoxIndex = bBoxVertexCopyResult.originalEndIndex;
|
||||||
|
|
||||||
// Copy over segments.
|
// Copy over segments.
|
||||||
copySegments(['segmentLines', 'segmentLineNormals'],
|
copySegments(['stencilSegments', 'stencilNormals'],
|
||||||
'segmentLineRanges',
|
'stencilSegmentPathRanges',
|
||||||
expandedArrays,
|
|
||||||
expandedRanges,
|
|
||||||
originalBuffers,
|
|
||||||
originalRanges,
|
|
||||||
expandedPathID,
|
|
||||||
originalPathID);
|
|
||||||
copySegments(['segmentCurves', 'segmentCurveNormals'],
|
|
||||||
'segmentCurveRanges',
|
|
||||||
expandedArrays,
|
expandedArrays,
|
||||||
expandedRanges,
|
expandedRanges,
|
||||||
originalBuffers,
|
originalBuffers,
|
||||||
|
@ -428,18 +390,14 @@ export class PathfinderMeshBuffers implements Meshes<WebGLBuffer>, PathRanges {
|
||||||
readonly bBoxSigns: WebGLBuffer;
|
readonly bBoxSigns: WebGLBuffer;
|
||||||
readonly bBoxIndices: WebGLBuffer;
|
readonly bBoxIndices: WebGLBuffer;
|
||||||
readonly bBoxPathIDs: WebGLBuffer;
|
readonly bBoxPathIDs: WebGLBuffer;
|
||||||
readonly segmentLines: WebGLBuffer;
|
readonly stencilSegments: WebGLBuffer;
|
||||||
readonly segmentCurves: WebGLBuffer;
|
readonly stencilSegmentPathIDs: WebGLBuffer;
|
||||||
readonly segmentLinePathIDs: WebGLBuffer;
|
readonly stencilNormals: WebGLBuffer;
|
||||||
readonly segmentCurvePathIDs: WebGLBuffer;
|
|
||||||
readonly segmentLineNormals: WebGLBuffer;
|
|
||||||
readonly segmentCurveNormals: WebGLBuffer;
|
|
||||||
|
|
||||||
readonly bQuadVertexPositionPathRanges: Range[];
|
readonly bQuadVertexPositionPathRanges: Range[];
|
||||||
readonly bQuadVertexInteriorIndexPathRanges: Range[];
|
readonly bQuadVertexInteriorIndexPathRanges: Range[];
|
||||||
readonly bBoxPathRanges: Range[];
|
readonly bBoxPathRanges: Range[];
|
||||||
readonly segmentCurveRanges: Range[];
|
readonly stencilSegmentPathRanges: Range[];
|
||||||
readonly segmentLineRanges: Range[];
|
|
||||||
|
|
||||||
constructor(gl: WebGLRenderingContext, meshData: PathfinderMeshData) {
|
constructor(gl: WebGLRenderingContext, meshData: PathfinderMeshData) {
|
||||||
for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof Meshes<void>>) {
|
for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof Meshes<void>>) {
|
||||||
|
|
|
@ -30,7 +30,7 @@ import {Hint} from "./text";
|
||||||
import {PathfinderFont, TextFrame, TextRun} from "./text";
|
import {PathfinderFont, TextFrame, TextRun} from "./text";
|
||||||
import {unwrapNull} from "./utils";
|
import {unwrapNull} from "./utils";
|
||||||
import {DemoView} from "./view";
|
import {DemoView} from "./view";
|
||||||
import {AdaptiveMonochromeXCAAStrategy} from './xcaa-strategy';
|
import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy';
|
||||||
|
|
||||||
const FONT: string = 'open-sans';
|
const FONT: string = 'open-sans';
|
||||||
const TEXT_COLOR: number[] = [0, 0, 0, 255];
|
const TEXT_COLOR: number[] = [0, 0, 0, 255];
|
||||||
|
@ -38,7 +38,7 @@ const TEXT_COLOR: number[] = [0, 0, 0, 255];
|
||||||
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
||||||
none: NoAAStrategy,
|
none: NoAAStrategy,
|
||||||
ssaa: SSAAStrategy,
|
ssaa: SSAAStrategy,
|
||||||
xcaa: AdaptiveMonochromeXCAAStrategy,
|
xcaa: AdaptiveStencilMeshAAAStrategy,
|
||||||
};
|
};
|
||||||
|
|
||||||
const RENDER_REFERENCE_URIS: PerTestType<string> = {
|
const RENDER_REFERENCE_URIS: PerTestType<string> = {
|
||||||
|
@ -91,7 +91,7 @@ type ReferenceRenderer = 'core-graphics' | 'freetype';
|
||||||
interface AntialiasingStrategyTable {
|
interface AntialiasingStrategyTable {
|
||||||
none: typeof NoAAStrategy;
|
none: typeof NoAAStrategy;
|
||||||
ssaa: typeof SSAAStrategy;
|
ssaa: typeof SSAAStrategy;
|
||||||
xcaa: typeof AdaptiveMonochromeXCAAStrategy;
|
xcaa: typeof AdaptiveStencilMeshAAAStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
class ReferenceTestAppController extends DemoAppController<ReferenceTestView> {
|
||||||
|
@ -659,6 +659,9 @@ class ReferenceTestTextRenderer extends Renderer {
|
||||||
renderContext: ReferenceTestView;
|
renderContext: ReferenceTestView;
|
||||||
camera: OrthographicCamera;
|
camera: OrthographicCamera;
|
||||||
|
|
||||||
|
needsStencil: boolean = false;
|
||||||
|
isMulticolor: boolean = false;
|
||||||
|
|
||||||
get usesSTTransform(): boolean {
|
get usesSTTransform(): boolean {
|
||||||
return this.camera.usesSTTransform;
|
return this.camera.usesSTTransform;
|
||||||
}
|
}
|
||||||
|
@ -667,10 +670,6 @@ class ReferenceTestTextRenderer extends Renderer {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isMulticolor(): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get bgColor(): glmatrix.vec4 {
|
get bgColor(): glmatrix.vec4 {
|
||||||
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
|
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ export abstract class Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract get isMulticolor(): boolean;
|
abstract get isMulticolor(): boolean;
|
||||||
|
abstract get needsStencil(): boolean;
|
||||||
|
|
||||||
abstract get destFramebuffer(): WebGLFramebuffer | null;
|
abstract get destFramebuffer(): WebGLFramebuffer | null;
|
||||||
abstract get destAllocatedSize(): glmatrix.vec2;
|
abstract get destAllocatedSize(): glmatrix.vec2;
|
||||||
|
|
|
@ -21,11 +21,9 @@ export interface ShaderMap<T> {
|
||||||
directInterior: T;
|
directInterior: T;
|
||||||
direct3DCurve: T;
|
direct3DCurve: T;
|
||||||
direct3DInterior: T;
|
direct3DInterior: T;
|
||||||
ecaaLine: T;
|
|
||||||
ecaaCurve: T;
|
|
||||||
ecaaTransformedCurve: T;
|
|
||||||
mcaa: T;
|
mcaa: T;
|
||||||
ssaaSubpixelResolve: T;
|
ssaaSubpixelResolve: T;
|
||||||
|
stencilAAA: T;
|
||||||
xcaaMonoResolve: T;
|
xcaaMonoResolve: T;
|
||||||
xcaaMonoSubpixelResolve: T;
|
xcaaMonoSubpixelResolve: T;
|
||||||
}
|
}
|
||||||
|
@ -47,9 +45,7 @@ export const SHADER_NAMES: Array<keyof ShaderMap<void>> = [
|
||||||
'direct3DInterior',
|
'direct3DInterior',
|
||||||
'ssaaSubpixelResolve',
|
'ssaaSubpixelResolve',
|
||||||
'mcaa',
|
'mcaa',
|
||||||
'ecaaLine',
|
'stencilAAA',
|
||||||
'ecaaCurve',
|
|
||||||
'ecaaTransformedCurve',
|
|
||||||
'xcaaMonoResolve',
|
'xcaaMonoResolve',
|
||||||
'xcaaMonoSubpixelResolve',
|
'xcaaMonoSubpixelResolve',
|
||||||
'demo3DDistantGlyph',
|
'demo3DDistantGlyph',
|
||||||
|
@ -93,18 +89,6 @@ const SHADER_URLS: ShaderMap<ShaderProgramURLs> = {
|
||||||
fragment: "/glsl/gles2/direct-interior.fs.glsl",
|
fragment: "/glsl/gles2/direct-interior.fs.glsl",
|
||||||
vertex: "/glsl/gles2/direct-interior.vs.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: {
|
mcaa: {
|
||||||
fragment: "/glsl/gles2/mcaa.fs.glsl",
|
fragment: "/glsl/gles2/mcaa.fs.glsl",
|
||||||
vertex: "/glsl/gles2/mcaa.vs.glsl",
|
vertex: "/glsl/gles2/mcaa.vs.glsl",
|
||||||
|
@ -113,6 +97,10 @@ const SHADER_URLS: ShaderMap<ShaderProgramURLs> = {
|
||||||
fragment: "/glsl/gles2/ssaa-subpixel-resolve.fs.glsl",
|
fragment: "/glsl/gles2/ssaa-subpixel-resolve.fs.glsl",
|
||||||
vertex: "/glsl/gles2/blit.vs.glsl",
|
vertex: "/glsl/gles2/blit.vs.glsl",
|
||||||
},
|
},
|
||||||
|
stencilAAA: {
|
||||||
|
fragment: "/glsl/gles2/stencil-aaa.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/stencil-aaa.vs.glsl",
|
||||||
|
},
|
||||||
xcaaMonoResolve: {
|
xcaaMonoResolve: {
|
||||||
fragment: "/glsl/gles2/xcaa-mono-resolve.fs.glsl",
|
fragment: "/glsl/gles2/xcaa-mono-resolve.fs.glsl",
|
||||||
vertex: "/glsl/gles2/xcaa-mono-resolve.vs.glsl",
|
vertex: "/glsl/gles2/xcaa-mono-resolve.vs.glsl",
|
||||||
|
|
|
@ -95,6 +95,8 @@ class SVGDemoView extends DemoView {
|
||||||
class SVGDemoRenderer extends SVGRenderer {
|
class SVGDemoRenderer extends SVGRenderer {
|
||||||
renderContext: SVGDemoView;
|
renderContext: SVGDemoView;
|
||||||
|
|
||||||
|
needsStencil: boolean = false;
|
||||||
|
|
||||||
protected get loader(): SVGLoader {
|
protected get loader(): SVGLoader {
|
||||||
return this.renderContext.appController.loader;
|
return this.renderContext.appController.loader;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,8 @@ export abstract class SVGRenderer extends Renderer {
|
||||||
|
|
||||||
camera: OrthographicCamera;
|
camera: OrthographicCamera;
|
||||||
|
|
||||||
|
needsStencil: boolean = false;
|
||||||
|
|
||||||
private options: SVGRendererOptions;
|
private options: SVGRendererOptions;
|
||||||
|
|
||||||
get isMulticolor(): boolean {
|
get isMulticolor(): boolean {
|
||||||
|
|
|
@ -33,7 +33,6 @@ import {TextRenderContext, TextRenderer} from './text-renderer';
|
||||||
import {assert, expectNotNull, panic, PathfinderError, scaleRect, UINT32_SIZE} from './utils';
|
import {assert, expectNotNull, panic, PathfinderError, scaleRect, UINT32_SIZE} from './utils';
|
||||||
import {unwrapNull} from './utils';
|
import {unwrapNull} from './utils';
|
||||||
import {DemoView, RenderContext, Timings, TIMINGS} from './view';
|
import {DemoView, RenderContext, Timings, TIMINGS} from './view';
|
||||||
import {AdaptiveMonochromeXCAAStrategy} from './xcaa-strategy';
|
|
||||||
|
|
||||||
const DEFAULT_TEXT: string =
|
const DEFAULT_TEXT: string =
|
||||||
`’Twas brillig, and the slithy toves
|
`’Twas brillig, and the slithy toves
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// pathfinder/client/src/text-renderer.ts
|
// 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
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
// 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 {ShaderMap} from './shader-loader';
|
||||||
import SSAAStrategy from './ssaa-strategy';
|
import SSAAStrategy from './ssaa-strategy';
|
||||||
import {calculatePixelRectForGlyph, computeStemDarkeningAmount, GlyphStore, Hint} from "./text";
|
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 {unwrapNull} from './utils';
|
||||||
import {RenderContext, Timings} from "./view";
|
import {RenderContext, Timings} from "./view";
|
||||||
import {AdaptiveMonochromeXCAAStrategy} from './xcaa-strategy';
|
import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy';
|
||||||
|
|
||||||
interface AntialiasingStrategyTable {
|
interface AntialiasingStrategyTable {
|
||||||
none: typeof NoAAStrategy;
|
none: typeof NoAAStrategy;
|
||||||
ssaa: typeof SSAAStrategy;
|
ssaa: typeof SSAAStrategy;
|
||||||
xcaa: typeof AdaptiveMonochromeXCAAStrategy;
|
xcaa: typeof AdaptiveStencilMeshAAAStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SQRT_1_2: number = 1.0 / Math.sqrt(2.0);
|
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 = {
|
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
||||||
none: NoAAStrategy,
|
none: NoAAStrategy,
|
||||||
ssaa: SSAAStrategy,
|
ssaa: SSAAStrategy,
|
||||||
xcaa: AdaptiveMonochromeXCAAStrategy,
|
xcaa: AdaptiveStencilMeshAAAStrategy,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface TextRenderContext extends RenderContext {
|
export interface TextRenderContext extends RenderContext {
|
||||||
|
@ -68,6 +69,10 @@ export abstract class TextRenderer extends Renderer {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get needsStencil(): boolean {
|
||||||
|
return this.renderContext.fontSize <= MAX_STEM_DARKENING_PIXELS_PER_EM;
|
||||||
|
}
|
||||||
|
|
||||||
get usesSTTransform(): boolean {
|
get usesSTTransform(): boolean {
|
||||||
return this.camera.usesSTTransform;
|
return this.camera.usesSTTransform;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]);
|
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.
|
// 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";
|
const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font";
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,6 @@ const PATCH_VERTICES: Float32Array = new Float32Array([
|
||||||
|
|
||||||
const MCAA_PATCH_INDICES: Uint8Array = new Uint8Array([0, 1, 2, 1, 3, 2]);
|
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 type TransformType = 'dilation' | 'affine' | '3d';
|
||||||
|
|
||||||
export abstract class XCAAStrategy extends AntialiasingStrategy {
|
export abstract class XCAAStrategy extends AntialiasingStrategy {
|
||||||
|
@ -175,7 +173,7 @@ export abstract class XCAAStrategy extends AntialiasingStrategy {
|
||||||
if (resolveProgram == null)
|
if (resolveProgram == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Set state for ECAA resolve.
|
// Set state for XCAA resolve.
|
||||||
const usedSize = renderer.destUsedSize;
|
const usedSize = renderer.destUsedSize;
|
||||||
gl.scissor(0, 0, usedSize[0], usedSize[1]);
|
gl.scissor(0, 0, usedSize[0], usedSize[1]);
|
||||||
gl.enable(gl.SCISSOR_TEST);
|
gl.enable(gl.SCISSOR_TEST);
|
||||||
|
@ -618,80 +616,53 @@ export class MCAAStrategy extends XCAAStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ECAAStrategy extends XCAAStrategy {
|
export class StencilAAAStrategy extends XCAAStrategy {
|
||||||
protected get patchIndices(): Uint8Array {
|
directRenderingMode: DirectRenderingMode = 'none';
|
||||||
return ECAA_CURVE_PATCH_INDICES;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get lineShaderProgramNames(): Array<keyof ShaderMap<void>> {
|
protected transformType: TransformType = 'affine';
|
||||||
return ['ecaaLine'];
|
protected patchIndices: Uint8Array = MCAA_PATCH_INDICES;
|
||||||
}
|
protected mightUseAAFramebuffer: boolean = true;
|
||||||
|
|
||||||
protected get curveShaderProgramNames(): Array<keyof ShaderMap<void>> {
|
private vao: WebGLVertexArrayObject;
|
||||||
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';
|
|
||||||
}
|
|
||||||
|
|
||||||
attachMeshes(renderer: Renderer): void {
|
attachMeshes(renderer: Renderer): void {
|
||||||
super.attachMeshes(renderer);
|
super.attachMeshes(renderer);
|
||||||
|
this.createVAO(renderer);
|
||||||
this.createLineVAOs(renderer);
|
|
||||||
this.createCurveVAOs(renderer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
antialiasObject(renderer: Renderer, objectIndex: number): void {
|
antialiasObject(renderer: Renderer, objectIndex: number): void {
|
||||||
super.antialiasObject(renderer, objectIndex);
|
super.antialiasObject(renderer, objectIndex);
|
||||||
|
|
||||||
|
const renderContext = renderer.renderContext;
|
||||||
|
const gl = renderContext.gl;
|
||||||
|
|
||||||
|
if (renderer.meshData == null)
|
||||||
|
return;
|
||||||
|
|
||||||
// Antialias.
|
// Antialias.
|
||||||
const shaderPrograms = renderer.renderContext.shaderPrograms;
|
const shaderPrograms = renderer.renderContext.shaderPrograms;
|
||||||
this.setAAState(renderer);
|
this.setAAState(renderer);
|
||||||
this.setBlendModeForAA(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 program = renderContext.shaderPrograms.stencilAAA;
|
||||||
const gl = renderContext.gl;
|
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;
|
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 {
|
protected clearForAA(renderer: Renderer): void {
|
||||||
const renderContext = renderer.renderContext;
|
const renderContext = renderer.renderContext;
|
||||||
const gl = renderContext.gl;
|
const gl = renderContext.gl;
|
||||||
|
@ -701,15 +672,26 @@ export class ECAAStrategy extends XCAAStrategy {
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
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 {
|
protected setAADepthState(renderer: Renderer): void {
|
||||||
const renderContext = renderer.renderContext;
|
const renderContext = renderer.renderContext;
|
||||||
const gl = renderContext.gl;
|
const gl = renderContext.gl;
|
||||||
|
|
||||||
gl.disable(gl.DEPTH_TEST);
|
gl.disable(gl.DEPTH_TEST);
|
||||||
|
gl.disable(gl.CULL_FACE);
|
||||||
|
}
|
||||||
|
|
||||||
gl.frontFace(gl.CCW);
|
protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number):
|
||||||
gl.cullFace(gl.BACK);
|
void {
|
||||||
gl.enable(gl.CULL_FACE);
|
super.setAAUniforms(renderer, uniforms, objectIndex);
|
||||||
|
renderer.setEmboldenAmountUniform(objectIndex, uniforms);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected clearForResolve(renderer: Renderer): void {
|
protected clearForResolve(renderer: Renderer): void {
|
||||||
|
@ -720,76 +702,77 @@ export class ECAAStrategy extends XCAAStrategy {
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected antialiasLinesOfObjectWithProgram(renderer: Renderer,
|
private createVAO(renderer: Renderer): void {
|
||||||
objectIndex: number,
|
if (renderer.meshes == null || renderer.meshData == null)
|
||||||
programName: keyof ShaderMap<void>):
|
|
||||||
void {
|
|
||||||
if (renderer.meshData == null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const renderContext = renderer.renderContext;
|
const renderContext = renderer.renderContext;
|
||||||
const gl = renderContext.gl;
|
const gl = renderContext.gl;
|
||||||
|
|
||||||
const pathRange = renderer.pathRangeForObject(objectIndex);
|
const program = renderContext.shaderPrograms.stencilAAA;
|
||||||
const meshIndex = renderer.meshIndexForObject(objectIndex);
|
const attributes = program.attributes;
|
||||||
|
|
||||||
const lineProgram = renderContext.shaderPrograms[programName];
|
this.vao = renderContext.vertexArrayObjectExt.createVertexArrayOES();
|
||||||
gl.useProgram(lineProgram.program);
|
renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.vao);
|
||||||
const uniforms = lineProgram.uniforms;
|
|
||||||
this.setAAUniforms(renderer, uniforms, objectIndex);
|
|
||||||
|
|
||||||
const vao = this.lineVAOs[programName];
|
const vertexPositionsBuffer = renderer.meshes[0].stencilSegments;
|
||||||
renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao);
|
const vertexNormalsBuffer = renderer.meshes[0].stencilNormals;
|
||||||
|
const pathIDsBuffer = renderer.meshes[0].stencilSegmentPathIDs;
|
||||||
|
|
||||||
// FIXME(pcwalton): Only render the appropriate instances.
|
gl.useProgram(program.program);
|
||||||
const count = renderer.meshData[meshIndex].segmentLineCount;
|
gl.bindBuffer(gl.ARRAY_BUFFER, renderContext.quadPositionsBuffer);
|
||||||
renderContext.instancedArraysExt
|
gl.vertexAttribPointer(attributes.aTessCoord, 2, gl.FLOAT, false, 0, 0);
|
||||||
.drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count);
|
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,
|
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aFromPosition, 1);
|
||||||
objectIndex: number,
|
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aCtrlPosition, 1);
|
||||||
stProgram: keyof ShaderMap<void>,
|
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aToPosition, 1);
|
||||||
transformedProgram: keyof ShaderMap<void>):
|
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aFromNormal, 1);
|
||||||
void {
|
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aCtrlNormal, 1);
|
||||||
if (renderer.usesSTTransform) {
|
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aToNormal, 1);
|
||||||
this.antialiasCurvesOfObjectWithProgram(renderer, objectIndex, stProgram, 0);
|
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.antialiasCurvesOfObjectWithProgram(renderer, objectIndex, transformedProgram, 0);
|
// TODO(pcwalton): Normals.
|
||||||
this.antialiasCurvesOfObjectWithProgram(renderer, objectIndex, transformedProgram, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private antialiasCurvesOfObjectWithProgram(renderer: Renderer,
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer);
|
||||||
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);
|
|
||||||
|
|
||||||
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
|
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
|
||||||
}
|
}
|
||||||
|
@ -802,166 +785,15 @@ export class ECAAStrategy extends XCAAStrategy {
|
||||||
gl.blendFunc(gl.ONE, gl.ONE);
|
gl.blendFunc(gl.ONE, gl.ONE);
|
||||||
gl.enable(gl.BLEND);
|
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.
|
/// FIXME(pcwalton): Share textures and FBOs between the two strategies.
|
||||||
export class AdaptiveMonochromeXCAAStrategy implements AntialiasingStrategy {
|
export class AdaptiveStencilMeshAAAStrategy implements AntialiasingStrategy {
|
||||||
private mcaaStrategy: MCAAStrategy;
|
private meshStrategy: MCAAStrategy;
|
||||||
private ecaaStrategy: ECAAStrategy;
|
private stencilStrategy: StencilAAAStrategy;
|
||||||
|
|
||||||
get directRenderingMode(): DirectRenderingMode {
|
get directRenderingMode(): DirectRenderingMode {
|
||||||
return 'none';
|
return 'none';
|
||||||
|
@ -972,27 +804,27 @@ export class AdaptiveMonochromeXCAAStrategy implements AntialiasingStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(level: number, subpixelAA: SubpixelAAType) {
|
constructor(level: number, subpixelAA: SubpixelAAType) {
|
||||||
this.mcaaStrategy = new MCAAStrategy(level, subpixelAA);
|
this.meshStrategy = new MCAAStrategy(level, subpixelAA);
|
||||||
this.ecaaStrategy = new ECAAStrategy(level, subpixelAA);
|
this.stencilStrategy = new StencilAAAStrategy(level, subpixelAA);
|
||||||
}
|
}
|
||||||
|
|
||||||
init(renderer: Renderer): void {
|
init(renderer: Renderer): void {
|
||||||
this.mcaaStrategy.init(renderer);
|
this.meshStrategy.init(renderer);
|
||||||
this.ecaaStrategy.init(renderer);
|
this.stencilStrategy.init(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
attachMeshes(renderer: Renderer): void {
|
attachMeshes(renderer: Renderer): void {
|
||||||
this.mcaaStrategy.attachMeshes(renderer);
|
this.meshStrategy.attachMeshes(renderer);
|
||||||
this.ecaaStrategy.attachMeshes(renderer);
|
this.stencilStrategy.attachMeshes(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
setFramebufferSize(renderer: Renderer): void {
|
setFramebufferSize(renderer: Renderer): void {
|
||||||
this.mcaaStrategy.setFramebufferSize(renderer);
|
this.meshStrategy.setFramebufferSize(renderer);
|
||||||
this.ecaaStrategy.setFramebufferSize(renderer);
|
this.stencilStrategy.setFramebufferSize(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
get transform(): glmatrix.mat4 {
|
get transform(): glmatrix.mat4 {
|
||||||
return this.mcaaStrategy.transform;
|
return this.meshStrategy.transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareForRendering(renderer: Renderer): void {
|
prepareForRendering(renderer: Renderer): void {
|
||||||
|
@ -1032,12 +864,7 @@ export class AdaptiveMonochromeXCAAStrategy implements AntialiasingStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAppropriateStrategy(renderer: Renderer): AntialiasingStrategy {
|
private getAppropriateStrategy(renderer: Renderer): AntialiasingStrategy {
|
||||||
if (glmatrix.vec2.equals(renderer.emboldenAmount, [0.0, 0.0]) &&
|
return renderer.needsStencil ? this.stencilStrategy : this.meshStrategy;
|
||||||
renderer.usesSTTransform) {
|
|
||||||
return this.mcaaStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.ecaaStrategy;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// pathfinder/demo/server/main.rs
|
// 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
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
// 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.
|
// Partition the decoded glyph outlines.
|
||||||
let mut library = MeshLibrary::new();
|
let mut library = MeshLibrary::new();
|
||||||
for (stored_path_index, path_descriptor) in path_descriptors.iter().enumerate() {
|
for (stored_path_index, path_descriptor) in path_descriptors.iter().enumerate() {
|
||||||
library.push_segments((path_descriptor.path_index + 1) as u16,
|
library.push_stencil_segments((path_descriptor.path_index + 1) as u16,
|
||||||
PathIter::new(paths[stored_path_index].iter().cloned()));
|
PathIter::new(paths[stored_path_index].iter().cloned()));
|
||||||
library.push_normals((path_descriptor.path_index + 1) as u16,
|
library.push_stencil_normals((path_descriptor.path_index + 1) as u16,
|
||||||
PathIter::new(paths[stored_path_index].iter().cloned()));
|
paths[stored_path_index].iter().cloned());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut partitioner = Partitioner::new(library);
|
let mut partitioner = Partitioner::new(library);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// pathfinder/partitioner/src/lib.rs
|
// 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
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
// 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>,
|
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)]
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub(crate) enum BVertexKind {
|
pub(crate) enum BVertexKind {
|
||||||
|
|
|
@ -13,7 +13,6 @@ use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
use euclid::approxeq::ApproxEq;
|
use euclid::approxeq::ApproxEq;
|
||||||
use euclid::{Point2D, Rect, Size2D, Vector2D};
|
use euclid::{Point2D, Rect, Size2D, Vector2D};
|
||||||
use lyon_path::PathEvent;
|
use lyon_path::PathEvent;
|
||||||
use lyon_path::iterator::PathIterator;
|
|
||||||
use pathfinder_path_utils::normals::PathNormals;
|
use pathfinder_path_utils::normals::PathNormals;
|
||||||
use pathfinder_path_utils::segments::{self, SegmentIter};
|
use pathfinder_path_utils::segments::{self, SegmentIter};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
@ -34,8 +33,8 @@ pub struct MeshLibrary {
|
||||||
pub b_vertex_positions: Vec<Point2D<f32>>,
|
pub b_vertex_positions: Vec<Point2D<f32>>,
|
||||||
pub b_vertex_loop_blinn_data: Vec<BVertexLoopBlinnData>,
|
pub b_vertex_loop_blinn_data: Vec<BVertexLoopBlinnData>,
|
||||||
pub b_boxes: Vec<BBox>,
|
pub b_boxes: Vec<BBox>,
|
||||||
pub segments: MeshLibrarySegments,
|
pub stencil_segments: Vec<StencilSegment>,
|
||||||
pub segment_normals: MeshLibrarySegmentNormals,
|
pub stencil_normals: Vec<StencilNormals>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MeshLibrary {
|
impl MeshLibrary {
|
||||||
|
@ -49,8 +48,8 @@ impl MeshLibrary {
|
||||||
b_vertex_positions: vec![],
|
b_vertex_positions: vec![],
|
||||||
b_vertex_loop_blinn_data: vec![],
|
b_vertex_loop_blinn_data: vec![],
|
||||||
b_boxes: vec![],
|
b_boxes: vec![],
|
||||||
segments: MeshLibrarySegments::new(),
|
stencil_segments: vec![],
|
||||||
segment_normals: MeshLibrarySegmentNormals::new(),
|
stencil_normals: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,8 +61,8 @@ impl MeshLibrary {
|
||||||
self.b_vertex_positions.clear();
|
self.b_vertex_positions.clear();
|
||||||
self.b_vertex_loop_blinn_data.clear();
|
self.b_vertex_loop_blinn_data.clear();
|
||||||
self.b_boxes.clear();
|
self.b_boxes.clear();
|
||||||
self.segments.clear();
|
self.stencil_segments.clear();
|
||||||
self.segment_normals.clear();
|
self.stencil_normals.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ensure_path_ranges(&mut self, path_id: u16) -> &mut PathRanges {
|
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)
|
pub fn push_stencil_segments<I>(&mut self, path_id: u16, stream: I)
|
||||||
where I: Iterator<Item = PathEvent> {
|
where I: Iterator<Item = PathEvent> {
|
||||||
let first_line_index = self.segments.lines.len() as u32;
|
let first_segment_index = self.stencil_segments.len() as u32;
|
||||||
let first_curve_index = self.segments.curves.len() as u32;
|
|
||||||
|
|
||||||
let segment_iter = SegmentIter::new(stream);
|
let segment_iter = SegmentIter::new(stream);
|
||||||
for segment in segment_iter {
|
for segment in segment_iter {
|
||||||
match segment {
|
match segment {
|
||||||
segments::Segment::Line(line_segment) => {
|
segments::Segment::Line(line_segment) => {
|
||||||
self.segments.lines.push(LineSegment {
|
self.stencil_segments.push(StencilSegment {
|
||||||
endpoint_0: line_segment.from,
|
from: line_segment.from,
|
||||||
endpoint_1: line_segment.to,
|
ctrl: line_segment.from.lerp(line_segment.to, 0.5),
|
||||||
|
to: line_segment.to,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
segments::Segment::Quadratic(curve_segment) => {
|
segments::Segment::Quadratic(quadratic_segment) => {
|
||||||
self.segments.curves.push(CurveSegment {
|
self.stencil_segments.push(StencilSegment {
|
||||||
endpoint_0: curve_segment.from,
|
from: quadratic_segment.from,
|
||||||
control_point: curve_segment.ctrl,
|
ctrl: quadratic_segment.ctrl,
|
||||||
endpoint_1: curve_segment.to,
|
to: quadratic_segment.to,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
segments::Segment::Cubic(..) => {
|
||||||
|
panic!("push_stencil_segments(): Convert cubics to quadratics first!")
|
||||||
|
}
|
||||||
segments::Segment::EndSubpath(..) => {}
|
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_segment_index = self.stencil_segments.len() as u32;
|
||||||
let last_curve_index = self.segments.curves.len() as u32;
|
|
||||||
|
|
||||||
let path_ranges = self.ensure_path_ranges(path_id);
|
let path_ranges = self.ensure_path_ranges(path_id);
|
||||||
path_ranges.segment_curves = first_curve_index..last_curve_index;
|
path_ranges.stencil_segments = first_segment_index..last_segment_index;
|
||||||
path_ranges.segment_lines = first_line_index..last_line_index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes vertex normals necessary for emboldening and/or stem darkening.
|
/// Computes vertex normals necessary for emboldening and/or stem darkening. This is intended
|
||||||
pub fn push_normals<I>(&mut self, _path_id: u16, stream: I) where I: PathIterator {
|
/// for stencil-and-cover.
|
||||||
let path_events: Vec<_> = stream.collect();
|
pub fn push_stencil_normals<I>(&mut self, _path_id: u16, stream: I)
|
||||||
|
where I: Iterator<Item = PathEvent> + Clone {
|
||||||
let mut normals = PathNormals::new();
|
let mut normals = PathNormals::new();
|
||||||
normals.add_path(path_events.iter().cloned());
|
normals.add_path(stream);
|
||||||
let normals = normals.normals();
|
self.stencil_normals.extend(normals.normals().iter().map(|normals| {
|
||||||
|
StencilNormals {
|
||||||
let mut current_point_normal_index = 0;
|
from: normals.from,
|
||||||
let mut next_normal_index = 0;
|
ctrl: normals.ctrl,
|
||||||
let mut first_normal_index_of_subpath = 0;
|
to: normals.to,
|
||||||
|
|
||||||
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!")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}))
|
||||||
|
|
||||||
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.
|
/// 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"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"bqii", &self.b_quad_vertex_interior_indices));
|
||||||
try!(write_simple_chunk(writer, b"bbox", &self.b_boxes));
|
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"sseg", &self.stencil_segments));
|
||||||
try!(write_simple_chunk(writer, b"scur", &self.segments.curves));
|
try!(write_simple_chunk(writer, b"snor", &self.stencil_normals));
|
||||||
try!(write_simple_chunk(writer, b"snli", &self.segment_normals.line_normals));
|
|
||||||
try!(write_simple_chunk(writer, b"sncu", &self.segment_normals.curve_normals));
|
|
||||||
|
|
||||||
let total_length = try!(writer.seek(SeekFrom::Current(0)));
|
let total_length = try!(writer.seek(SeekFrom::Current(0)));
|
||||||
try!(writer.seek(SeekFrom::Start(4)));
|
try!(writer.seek(SeekFrom::Start(4)));
|
||||||
|
@ -413,8 +370,10 @@ impl MeshLibrary {
|
||||||
|ranges| &ranges.b_quad_vertex_interior_indices));
|
|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"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"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,
|
||||||
try!(write_path_range(writer, b"scur", path_ranges, |ranges| &ranges.segment_curves));
|
b"sseg",
|
||||||
|
path_ranges,
|
||||||
|
|ranges| &ranges.stencil_segments));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -469,6 +428,7 @@ pub struct PathRanges {
|
||||||
pub b_boxes: Range<u32>,
|
pub b_boxes: Range<u32>,
|
||||||
pub segment_lines: Range<u32>,
|
pub segment_lines: Range<u32>,
|
||||||
pub segment_curves: Range<u32>,
|
pub segment_curves: Range<u32>,
|
||||||
|
pub stencil_segments: Range<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PathRanges {
|
impl PathRanges {
|
||||||
|
@ -481,6 +441,7 @@ impl PathRanges {
|
||||||
b_boxes: 0..0,
|
b_boxes: 0..0,
|
||||||
segment_lines: 0..0,
|
segment_lines: 0..0,
|
||||||
segment_curves: 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)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
pub struct BBox {
|
pub struct BBox {
|
||||||
pub upper_left_position: Point2D<f32>,
|
pub upper_left_position: Point2D<f32>,
|
||||||
|
@ -578,12 +473,18 @@ pub struct BBox {
|
||||||
pub lower_mode: f32,
|
pub lower_mode: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
struct CornerPositions {
|
pub struct StencilSegment {
|
||||||
upper_left: Point2D<f32>,
|
pub from: Point2D<f32>,
|
||||||
upper_right: Point2D<f32>,
|
pub ctrl: Point2D<f32>,
|
||||||
lower_left: Point2D<f32>,
|
pub to: Point2D<f32>,
|
||||||
lower_right: 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)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
|
|
@ -8,13 +8,19 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
use euclid::Vector2D;
|
use euclid::{Point2D, Vector2D};
|
||||||
use euclid::approxeq::ApproxEq;
|
|
||||||
use lyon_path::PathEvent;
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct PathNormals {
|
pub struct PathNormals {
|
||||||
normals: Vec<Vector2D<f32>>,
|
normals: Vec<SegmentNormals>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PathNormals {
|
impl PathNormals {
|
||||||
|
@ -26,7 +32,7 @@ impl PathNormals {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn normals(&self) -> &[Vector2D<f32>] {
|
pub fn normals(&self) -> &[SegmentNormals] {
|
||||||
&self.normals
|
&self.normals
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,77 +40,96 @@ impl PathNormals {
|
||||||
self.normals.clear()
|
self.normals.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_path<I>(&mut self, path: I) where I: Iterator<Item = PathEvent> {
|
pub fn add_path<I>(&mut self, mut stream: I) where I: Iterator<Item = PathEvent> + Clone {
|
||||||
let mut path = path.peekable();
|
let (mut path_stream, mut path_points) = (stream.clone(), vec![]);
|
||||||
while path.peek().is_some() {
|
while let Some(event) = stream.next() {
|
||||||
let mut positions = vec![];
|
match event {
|
||||||
loop {
|
PathEvent::MoveTo(to) => path_points.push(to),
|
||||||
match path.next() {
|
PathEvent::LineTo(to) => path_points.push(to),
|
||||||
Some(PathEvent::MoveTo(to)) | Some(PathEvent::LineTo(to)) => {
|
PathEvent::QuadraticTo(ctrl, to) => path_points.extend_from_slice(&[ctrl, to]),
|
||||||
positions.push(to);
|
PathEvent::CubicTo(..) => {
|
||||||
}
|
panic!("PathNormals::add_path(): Convert cubics to quadratics first!")
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
PathEvent::Arc(..) => {
|
||||||
if let Some(&PathEvent::MoveTo(..)) = path.peek() {
|
panic!("PathNormals::add_path(): Convert arcs to quadratics first!")
|
||||||
break
|
}
|
||||||
|
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() {
|
fn flush<I>(&mut self, path_stream: I, path_points: &mut Vec<Point2D<f32>>)
|
||||||
let mut prev_index = this_index;
|
where I: Iterator<Item = PathEvent> + Clone {
|
||||||
let mut prev_vector;
|
match path_points.len() {
|
||||||
loop {
|
0 | 1 => path_points.clear(),
|
||||||
if prev_index > 0 {
|
2 => {
|
||||||
prev_index -= 1
|
self.normals.push(SegmentNormals {
|
||||||
} else {
|
from: path_points[1] - path_points[0],
|
||||||
prev_index = positions.len() - 1
|
ctrl: Vector2D::zero(),
|
||||||
}
|
to: path_points[0] - path_points[1],
|
||||||
prev_vector = *this_position - positions[prev_index];
|
});
|
||||||
if !prev_vector.square_length().approx_eq(&0.0) {
|
path_points.clear();
|
||||||
break
|
}
|
||||||
}
|
_ => 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],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
PathEvent::QuadraticTo(..) => {
|
||||||
let mut next_index = this_index;
|
next_normal_index += 2;
|
||||||
let mut next_vector;
|
self.normals.push(SegmentNormals {
|
||||||
loop {
|
from: normals[next_normal_index - 3],
|
||||||
if next_index + 1 < positions.len() {
|
ctrl: normals[next_normal_index - 2],
|
||||||
next_index += 1
|
to: normals[next_normal_index - 1],
|
||||||
} else {
|
})
|
||||||
next_index = 0
|
|
||||||
}
|
|
||||||
next_vector = positions[next_index] - *this_position;
|
|
||||||
if !next_vector.square_length().approx_eq(&0.0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
PathEvent::CubicTo(..) | PathEvent::Arc(..) => unreachable!(),
|
||||||
let prev_normal = rotate(&prev_vector).normalize();
|
PathEvent::Close => {
|
||||||
let next_normal = rotate(&next_vector).normalize();
|
self.normals.push(SegmentNormals {
|
||||||
let mut bisector = (prev_normal + next_normal) * 0.5;
|
from: normals[next_normal_index - 1],
|
||||||
if bisector.square_length().approx_eq(&0.0) {
|
ctrl: Vector2D::zero(),
|
||||||
bisector = Vector2D::new(next_vector.y, next_vector.x)
|
to: normals[0],
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.normals.push(bisector.normalize());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rotate(vector: &Vector2D<f32>) -> Vector2D<f32> {
|
fn compute_normal(prev: &Point2D<f32>, current: &Point2D<f32>, next: &Point2D<f32>)
|
||||||
Vector2D::new(-vector.y, vector.x)
|
-> Vector2D<f32> {
|
||||||
|
let vector = ((*current - *prev) + (*next - *current)).normalize();
|
||||||
|
Vector2D::new(vector.y, -vector.x)
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,12 +48,9 @@ impl<I> Iterator for SegmentIter<I> where I: Iterator<Item = PathEvent> {
|
||||||
Some(PathEvent::Close) => {
|
Some(PathEvent::Close) => {
|
||||||
self.was_just_closed = true;
|
self.was_just_closed = true;
|
||||||
let state = self.inner.get_state();
|
let state = self.inner.get_state();
|
||||||
/*if state.first == current_point {
|
|
||||||
return Some(Segment::EndSubpath(true))
|
|
||||||
}*/
|
|
||||||
self.stack.push(Segment::EndSubpath(true));
|
self.stack.push(Segment::EndSubpath(true));
|
||||||
Some(Segment::Line(LineSegment {
|
Some(Segment::Line(LineSegment {
|
||||||
from: state.current,
|
from: current_point,
|
||||||
to: state.first,
|
to: state.first,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,14 @@ int imod(int ia, int ib) {
|
||||||
return int(floor(m + 0.5));
|
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
|
/// Returns the *2D* result of transforming the given 2D point with the given 4D transformation
|
||||||
/// matrix.
|
/// matrix.
|
||||||
///
|
///
|
||||||
|
@ -58,6 +66,11 @@ vec2 transformVertexPositionAffine(vec2 position, vec4 transformST, vec2 transfo
|
||||||
return position * transformST.xy + position.yx * transformExt + transformST.zw;
|
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
|
/// Interpolates the given 2D position in the vertical direction using the given ultra-slight
|
||||||
/// hints.
|
/// hints.
|
||||||
///
|
///
|
||||||
|
@ -110,170 +123,6 @@ vec2 dilatePosition(vec2 position, float normalAngle, vec2 amount) {
|
||||||
return position + vec2(cos(normalAngle), -sin(normalAngle)) * 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.
|
/// Returns true if the slope of the line along the given vector is negative.
|
||||||
bool slopeIsNegative(vec2 dp) {
|
bool slopeIsNegative(vec2 dp) {
|
||||||
return dp.y < 0.0;
|
return dp.y < 0.0;
|
||||||
|
@ -415,10 +264,30 @@ vec4 fetchPathAffineTransform(out vec2 outPathTransformExt,
|
||||||
return fetchFloat4Data(pathTransformSTTexture, pathID, pathTransformSTDimensions);
|
return fetchFloat4Data(pathTransformSTTexture, pathID, pathTransformSTDimensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
float detMat2(mat2 m) {
|
// Are we inside the convex hull of the curve? (This will always be false if this is a line.)
|
||||||
return m[0][0] * m[1][1] - m[0][1] * m[1][0];
|
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) {
|
float signedDistanceToCurve(vec2 uv, vec2 dUVDX, vec2 dUVDY, bool inCurve) {
|
||||||
return mat2(m[1][1], -m[0][1], -m[1][0], m[0][0]) / detMat2(m);
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,8 @@ void main() {
|
||||||
vec2 translation = uTransformST.zw + globalTransformLinear * transformST.zw;
|
vec2 translation = uTransformST.zw + globalTransformLinear * transformST.zw;
|
||||||
|
|
||||||
float onePixel = 2.0 / float(uFramebufferSize.y);
|
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);
|
vec2 position = aPosition + vec2(0.0, imod(vertexID, 6) < 3 ? dilation : -dilation);
|
||||||
position = transformLinear * position + translation;
|
position = transformLinear * position + translation;
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -19,36 +19,13 @@ varying vec4 vColor;
|
||||||
varying vec4 vUV;
|
varying vec4 vUV;
|
||||||
varying vec4 vSignMode;
|
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() {
|
void main() {
|
||||||
float alpha = 1.0;
|
bool inUpperCurve = insideCurve(vec3(vUV.xy, vSignMode.z > 0.0 ? 1.0 : 0.0));
|
||||||
alpha -= computeAlpha(vUV.xy, vSignMode.x, vSignMode.z);
|
bool inLowerCurve = insideCurve(vec3(vUV.zw, vSignMode.w > 0.0 ? 1.0 : 0.0));
|
||||||
alpha -= computeAlpha(vUV.zw, vSignMode.y, vSignMode.w);
|
|
||||||
|
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;
|
gl_FragColor = alpha * vColor;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// pathfinder/shaders/gles2/mcaa.vs.glsl
|
// 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
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
@ -57,6 +57,7 @@ attribute vec4 aRect;
|
||||||
attribute vec4 aUV;
|
attribute vec4 aUV;
|
||||||
attribute vec4 aDUVDX;
|
attribute vec4 aDUVDX;
|
||||||
attribute vec4 aDUVDY;
|
attribute vec4 aDUVDY;
|
||||||
|
// TODO(pcwalton): This is redundant; sign 0 can be used to indicate lines.
|
||||||
attribute vec4 aSignMode;
|
attribute vec4 aSignMode;
|
||||||
attribute float aPathID;
|
attribute float aPathID;
|
||||||
|
|
||||||
|
@ -85,7 +86,8 @@ void main() {
|
||||||
translation = uTransformST.zw + globalTransformLinear * translation;
|
translation = uTransformST.zw + globalTransformLinear * translation;
|
||||||
|
|
||||||
float onePixel = 2.0 / float(uFramebufferSize.y);
|
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;
|
tessCoord.y += tessCoord.y < 0.5 ? -dilation : dilation;
|
||||||
|
|
||||||
vec2 position = transformLinear * tessCoord + translation;
|
vec2 position = transformLinear * tessCoord + translation;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -100,6 +100,7 @@ fn convert_font(font_path: &Path, output_path: &Path) -> Result<(), ()> {
|
||||||
let path_index = (glyph_index + 1) as u16;
|
let path_index = (glyph_index + 1) as u16;
|
||||||
partitioner.library_mut().push_segments(path_index, path.iter());
|
partitioner.library_mut().push_segments(path_index, path.iter());
|
||||||
partitioner.library_mut().push_normals(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()));
|
paths.push((path_index, path.iter().collect()));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue