Simplify the B-quad vertex position VBO to have a uniform format, and
enable early Z for SVG. Additionally, this switches the B-quad patches for XCAA to be convex hulls instead of bounding boxes, reducing fragment shader load. This is a large speedup for the Ghostscript tiger demo: 5x or more. Closes #33.
This commit is contained in:
parent
67dd6cd8ed
commit
6a640eca74
|
@ -7,3 +7,6 @@ members = [
|
|||
"utils/frontend",
|
||||
"utils/gamma-lut",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
ring = { git = "https://github.com/SergioBenitez/ring", branch = "v0.12" }
|
||||
|
|
|
@ -18,7 +18,7 @@ import {DemoView} from './view';
|
|||
|
||||
export type AntialiasingStrategyName = 'none' | 'ssaa' | 'xcaa';
|
||||
|
||||
export type DirectRenderingMode = 'none' | 'color';
|
||||
export type DirectRenderingMode = 'none' | 'conservative' | 'color';
|
||||
|
||||
export type SubpixelAAType = 'none' | 'medium';
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ export interface EXTDisjointTimerQuery {
|
|||
|
||||
export class WebGLQuery {}
|
||||
|
||||
export const QUAD_ELEMENTS: Uint8Array = new Uint8Array([2, 0, 1, 1, 3, 2]);
|
||||
export const QUAD_ELEMENTS: Uint8Array = new Uint8Array([0, 1, 2, 1, 3, 2]);
|
||||
|
||||
export function createFramebufferColorTexture(gl: WebGLRenderingContext,
|
||||
size: glmatrix.vec2,
|
||||
|
|
|
@ -268,9 +268,7 @@ class MeshDebuggerView extends PathfinderView {
|
|||
context.font = `12px ${POINT_LABEL_FONT}`;
|
||||
context.lineWidth = invScaleFactor;
|
||||
|
||||
const bQuads = new Uint32Array(meshes.bQuads);
|
||||
const positions = new Float32Array(meshes.bVertexPositions);
|
||||
const bVertexNormals = new Float32Array(meshes.bVertexNormals);
|
||||
const bQuadVertexPositions = new Float32Array(meshes.bQuadVertexPositions);
|
||||
|
||||
const normals: NormalsTable<Float32Array> = {
|
||||
lowerCurve: new Float32Array(0),
|
||||
|
@ -279,54 +277,24 @@ class MeshDebuggerView extends PathfinderView {
|
|||
upperLine: new Float32Array(0),
|
||||
};
|
||||
|
||||
const drawnVertices: boolean[] = [], drawnNormals: boolean[] = [];
|
||||
|
||||
// Draw B-quads.
|
||||
for (let bQuadIndex = 0; bQuadIndex < meshes.bQuadCount; bQuadIndex++) {
|
||||
for (let bQuadIndex = 0; bQuadIndex < meshes.bQuadVertexPositionCount; bQuadIndex++) {
|
||||
const bQuadStartOffset = (B_QUAD_SIZE * bQuadIndex) / UINT32_SIZE;
|
||||
|
||||
const upperLeftIndex = bQuads[bQuadStartOffset +
|
||||
B_QUAD_UPPER_LEFT_VERTEX_OFFSET / UINT32_SIZE];
|
||||
const upperRightIndex = bQuads[bQuadStartOffset +
|
||||
B_QUAD_UPPER_RIGHT_VERTEX_OFFSET / UINT32_SIZE];
|
||||
const upperControlPointIndex =
|
||||
bQuads[bQuadStartOffset + B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET / UINT32_SIZE];
|
||||
const lowerLeftIndex = bQuads[bQuadStartOffset +
|
||||
B_QUAD_LOWER_LEFT_VERTEX_OFFSET / UINT32_SIZE];
|
||||
const lowerRightIndex = bQuads[bQuadStartOffset +
|
||||
B_QUAD_LOWER_RIGHT_VERTEX_OFFSET / UINT32_SIZE];
|
||||
const lowerControlPointIndex =
|
||||
bQuads[bQuadStartOffset + B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET / UINT32_SIZE];
|
||||
|
||||
const upperLeftPosition = unwrapNull(getPosition(positions, upperLeftIndex));
|
||||
const upperRightPosition = unwrapNull(getPosition(positions, upperRightIndex));
|
||||
const upperControlPointPosition = getPosition(positions, upperControlPointIndex);
|
||||
const lowerLeftPosition = unwrapNull(getPosition(positions, lowerLeftIndex));
|
||||
const lowerRightPosition = unwrapNull(getPosition(positions, lowerRightIndex));
|
||||
const lowerControlPointPosition = getPosition(positions, lowerControlPointIndex);
|
||||
const upperLeftPosition = getPosition(bQuadVertexPositions, bQuadIndex, 0);
|
||||
const upperControlPointPosition = getPosition(bQuadVertexPositions, bQuadIndex, 1);
|
||||
const upperRightPosition = getPosition(bQuadVertexPositions, bQuadIndex, 2);
|
||||
const lowerRightPosition = getPosition(bQuadVertexPositions, bQuadIndex, 3);
|
||||
const lowerControlPointPosition = getPosition(bQuadVertexPositions, bQuadIndex, 4);
|
||||
const lowerLeftPosition = getPosition(bQuadVertexPositions, bQuadIndex, 5);
|
||||
|
||||
if (this.drawVertices) {
|
||||
drawVertexIfNecessary(context,
|
||||
drawnVertices,
|
||||
upperLeftIndex,
|
||||
upperLeftPosition,
|
||||
invScaleFactor);
|
||||
drawVertexIfNecessary(context,
|
||||
drawnVertices,
|
||||
upperRightIndex,
|
||||
upperRightPosition,
|
||||
invScaleFactor);
|
||||
drawVertexIfNecessary(context,
|
||||
drawnVertices,
|
||||
lowerLeftIndex,
|
||||
lowerLeftPosition,
|
||||
invScaleFactor);
|
||||
drawVertexIfNecessary(context,
|
||||
drawnVertices,
|
||||
lowerRightIndex,
|
||||
lowerRightPosition,
|
||||
invScaleFactor);
|
||||
drawVertexIfNecessary(context, upperLeftPosition, invScaleFactor);
|
||||
drawVertexIfNecessary(context, upperRightPosition, invScaleFactor);
|
||||
drawVertexIfNecessary(context, lowerLeftPosition, invScaleFactor);
|
||||
drawVertexIfNecessary(context, lowerRightPosition, invScaleFactor);
|
||||
}
|
||||
|
||||
context.beginPath();
|
||||
context.moveTo(upperLeftPosition[0], -upperLeftPosition[1]);
|
||||
if (upperControlPointPosition != null) {
|
||||
|
@ -366,22 +334,6 @@ class MeshDebuggerView extends PathfinderView {
|
|||
context.moveTo(lowerLeftPosition[0], -lowerLeftPosition[1]);
|
||||
context.lineTo(upperLeftPosition[0], -upperLeftPosition[1]);
|
||||
context.stroke();
|
||||
|
||||
// Draw B-quad normals.
|
||||
const lowerLeftNormal = bVertexNormals[lowerLeftIndex];
|
||||
const lowerRightNormal = bVertexNormals[lowerRightIndex];
|
||||
const upperLeftNormal = bVertexNormals[upperLeftIndex];
|
||||
const upperRightNormal = bVertexNormals[upperRightIndex];
|
||||
if (this.drawVertices && this.drawNormals) {
|
||||
drawNormal(context, lowerLeftPosition, lowerLeftNormal, invScaleFactor,
|
||||
'bVertex');
|
||||
drawNormal(context, lowerRightPosition, lowerRightNormal, invScaleFactor,
|
||||
'bVertex');
|
||||
drawNormal(context, upperLeftPosition, upperLeftNormal, invScaleFactor,
|
||||
'bVertex');
|
||||
drawNormal(context, upperRightPosition, upperRightNormal, invScaleFactor,
|
||||
'bVertex');
|
||||
}
|
||||
}
|
||||
|
||||
// Draw segments.
|
||||
|
@ -429,10 +381,16 @@ class MeshDebuggerView extends PathfinderView {
|
|||
}
|
||||
}
|
||||
|
||||
function getPosition(positions: Float32Array, vertexIndex: number): glmatrix.vec2 | null {
|
||||
if (vertexIndex === UINT32_MAX)
|
||||
return null;
|
||||
return glmatrix.vec2.clone([positions[vertexIndex * 2 + 0], positions[vertexIndex * 2 + 1]]);
|
||||
function getPosition(positions: Float32Array, bQuadIndex: number, vertexIndex: number):
|
||||
glmatrix.vec2 {
|
||||
return glmatrix.vec2.clone([
|
||||
positions[(bQuadIndex * 6 + vertexIndex) * 2 + 0],
|
||||
positions[(bQuadIndex * 6 + vertexIndex) * 2 + 1],
|
||||
]);
|
||||
}
|
||||
|
||||
function getNormal(normals: Float32Array, bQuadIndex: number, vertexIndex: number): number {
|
||||
return normals[bQuadIndex * 6 + vertexIndex];
|
||||
}
|
||||
|
||||
function getNormals(normals: NormalsTable<Float32Array>,
|
||||
|
@ -526,25 +484,12 @@ function drawSegmentVertices(context: CanvasRenderingContext2D,
|
|||
}
|
||||
|
||||
function drawVertexIfNecessary(context: CanvasRenderingContext2D,
|
||||
markedVertices: boolean[],
|
||||
vertexIndex: number,
|
||||
position: Float32Array,
|
||||
invScaleFactor: number) {
|
||||
if (markedVertices[vertexIndex] != null)
|
||||
return;
|
||||
markedVertices[vertexIndex] = true;
|
||||
|
||||
context.beginPath();
|
||||
context.moveTo(position[0], -position[1]);
|
||||
context.arc(position[0], -position[1], POINT_RADIUS * invScaleFactor, 0, 2.0 * Math.PI);
|
||||
context.fill();
|
||||
|
||||
context.save();
|
||||
context.scale(invScaleFactor, invScaleFactor);
|
||||
context.fillText("" + vertexIndex,
|
||||
position[0] / invScaleFactor + POINT_LABEL_OFFSET[0],
|
||||
-position[1] / invScaleFactor + POINT_LABEL_OFFSET[1]);
|
||||
context.restore();
|
||||
}
|
||||
|
||||
function drawSegmentVertex(context: CanvasRenderingContext2D,
|
||||
|
|
|
@ -12,7 +12,7 @@ import * as base64js from 'base64-js';
|
|||
|
||||
import * as _ from 'lodash';
|
||||
import {expectNotNull, FLOAT32_SIZE, panic, PathfinderError, Range, UINT16_SIZE} from './utils';
|
||||
import {UINT32_MAX, UINT32_SIZE, unwrapNull, unwrapUndef} from './utils';
|
||||
import {UINT32_MAX, UINT32_SIZE, UINT8_SIZE, unwrapNull, unwrapUndef} from './utils';
|
||||
|
||||
interface BufferTypeFourCCTable {
|
||||
[fourCC: string]: keyof Meshes<void>;
|
||||
|
@ -87,24 +87,9 @@ const SEGMENT_LINE_SIZE: number = 4 * 4;
|
|||
const SEGMENT_CURVE_SIZE: number = 4 * 6;
|
||||
|
||||
const MESH_TYPES: Meshes<MeshBufferTypeDescriptor> = {
|
||||
bQuadVertexInteriorIndices: { type: 'Uint32', size: 1 },
|
||||
bQuadVertexPositionPathIDs: { type: 'Uint16', size: 6 },
|
||||
bQuadVertexPositions: { type: 'Float32', size: 12 },
|
||||
bQuads: { type: 'Uint32', size: B_QUAD_FIELD_COUNT },
|
||||
bVertexLoopBlinnData: { type: 'Uint32', size: 1 },
|
||||
bVertexNormals: { type: 'Float32', size: 1 },
|
||||
bVertexPathIDs: { type: 'Uint16', size: 1 },
|
||||
bVertexPositions: { type: 'Float32', size: 2 },
|
||||
coverCurveIndices: { type: 'Uint32', size: 1 },
|
||||
coverInteriorIndices: { type: 'Uint32', size: 1 },
|
||||
edgeBoundingBoxPathIDs: { type: 'Uint16', size: 1 },
|
||||
edgeBoundingBoxVertexPositions: { type: 'Float32', size: 4 },
|
||||
edgeLowerCurvePathIDs: { type: 'Uint16', size: 1 },
|
||||
edgeLowerCurveVertexPositions: { type: 'Float32', size: 6 },
|
||||
edgeLowerLinePathIDs: { type: 'Uint16', size: 1 },
|
||||
edgeLowerLineVertexPositions: { type: 'Float32', size: 4 },
|
||||
edgeUpperCurvePathIDs: { type: 'Uint16', size: 1 },
|
||||
edgeUpperCurveVertexPositions: { type: 'Float32', size: 6 },
|
||||
edgeUpperLinePathIDs: { type: 'Uint16', size: 1 },
|
||||
edgeUpperLineVertexPositions: { type: 'Float32', size: 4 },
|
||||
segmentCurveNormals: { type: 'Float32', size: 3 },
|
||||
segmentCurvePathIDs: { type: 'Uint16', size: 1 },
|
||||
segmentCurves: { type: 'Float32', size: 6 },
|
||||
|
@ -114,24 +99,9 @@ const MESH_TYPES: Meshes<MeshBufferTypeDescriptor> = {
|
|||
};
|
||||
|
||||
const BUFFER_TYPES: Meshes<BufferType> = {
|
||||
bQuadVertexInteriorIndices: 'ELEMENT_ARRAY_BUFFER',
|
||||
bQuadVertexPositionPathIDs: 'ARRAY_BUFFER',
|
||||
bQuadVertexPositions: 'ARRAY_BUFFER',
|
||||
bQuads: 'ARRAY_BUFFER',
|
||||
bVertexLoopBlinnData: 'ARRAY_BUFFER',
|
||||
bVertexNormals: 'ARRAY_BUFFER',
|
||||
bVertexPathIDs: 'ARRAY_BUFFER',
|
||||
bVertexPositions: 'ARRAY_BUFFER',
|
||||
coverCurveIndices: 'ELEMENT_ARRAY_BUFFER',
|
||||
coverInteriorIndices: 'ELEMENT_ARRAY_BUFFER',
|
||||
edgeBoundingBoxPathIDs: 'ARRAY_BUFFER',
|
||||
edgeBoundingBoxVertexPositions: 'ARRAY_BUFFER',
|
||||
edgeLowerCurvePathIDs: 'ARRAY_BUFFER',
|
||||
edgeLowerCurveVertexPositions: 'ARRAY_BUFFER',
|
||||
edgeLowerLinePathIDs: 'ARRAY_BUFFER',
|
||||
edgeLowerLineVertexPositions: 'ARRAY_BUFFER',
|
||||
edgeUpperCurvePathIDs: 'ARRAY_BUFFER',
|
||||
edgeUpperCurveVertexPositions: 'ARRAY_BUFFER',
|
||||
edgeUpperLinePathIDs: 'ARRAY_BUFFER',
|
||||
edgeUpperLineVertexPositions: 'ARRAY_BUFFER',
|
||||
segmentCurveNormals: 'ARRAY_BUFFER',
|
||||
segmentCurvePathIDs: 'ARRAY_BUFFER',
|
||||
segmentCurves: 'ARRAY_BUFFER',
|
||||
|
@ -148,18 +118,8 @@ const MESH_LIBRARY_FOURCC: string = 'PFML';
|
|||
|
||||
// Must match the FourCCs in `pathfinder_partitioner::mesh_library::MeshLibrary::serialize_into()`.
|
||||
const BUFFER_TYPE_FOURCCS: BufferTypeFourCCTable = {
|
||||
bqua: 'bQuads',
|
||||
bqii: 'bQuadVertexInteriorIndices',
|
||||
bqvp: 'bQuadVertexPositions',
|
||||
bvlb: 'bVertexLoopBlinnData',
|
||||
bvno: 'bVertexNormals',
|
||||
bvpo: 'bVertexPositions',
|
||||
cvci: 'coverCurveIndices',
|
||||
cvii: 'coverInteriorIndices',
|
||||
ebbv: 'edgeBoundingBoxVertexPositions',
|
||||
elcv: 'edgeLowerCurveVertexPositions',
|
||||
ellv: 'edgeLowerLineVertexPositions',
|
||||
eucv: 'edgeUpperCurveVertexPositions',
|
||||
eulv: 'edgeUpperLineVertexPositions',
|
||||
scur: 'segmentCurves',
|
||||
slin: 'segmentLines',
|
||||
sncu: 'segmentCurveNormals',
|
||||
|
@ -169,57 +129,28 @@ const BUFFER_TYPE_FOURCCS: BufferTypeFourCCTable = {
|
|||
// Must match the FourCCs in
|
||||
// `pathfinder_partitioner::mesh_library::MeshLibrary::serialize_into::write_path_ranges()`.
|
||||
const PATH_RANGE_TYPE_FOURCCS: PathRangeTypeFourCCTable = {
|
||||
bqua: 'bQuadPathRanges',
|
||||
bqii: 'bQuadVertexInteriorIndexPathRanges',
|
||||
bqvp: 'bQuadVertexPositionPathRanges',
|
||||
bver: 'bVertexPathRanges',
|
||||
cvci: 'coverCurveIndexRanges',
|
||||
cvii: 'coverInteriorIndexRanges',
|
||||
ebbo: 'edgeBoundingBoxRanges',
|
||||
elci: 'edgeLowerCurveIndexRanges',
|
||||
elli: 'edgeLowerLineIndexRanges',
|
||||
euci: 'edgeUpperCurveIndexRanges',
|
||||
euli: 'edgeUpperLineIndexRanges',
|
||||
scur: 'segmentCurveRanges',
|
||||
slin: 'segmentLineRanges',
|
||||
};
|
||||
|
||||
const RANGE_TO_COUNT_TABLE: RangeToCountTable = {
|
||||
bQuadPathRanges: 'bQuadCount',
|
||||
bQuadVertexInteriorIndexPathRanges: 'bQuadVertexInteriorIndexCount',
|
||||
bQuadVertexPositionPathRanges: 'bQuadVertexPositionCount',
|
||||
bVertexPathRanges: 'bVertexCount',
|
||||
coverCurveIndexRanges: 'coverCurveCount',
|
||||
coverInteriorIndexRanges: 'coverInteriorCount',
|
||||
edgeBoundingBoxRanges: 'edgeBoundingBoxCount',
|
||||
edgeLowerCurveIndexRanges: 'edgeLowerCurveCount',
|
||||
edgeLowerLineIndexRanges: 'edgeLowerLineCount',
|
||||
edgeUpperCurveIndexRanges: 'edgeUpperCurveCount',
|
||||
edgeUpperLineIndexRanges: 'edgeUpperLineCount',
|
||||
segmentCurveRanges: 'segmentCurveCount',
|
||||
segmentLineRanges: 'segmentLineCount',
|
||||
};
|
||||
|
||||
const RANGE_TO_RANGE_BUFFER_TABLE: RangeToRangeBufferTable = {
|
||||
bVertexPathRanges: 'bVertexPathIDs',
|
||||
edgeBoundingBoxRanges: 'edgeBoundingBoxPathIDs',
|
||||
edgeLowerCurveIndexRanges: 'edgeLowerCurvePathIDs',
|
||||
edgeLowerLineIndexRanges: 'edgeLowerLinePathIDs',
|
||||
edgeUpperCurveIndexRanges: 'edgeUpperCurvePathIDs',
|
||||
edgeUpperLineIndexRanges: 'edgeUpperLinePathIDs',
|
||||
bQuadVertexPositionPathRanges: 'bQuadVertexPositionPathIDs',
|
||||
segmentCurveRanges: 'segmentCurvePathIDs',
|
||||
segmentLineRanges: 'segmentLinePathIDs',
|
||||
};
|
||||
|
||||
const RANGE_KEYS: Array<keyof PathRanges> = [
|
||||
'bQuadPathRanges',
|
||||
'bQuadVertexPositionPathRanges',
|
||||
'bVertexPathRanges',
|
||||
'coverInteriorIndexRanges',
|
||||
'coverCurveIndexRanges',
|
||||
'edgeBoundingBoxRanges',
|
||||
'edgeUpperLineIndexRanges',
|
||||
'edgeUpperCurveIndexRanges',
|
||||
'edgeLowerLineIndexRanges',
|
||||
'edgeLowerCurveIndexRanges',
|
||||
'bQuadVertexInteriorIndexPathRanges',
|
||||
'segmentCurveRanges',
|
||||
'segmentLineRanges',
|
||||
];
|
||||
|
@ -227,113 +158,51 @@ const RANGE_KEYS: Array<keyof PathRanges> = [
|
|||
type BufferType = 'ARRAY_BUFFER' | 'ELEMENT_ARRAY_BUFFER';
|
||||
|
||||
export interface Meshes<T> {
|
||||
readonly bQuads: T;
|
||||
readonly bQuadVertexPositions: T;
|
||||
readonly bVertexPositions: T;
|
||||
readonly bVertexLoopBlinnData: T;
|
||||
readonly bVertexNormals: T;
|
||||
readonly coverInteriorIndices: T;
|
||||
readonly coverCurveIndices: T;
|
||||
readonly edgeBoundingBoxVertexPositions: T;
|
||||
readonly edgeLowerCurveVertexPositions: T;
|
||||
readonly edgeLowerLineVertexPositions: T;
|
||||
readonly edgeUpperCurveVertexPositions: T;
|
||||
readonly edgeUpperLineVertexPositions: T;
|
||||
readonly bQuadVertexInteriorIndices: T;
|
||||
readonly segmentLines: T;
|
||||
readonly segmentCurves: T;
|
||||
readonly segmentLineNormals: T;
|
||||
readonly segmentCurveNormals: T;
|
||||
|
||||
bVertexPathIDs: T;
|
||||
edgeBoundingBoxPathIDs: T;
|
||||
bQuadVertexPositionPathIDs: T;
|
||||
segmentLinePathIDs: T;
|
||||
segmentCurvePathIDs: T;
|
||||
edgeLowerCurvePathIDs: T;
|
||||
edgeLowerLinePathIDs: T;
|
||||
edgeUpperCurvePathIDs: T;
|
||||
edgeUpperLinePathIDs: T;
|
||||
}
|
||||
|
||||
interface MeshDataCounts {
|
||||
readonly bQuadCount: number;
|
||||
readonly bQuadVertexPositionCount: number;
|
||||
readonly bVertexCount: number;
|
||||
readonly coverCurveCount: number;
|
||||
readonly coverInteriorCount: number;
|
||||
readonly edgeBoundingBoxCount: number;
|
||||
readonly edgeLowerCurveCount: number;
|
||||
readonly edgeUpperCurveCount: number;
|
||||
readonly edgeLowerLineCount: number;
|
||||
readonly edgeUpperLineCount: number;
|
||||
readonly bQuadVertexInteriorIndexCount: number;
|
||||
readonly segmentLineCount: number;
|
||||
readonly segmentCurveCount: number;
|
||||
}
|
||||
|
||||
interface PathRanges {
|
||||
bQuadPathRanges: Range[];
|
||||
bQuadVertexPositionPathRanges: Range[];
|
||||
bVertexPathRanges: Range[];
|
||||
coverInteriorIndexRanges: Range[];
|
||||
coverCurveIndexRanges: Range[];
|
||||
edgeBoundingBoxRanges: Range[];
|
||||
edgeUpperLineIndexRanges: Range[];
|
||||
edgeUpperCurveIndexRanges: Range[];
|
||||
edgeLowerLineIndexRanges: Range[];
|
||||
edgeLowerCurveIndexRanges: Range[];
|
||||
bQuadVertexInteriorIndexPathRanges: Range[];
|
||||
segmentCurveRanges: Range[];
|
||||
segmentLineRanges: Range[];
|
||||
}
|
||||
|
||||
export class PathfinderMeshData implements Meshes<ArrayBuffer>, MeshDataCounts, PathRanges {
|
||||
readonly bQuads: ArrayBuffer;
|
||||
readonly bQuadVertexPositions: ArrayBuffer;
|
||||
readonly bVertexPositions: ArrayBuffer;
|
||||
readonly bVertexLoopBlinnData: ArrayBuffer;
|
||||
readonly bVertexNormals: ArrayBuffer;
|
||||
readonly coverInteriorIndices: ArrayBuffer;
|
||||
readonly coverCurveIndices: ArrayBuffer;
|
||||
readonly edgeBoundingBoxVertexPositions: ArrayBuffer;
|
||||
readonly edgeLowerCurveVertexPositions: ArrayBuffer;
|
||||
readonly edgeLowerLineVertexPositions: ArrayBuffer;
|
||||
readonly edgeUpperCurveVertexPositions: ArrayBuffer;
|
||||
readonly edgeUpperLineVertexPositions: ArrayBuffer;
|
||||
readonly bQuadVertexInteriorIndices: ArrayBuffer;
|
||||
readonly segmentLines: ArrayBuffer;
|
||||
readonly segmentCurves: ArrayBuffer;
|
||||
readonly segmentLineNormals: ArrayBuffer;
|
||||
readonly segmentCurveNormals: ArrayBuffer;
|
||||
|
||||
readonly bQuadCount: number;
|
||||
readonly bQuadVertexPositionCount: number;
|
||||
readonly bVertexCount: number;
|
||||
readonly coverCurveCount: number;
|
||||
readonly coverInteriorCount: number;
|
||||
readonly edgeBoundingBoxCount: number;
|
||||
readonly edgeLowerCurveCount: number;
|
||||
readonly edgeUpperCurveCount: number;
|
||||
readonly edgeLowerLineCount: number;
|
||||
readonly edgeUpperLineCount: number;
|
||||
readonly bQuadVertexInteriorIndexCount: number;
|
||||
readonly segmentLineCount: number;
|
||||
readonly segmentCurveCount: number;
|
||||
|
||||
bVertexPathIDs: ArrayBuffer;
|
||||
edgeBoundingBoxPathIDs: ArrayBuffer;
|
||||
edgeLowerCurvePathIDs: ArrayBuffer;
|
||||
edgeLowerLinePathIDs: ArrayBuffer;
|
||||
edgeUpperCurvePathIDs: ArrayBuffer;
|
||||
edgeUpperLinePathIDs: ArrayBuffer;
|
||||
bQuadVertexPositionPathIDs: ArrayBuffer;
|
||||
segmentCurvePathIDs: ArrayBuffer;
|
||||
segmentLinePathIDs: ArrayBuffer;
|
||||
|
||||
bQuadPathRanges: Range[];
|
||||
bQuadVertexPositionPathRanges: Range[];
|
||||
bVertexPathRanges: Range[];
|
||||
coverInteriorIndexRanges: Range[];
|
||||
coverCurveIndexRanges: Range[];
|
||||
edgeBoundingBoxRanges: Range[];
|
||||
edgeUpperLineIndexRanges: Range[];
|
||||
edgeUpperCurveIndexRanges: Range[];
|
||||
edgeLowerLineIndexRanges: Range[];
|
||||
edgeLowerCurveIndexRanges: Range[];
|
||||
bQuadVertexInteriorIndexPathRanges: Range[];
|
||||
segmentCurveRanges: Range[];
|
||||
segmentLineRanges: Range[];
|
||||
|
||||
|
@ -368,22 +237,10 @@ export class PathfinderMeshData implements Meshes<ArrayBuffer>, MeshDataCounts,
|
|||
this[range] = ranges[range];
|
||||
}
|
||||
|
||||
this.bQuadCount = this.bQuads.byteLength / B_QUAD_SIZE;
|
||||
this.bQuadVertexPositionCount = this.bQuadVertexPositions.byteLength /
|
||||
B_QUAD_VERTEX_POSITION_SIZE;
|
||||
this.bVertexCount = this.bVertexPositions.byteLength / B_VERTEX_POSITION_SIZE;
|
||||
this.coverCurveCount = this.coverCurveIndices.byteLength / INDEX_SIZE;
|
||||
this.coverInteriorCount = this.coverInteriorIndices.byteLength / INDEX_SIZE;
|
||||
this.edgeBoundingBoxCount = this.edgeBoundingBoxVertexPositions.byteLength /
|
||||
EDGE_BOUNDING_BOX_VERTEX_POSITION_SIZE;
|
||||
this.edgeUpperLineCount = this.edgeUpperLineVertexPositions.byteLength /
|
||||
EDGE_UPPER_LINE_VERTEX_POSITION_SIZE;
|
||||
this.edgeLowerLineCount = this.edgeLowerLineVertexPositions.byteLength /
|
||||
EDGE_LOWER_LINE_VERTEX_POSITION_SIZE;
|
||||
this.edgeUpperCurveCount = this.edgeUpperCurveVertexPositions.byteLength /
|
||||
EDGE_UPPER_CURVE_VERTEX_POSITION_SIZE;
|
||||
this.edgeLowerCurveCount = this.edgeLowerCurveVertexPositions.byteLength /
|
||||
EDGE_LOWER_CURVE_VERTEX_POSITION_SIZE;
|
||||
this.bQuadVertexInteriorIndexCount = this.bQuadVertexInteriorIndices.byteLength /
|
||||
INDEX_SIZE;
|
||||
this.segmentCurveCount = this.segmentCurves.byteLength / SEGMENT_CURVE_SIZE;
|
||||
this.segmentLineCount = this.segmentLines.byteLength / SEGMENT_LINE_SIZE;
|
||||
|
||||
|
@ -418,26 +275,8 @@ export class PathfinderMeshData implements Meshes<ArrayBuffer>, MeshDataCounts,
|
|||
const expandedPathID = newPathIndex + 1;
|
||||
const originalPathID = pathIDs[newPathIndex];
|
||||
|
||||
const bVertexCopyResult = copyVertices(['bVertexPositions',
|
||||
'bVertexLoopBlinnData',
|
||||
'bVertexNormals'],
|
||||
'bVertexPathRanges',
|
||||
expandedArrays,
|
||||
expandedRanges,
|
||||
originalBuffers,
|
||||
originalRanges,
|
||||
expandedPathID,
|
||||
originalPathID);
|
||||
|
||||
if (bVertexCopyResult == null)
|
||||
continue;
|
||||
|
||||
const firstExpandedBVertexIndex = bVertexCopyResult.expandedStartIndex;
|
||||
const firstBVertexIndex = bVertexCopyResult.originalStartIndex;
|
||||
const lastBVertexIndex = bVertexCopyResult.originalEndIndex;
|
||||
|
||||
// Copy over B-quad vertex positions.
|
||||
copyVertices(['bQuadVertexPositions'],
|
||||
const bQuadVertexCopyResult = copyVertices(['bQuadVertexPositions'],
|
||||
'bQuadVertexPositionPathRanges',
|
||||
expandedArrays,
|
||||
expandedRanges,
|
||||
|
@ -446,62 +285,21 @@ export class PathfinderMeshData implements Meshes<ArrayBuffer>, MeshDataCounts,
|
|||
expandedPathID,
|
||||
originalPathID);
|
||||
|
||||
// Copy over edge data.
|
||||
copyVertices(['edgeBoundingBoxVertexPositions'],
|
||||
'edgeBoundingBoxRanges',
|
||||
expandedArrays,
|
||||
expandedRanges,
|
||||
originalBuffers,
|
||||
originalRanges,
|
||||
expandedPathID,
|
||||
originalPathID);
|
||||
for (const edgeBufferName of EDGE_BUFFER_NAMES) {
|
||||
copyVertices([`edge${edgeBufferName}VertexPositions` as keyof Meshes<void>],
|
||||
`edge${edgeBufferName}IndexRanges` as keyof PathRanges,
|
||||
expandedArrays,
|
||||
expandedRanges,
|
||||
originalBuffers,
|
||||
originalRanges,
|
||||
expandedPathID,
|
||||
originalPathID);
|
||||
}
|
||||
if (bQuadVertexCopyResult == null)
|
||||
continue;
|
||||
|
||||
const firstExpandedBQuadVertexIndex = bQuadVertexCopyResult.expandedStartIndex;
|
||||
const firstBQuadVertexIndex = bQuadVertexCopyResult.originalStartIndex;
|
||||
const lastBQuadVertexIndex = bQuadVertexCopyResult.originalEndIndex;
|
||||
|
||||
// Copy over indices.
|
||||
copyIndices(expandedArrays.coverInteriorIndices,
|
||||
expandedRanges.coverInteriorIndexRanges,
|
||||
originalBuffers.coverInteriorIndices as Uint32Array,
|
||||
firstExpandedBVertexIndex,
|
||||
firstBVertexIndex,
|
||||
lastBVertexIndex,
|
||||
copyIndices(expandedArrays.bQuadVertexInteriorIndices,
|
||||
expandedRanges.bQuadVertexInteriorIndexPathRanges,
|
||||
originalBuffers.bQuadVertexInteriorIndices as Uint32Array,
|
||||
firstExpandedBQuadVertexIndex * 6,
|
||||
firstBQuadVertexIndex * 6,
|
||||
lastBQuadVertexIndex * 6,
|
||||
expandedPathID);
|
||||
copyIndices(expandedArrays.coverCurveIndices,
|
||||
expandedRanges.coverCurveIndexRanges,
|
||||
originalBuffers.coverCurveIndices as Uint32Array,
|
||||
firstExpandedBVertexIndex,
|
||||
firstBVertexIndex,
|
||||
lastBVertexIndex,
|
||||
expandedPathID);
|
||||
|
||||
// Copy over B-quads.
|
||||
const originalBQuadRange = originalRanges.bQuadPathRanges[originalPathID - 1];
|
||||
const firstExpandedBQuadIndex = expandedArrays.bQuads.length / B_QUAD_FIELD_COUNT;
|
||||
expandedRanges.bQuadPathRanges[expandedPathID - 1] =
|
||||
new Range(firstExpandedBQuadIndex,
|
||||
firstExpandedBQuadIndex + originalBQuadRange.length);
|
||||
const indexDelta = firstExpandedBVertexIndex - firstBVertexIndex;
|
||||
for (let bQuadIndex = originalBQuadRange.start;
|
||||
bQuadIndex < originalBQuadRange.end;
|
||||
bQuadIndex++) {
|
||||
const bQuad = originalBuffers.bQuads[bQuadIndex];
|
||||
for (let indexIndex = 0; indexIndex < B_QUAD_FIELD_COUNT; indexIndex++) {
|
||||
const srcIndex = originalBuffers.bQuads[bQuadIndex * B_QUAD_FIELD_COUNT +
|
||||
indexIndex];
|
||||
if (srcIndex === UINT32_MAX)
|
||||
expandedArrays.bQuads.push(srcIndex);
|
||||
else
|
||||
expandedArrays.bQuads.push(srcIndex + indexDelta);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy over segments.
|
||||
copySegments(['segmentLines', 'segmentLineNormals'],
|
||||
|
@ -560,43 +358,33 @@ export class PathfinderMeshData implements Meshes<ArrayBuffer>, MeshDataCounts,
|
|||
if (!RANGE_TO_RANGE_BUFFER_TABLE.hasOwnProperty(rangeKey))
|
||||
continue;
|
||||
|
||||
const count = this[RANGE_TO_COUNT_TABLE[rangeKey]];
|
||||
const rangeBufferKey = RANGE_TO_RANGE_BUFFER_TABLE[rangeKey];
|
||||
|
||||
const instanceCount = this[RANGE_TO_COUNT_TABLE[rangeKey]];
|
||||
const fieldCount = MESH_TYPES[rangeBufferKey].size;
|
||||
const ranges = this[rangeKey as keyof PathRanges];
|
||||
|
||||
const destBuffer = new Uint16Array(count);
|
||||
const destBuffer = new Uint16Array(instanceCount * fieldCount);
|
||||
let destIndex = 0;
|
||||
for (let pathIndex = 0; pathIndex < ranges.length; pathIndex++) {
|
||||
const range = ranges[pathIndex];
|
||||
for (let subindex = range.start; subindex < range.end; subindex++) {
|
||||
for (let fieldIndex = 0; fieldIndex < fieldCount; fieldIndex++) {
|
||||
destBuffer[destIndex] = pathIndex + 1;
|
||||
destIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(this as any)[RANGE_TO_RANGE_BUFFER_TABLE[rangeKey]] = destBuffer;
|
||||
(this as any)[rangeBufferKey] = destBuffer.buffer as ArrayBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PathfinderMeshBuffers implements Meshes<WebGLBuffer>, PathRanges {
|
||||
readonly bQuads: WebGLBuffer;
|
||||
readonly bQuadVertexPositions: WebGLBuffer;
|
||||
readonly bVertexPositions: WebGLBuffer;
|
||||
readonly bVertexPathIDs: WebGLBuffer;
|
||||
readonly bVertexLoopBlinnData: WebGLBuffer;
|
||||
readonly bVertexNormals: WebGLBuffer;
|
||||
readonly coverInteriorIndices: WebGLBuffer;
|
||||
readonly coverCurveIndices: WebGLBuffer;
|
||||
readonly edgeBoundingBoxPathIDs: WebGLBuffer;
|
||||
readonly edgeBoundingBoxVertexPositions: WebGLBuffer;
|
||||
readonly edgeLowerCurvePathIDs: WebGLBuffer;
|
||||
readonly edgeLowerCurveVertexPositions: WebGLBuffer;
|
||||
readonly edgeLowerLinePathIDs: WebGLBuffer;
|
||||
readonly edgeLowerLineVertexPositions: WebGLBuffer;
|
||||
readonly edgeUpperCurvePathIDs: WebGLBuffer;
|
||||
readonly edgeUpperCurveVertexPositions: WebGLBuffer;
|
||||
readonly edgeUpperLinePathIDs: WebGLBuffer;
|
||||
readonly edgeUpperLineVertexPositions: WebGLBuffer;
|
||||
readonly bQuadVertexPositionPathIDs: WebGLBuffer;
|
||||
readonly bQuadVertexInteriorIndices: WebGLBuffer;
|
||||
readonly segmentLines: WebGLBuffer;
|
||||
readonly segmentCurves: WebGLBuffer;
|
||||
readonly segmentLinePathIDs: WebGLBuffer;
|
||||
|
@ -604,16 +392,8 @@ export class PathfinderMeshBuffers implements Meshes<WebGLBuffer>, PathRanges {
|
|||
readonly segmentLineNormals: WebGLBuffer;
|
||||
readonly segmentCurveNormals: WebGLBuffer;
|
||||
|
||||
readonly bQuadPathRanges: Range[];
|
||||
readonly bQuadVertexPositionPathRanges: Range[];
|
||||
readonly bVertexPathRanges: Range[];
|
||||
readonly coverInteriorIndexRanges: Range[];
|
||||
readonly coverCurveIndexRanges: Range[];
|
||||
readonly edgeBoundingBoxRanges: Range[];
|
||||
readonly edgeUpperLineIndexRanges: Range[];
|
||||
readonly edgeUpperCurveIndexRanges: Range[];
|
||||
readonly edgeLowerLineIndexRanges: Range[];
|
||||
readonly edgeLowerCurveIndexRanges: Range[];
|
||||
readonly bQuadVertexInteriorIndexPathRanges: Range[];
|
||||
readonly segmentCurveRanges: Range[];
|
||||
readonly segmentLineRanges: Range[];
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
import * as glmatrix from 'gl-matrix';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import {AntialiasingStrategy, AntialiasingStrategyName, GammaCorrectionMode} from './aa-strategy';
|
||||
import {AntialiasingStrategy, AntialiasingStrategyName, DirectRenderingMode} from './aa-strategy';
|
||||
import {GammaCorrectionMode} from './aa-strategy';
|
||||
import {TileInfo} from './aa-strategy';
|
||||
import {NoAAStrategy, StemDarkeningMode, SubpixelAAType} from './aa-strategy';
|
||||
import {AAOptions} from './app-controller';
|
||||
|
@ -24,6 +25,8 @@ import {RenderContext, Timings} from "./view";
|
|||
|
||||
const MAX_PATHS: number = 65535;
|
||||
|
||||
const MAX_VERTICES: number = 4 * 1024 * 1024;
|
||||
|
||||
const TIME_INTERVAL_DELAY: number = 32;
|
||||
|
||||
const B_LOOP_BLINN_DATA_SIZE: number = 4;
|
||||
|
@ -92,6 +95,7 @@ export abstract class Renderer {
|
|||
private gammaLUTTexture: WebGLTexture | null;
|
||||
|
||||
private instancedPathIDVBO: WebGLBuffer | null;
|
||||
private vertexIDVBO: WebGLBuffer | null;
|
||||
private timerQueryPollInterval: number | null;
|
||||
|
||||
constructor(renderContext: RenderContext) {
|
||||
|
@ -110,6 +114,7 @@ export abstract class Renderer {
|
|||
if (this.pathIDsAreInstanced)
|
||||
this.initInstancedPathIDVBO();
|
||||
|
||||
this.initVertexIDVBO();
|
||||
this.initGammaLUTTexture();
|
||||
|
||||
this.antialiasingStrategy = new NoAAStrategy(0, 'none');
|
||||
|
@ -154,6 +159,17 @@ export abstract class Renderer {
|
|||
|
||||
const objectCount = this.objectCount;
|
||||
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
|
||||
if (antialiasingStrategy.directRenderingMode !== 'none') {
|
||||
// Prepare for direct rendering.
|
||||
antialiasingStrategy.prepareToRenderObject(this, objectIndex);
|
||||
|
||||
// Clear.
|
||||
this.clearForDirectRendering(objectIndex);
|
||||
|
||||
// Perform direct rendering (Loop-Blinn).
|
||||
this.directlyRenderObject(pass, objectIndex);
|
||||
}
|
||||
|
||||
// Antialias.
|
||||
antialiasingStrategy.antialiasObject(this, objectIndex);
|
||||
|
||||
|
@ -168,17 +184,6 @@ export abstract class Renderer {
|
|||
renderContext.compositingTimerQuery);
|
||||
}
|
||||
|
||||
// Perform direct rendering (Loop-Blinn).
|
||||
if (antialiasingStrategy.directRenderingMode !== 'none') {
|
||||
// Prepare for direct rendering.
|
||||
antialiasingStrategy.prepareToRenderObject(this, objectIndex);
|
||||
|
||||
// Clear.
|
||||
this.clearForDirectRendering(objectIndex);
|
||||
|
||||
this.directlyRenderObject(pass, objectIndex);
|
||||
}
|
||||
|
||||
// Perform post-antialiasing tasks.
|
||||
antialiasingStrategy.finishAntialiasingObject(this, objectIndex);
|
||||
|
||||
|
@ -351,7 +356,7 @@ export abstract class Renderer {
|
|||
pathRangeForObject(objectIndex: number): Range {
|
||||
if (this.meshes == null)
|
||||
return new Range(0, 0);
|
||||
const bVertexPathRanges = this.meshes[objectIndex].bVertexPathRanges;
|
||||
const bVertexPathRanges = this.meshes[objectIndex].bQuadVertexPositionPathRanges;
|
||||
return new Range(1, bVertexPathRanges.length + 1);
|
||||
}
|
||||
|
||||
|
@ -382,7 +387,8 @@ export abstract class Renderer {
|
|||
PathTransformBuffers<Float32Array>;
|
||||
|
||||
protected abstract directCurveProgramName(): keyof ShaderMap<void>;
|
||||
protected abstract directInteriorProgramName(): keyof ShaderMap<void>;
|
||||
protected abstract directInteriorProgramName(renderingMode: DirectRenderingMode):
|
||||
keyof ShaderMap<void>;
|
||||
|
||||
protected drawSceneryIfNecessary(): void {}
|
||||
|
||||
|
@ -409,17 +415,9 @@ export abstract class Renderer {
|
|||
return;
|
||||
|
||||
gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
|
||||
|
||||
switch (renderingMode) {
|
||||
case 'color':
|
||||
gl.clearDepth(0.0);
|
||||
gl.depthMask(true);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
break;
|
||||
case 'none':
|
||||
// Nothing to do.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected getModelviewTransform(pathIndex: number): glmatrix.mat4 {
|
||||
|
@ -460,24 +458,27 @@ export abstract class Renderer {
|
|||
const pathRange = this.pathRangeForObject(objectIndex);
|
||||
const meshIndex = this.meshIndexForObject(objectIndex);
|
||||
|
||||
const meshes = this.meshes[meshIndex];
|
||||
const meshData = this.meshData[meshIndex];
|
||||
const meshes = this.meshes![meshIndex];
|
||||
const meshData = this.meshData![meshIndex];
|
||||
|
||||
// Set up implicit cover state.
|
||||
gl.depthFunc(gl.GREATER);
|
||||
gl.depthMask(true);
|
||||
gl.enable(gl.DEPTH_TEST);
|
||||
gl.disable(gl.BLEND);
|
||||
gl.cullFace(gl.BACK);
|
||||
gl.frontFace(gl.CCW);
|
||||
gl.enable(gl.CULL_FACE);
|
||||
|
||||
// Set up the implicit cover interior VAO.
|
||||
const directInteriorProgramName = this.directInteriorProgramName();
|
||||
const directInteriorProgramName = this.directInteriorProgramName(renderingMode);
|
||||
const directInteriorProgram = renderContext.shaderPrograms[directInteriorProgramName];
|
||||
if (this.implicitCoverInteriorVAO == null) {
|
||||
this.implicitCoverInteriorVAO = renderContext.vertexArrayObjectExt
|
||||
.createVertexArrayOES();
|
||||
}
|
||||
renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.implicitCoverInteriorVAO);
|
||||
this.initImplicitCoverInteriorVAO(objectIndex, instanceRange);
|
||||
this.initImplicitCoverInteriorVAO(objectIndex, instanceRange, renderingMode);
|
||||
|
||||
// Draw direct interior parts.
|
||||
this.setTransformUniform(directInteriorProgram.uniforms, pass, objectIndex);
|
||||
|
@ -489,21 +490,26 @@ export abstract class Renderer {
|
|||
this.pathTransformBufferTextures[meshIndex]
|
||||
.ext
|
||||
.bind(gl, directInteriorProgram.uniforms, 2);
|
||||
const coverInteriorRange = getMeshIndexRange(meshes.coverInteriorIndexRanges, pathRange);
|
||||
const bQuadInteriorRange = getMeshIndexRange(meshes.bQuadVertexInteriorIndexPathRanges,
|
||||
pathRange);
|
||||
if (!this.pathIDsAreInstanced) {
|
||||
gl.drawElements(gl.TRIANGLES,
|
||||
coverInteriorRange.length,
|
||||
bQuadInteriorRange.length,
|
||||
gl.UNSIGNED_INT,
|
||||
coverInteriorRange.start * UINT32_SIZE);
|
||||
bQuadInteriorRange.start * UINT32_SIZE);
|
||||
} else {
|
||||
renderContext.instancedArraysExt
|
||||
.drawElementsInstancedANGLE(gl.TRIANGLES,
|
||||
coverInteriorRange.length,
|
||||
bQuadInteriorRange.length,
|
||||
gl.UNSIGNED_INT,
|
||||
0,
|
||||
instanceRange.length);
|
||||
}
|
||||
|
||||
gl.disable(gl.CULL_FACE);
|
||||
|
||||
// Render curves, if applicable.
|
||||
if (renderingMode !== 'conservative') {
|
||||
// Set up direct curve state.
|
||||
gl.depthMask(false);
|
||||
gl.enable(gl.BLEND);
|
||||
|
@ -515,8 +521,10 @@ export abstract class Renderer {
|
|||
// TODO(pcwalton): Cache these.
|
||||
const directCurveProgramName = this.directCurveProgramName();
|
||||
const directCurveProgram = renderContext.shaderPrograms[directCurveProgramName];
|
||||
if (this.implicitCoverCurveVAO == null)
|
||||
this.implicitCoverCurveVAO = renderContext.vertexArrayObjectExt.createVertexArrayOES();
|
||||
if (this.implicitCoverCurveVAO == null) {
|
||||
this.implicitCoverCurveVAO = renderContext.vertexArrayObjectExt
|
||||
.createVertexArrayOES();
|
||||
}
|
||||
renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.implicitCoverCurveVAO);
|
||||
this.initImplicitCoverCurveVAO(objectIndex, instanceRange);
|
||||
|
||||
|
@ -526,21 +534,24 @@ export abstract class Renderer {
|
|||
this.setHintsUniform(directCurveProgram.uniforms);
|
||||
this.setPathColorsUniform(objectIndex, directCurveProgram.uniforms, 0);
|
||||
this.setEmboldenAmountUniform(objectIndex, directCurveProgram.uniforms);
|
||||
this.pathTransformBufferTextures[meshIndex].st.bind(gl, directCurveProgram.uniforms, 1);
|
||||
this.pathTransformBufferTextures[meshIndex].ext.bind(gl, directCurveProgram.uniforms, 2);
|
||||
const coverCurveRange = getMeshIndexRange(meshes.coverCurveIndexRanges, pathRange);
|
||||
this.pathTransformBufferTextures[meshIndex]
|
||||
.st
|
||||
.bind(gl, directCurveProgram.uniforms, 1);
|
||||
this.pathTransformBufferTextures[meshIndex]
|
||||
.ext
|
||||
.bind(gl, directCurveProgram.uniforms, 2);
|
||||
const coverCurveRange = getMeshIndexRange(meshes.bQuadVertexPositionPathRanges,
|
||||
pathRange);
|
||||
if (!this.pathIDsAreInstanced) {
|
||||
gl.drawElements(gl.TRIANGLES,
|
||||
coverCurveRange.length,
|
||||
gl.UNSIGNED_INT,
|
||||
coverCurveRange.start * UINT32_SIZE);
|
||||
gl.drawArrays(gl.TRIANGLES, coverCurveRange.start * 6, coverCurveRange.length * 6);
|
||||
} else {
|
||||
renderContext.instancedArraysExt.drawElementsInstancedANGLE(gl.TRIANGLES,
|
||||
coverCurveRange.length,
|
||||
gl.UNSIGNED_INT,
|
||||
renderContext.instancedArraysExt
|
||||
.drawArraysInstancedANGLE(gl.TRIANGLES,
|
||||
0,
|
||||
coverCurveRange.length * 6,
|
||||
instanceRange.length);
|
||||
}
|
||||
}
|
||||
|
||||
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
|
||||
|
||||
|
@ -548,7 +559,7 @@ export abstract class Renderer {
|
|||
antialiasingStrategy.finishDirectlyRenderingObject(this, objectIndex);
|
||||
}
|
||||
|
||||
private finishTiming() {
|
||||
private finishTiming(): void {
|
||||
const renderContext = this.renderContext;
|
||||
|
||||
if (this.timerQueryPollInterval != null)
|
||||
|
@ -613,17 +624,20 @@ export abstract class Renderer {
|
|||
|
||||
const meshIndex = this.meshIndexForObject(objectIndex);
|
||||
const meshes = this.meshes[meshIndex];
|
||||
const meshData = unwrapNull(this.meshData)[meshIndex];
|
||||
|
||||
const directCurveProgramName = this.directCurveProgramName();
|
||||
const directCurveProgram = renderContext.shaderPrograms[directCurveProgramName];
|
||||
gl.useProgram(directCurveProgram.program);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bVertexPositions);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositions);
|
||||
gl.vertexAttribPointer(directCurveProgram.attributes.aPosition, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexIDVBO);
|
||||
gl.vertexAttribPointer(directCurveProgram.attributes.aVertexID, 1, gl.FLOAT, false, 0, 0);
|
||||
|
||||
if (this.pathIDsAreInstanced)
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.instancedPathIDVBO);
|
||||
else
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bVertexPathIDs);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositionPathIDs);
|
||||
gl.vertexAttribPointer(directCurveProgram.attributes.aPathID,
|
||||
1,
|
||||
gl.UNSIGNED_SHORT,
|
||||
|
@ -635,35 +649,15 @@ export abstract class Renderer {
|
|||
.vertexAttribDivisorANGLE(directCurveProgram.attributes.aPathID, 1);
|
||||
}
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bVertexLoopBlinnData);
|
||||
gl.vertexAttribPointer(directCurveProgram.attributes.aTexCoord,
|
||||
2,
|
||||
gl.UNSIGNED_BYTE,
|
||||
false,
|
||||
B_LOOP_BLINN_DATA_SIZE,
|
||||
B_LOOP_BLINN_DATA_TEX_COORD_OFFSET);
|
||||
gl.vertexAttribPointer(directCurveProgram.attributes.aSign,
|
||||
1,
|
||||
gl.BYTE,
|
||||
false,
|
||||
B_LOOP_BLINN_DATA_SIZE,
|
||||
B_LOOP_BLINN_DATA_SIGN_OFFSET);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bVertexNormals);
|
||||
gl.vertexAttribPointer(directCurveProgram.attributes.aNormalAngle,
|
||||
1,
|
||||
gl.FLOAT,
|
||||
false,
|
||||
FLOAT32_SIZE,
|
||||
0);
|
||||
gl.enableVertexAttribArray(directCurveProgram.attributes.aPosition);
|
||||
gl.enableVertexAttribArray(directCurveProgram.attributes.aTexCoord);
|
||||
gl.enableVertexAttribArray(directCurveProgram.attributes.aVertexID);
|
||||
gl.enableVertexAttribArray(directCurveProgram.attributes.aPathID);
|
||||
gl.enableVertexAttribArray(directCurveProgram.attributes.aSign);
|
||||
gl.enableVertexAttribArray(directCurveProgram.attributes.aNormalAngle);
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, meshes.coverCurveIndices);
|
||||
}
|
||||
|
||||
private initImplicitCoverInteriorVAO(objectIndex: number, instanceRange: Range): void {
|
||||
private initImplicitCoverInteriorVAO(objectIndex: number,
|
||||
instanceRange: Range,
|
||||
renderingMode: DirectRenderingMode):
|
||||
void {
|
||||
if (this.meshes == null)
|
||||
return;
|
||||
|
||||
|
@ -673,10 +667,10 @@ export abstract class Renderer {
|
|||
const meshIndex = this.meshIndexForObject(objectIndex);
|
||||
const meshes = this.meshes[meshIndex];
|
||||
|
||||
const directInteriorProgramName = this.directInteriorProgramName();
|
||||
const directInteriorProgramName = this.directInteriorProgramName(renderingMode);
|
||||
const directInteriorProgram = renderContext.shaderPrograms[directInteriorProgramName];
|
||||
gl.useProgram(directInteriorProgram.program);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bVertexPositions);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositions);
|
||||
gl.vertexAttribPointer(directInteriorProgram.attributes.aPosition,
|
||||
2,
|
||||
gl.FLOAT,
|
||||
|
@ -687,7 +681,7 @@ export abstract class Renderer {
|
|||
if (this.pathIDsAreInstanced)
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.instancedPathIDVBO);
|
||||
else
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bVertexPathIDs);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositionPathIDs);
|
||||
gl.vertexAttribPointer(directInteriorProgram.attributes.aPathID,
|
||||
1,
|
||||
gl.UNSIGNED_SHORT,
|
||||
|
@ -699,18 +693,21 @@ export abstract class Renderer {
|
|||
.vertexAttribDivisorANGLE(directInteriorProgram.attributes.aPathID, 1);
|
||||
}
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bVertexNormals);
|
||||
gl.vertexAttribPointer(directInteriorProgram.attributes.aNormalAngle,
|
||||
if (directInteriorProgramName === 'conservativeInterior') {
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexIDVBO);
|
||||
gl.vertexAttribPointer(directInteriorProgram.attributes.aVertexID,
|
||||
1,
|
||||
gl.FLOAT,
|
||||
false,
|
||||
FLOAT32_SIZE,
|
||||
0,
|
||||
0);
|
||||
}
|
||||
|
||||
gl.enableVertexAttribArray(directInteriorProgram.attributes.aPosition);
|
||||
gl.enableVertexAttribArray(directInteriorProgram.attributes.aPathID);
|
||||
gl.enableVertexAttribArray(directInteriorProgram.attributes.aNormalAngle);
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, meshes.coverInteriorIndices);
|
||||
if (directInteriorProgramName === 'conservativeInterior')
|
||||
gl.enableVertexAttribArray(directInteriorProgram.attributes.aVertexID);
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, meshes.bQuadVertexInteriorIndices);
|
||||
}
|
||||
|
||||
private initInstancedPathIDVBO(): void {
|
||||
|
@ -721,11 +718,25 @@ export abstract class Renderer {
|
|||
for (let pathIndex = 0; pathIndex < MAX_PATHS; pathIndex++)
|
||||
pathIDs[pathIndex] = pathIndex + 1;
|
||||
|
||||
this.instancedPathIDVBO = renderContext.gl.createBuffer();
|
||||
this.instancedPathIDVBO = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.instancedPathIDVBO);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, pathIDs, gl.STATIC_DRAW);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
||||
}
|
||||
|
||||
private initVertexIDVBO(): void {
|
||||
const renderContext = this.renderContext;
|
||||
const gl = renderContext.gl;
|
||||
|
||||
const vertexIDs = new Float32Array(MAX_VERTICES);
|
||||
for (let vertexID = 0; vertexID < MAX_VERTICES; vertexID++)
|
||||
vertexIDs[vertexID] = vertexID;
|
||||
|
||||
this.vertexIDVBO = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexIDVBO);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, vertexIDs, gl.STATIC_DRAW);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
||||
}
|
||||
}
|
||||
|
||||
function getMeshIndexRange(indexRanges: Range[], pathRange: Range): Range {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {expectNotNull, PathfinderError, unwrapNull} from './utils';
|
|||
export interface ShaderMap<T> {
|
||||
blitLinear: T;
|
||||
blitGamma: T;
|
||||
conservativeInterior: T;
|
||||
demo3DDistantGlyph: T;
|
||||
demo3DMonument: T;
|
||||
directCurve: T;
|
||||
|
@ -39,6 +40,7 @@ const COMMON_SHADER_URL: string = '/glsl/gles2/common.inc.glsl';
|
|||
export const SHADER_NAMES: Array<keyof ShaderMap<void>> = [
|
||||
'blitLinear',
|
||||
'blitGamma',
|
||||
'conservativeInterior',
|
||||
'directCurve',
|
||||
'directInterior',
|
||||
'direct3DCurve',
|
||||
|
@ -63,6 +65,10 @@ const SHADER_URLS: ShaderMap<ShaderProgramURLs> = {
|
|||
fragment: "/glsl/gles2/blit-linear.fs.glsl",
|
||||
vertex: "/glsl/gles2/blit.vs.glsl",
|
||||
},
|
||||
conservativeInterior: {
|
||||
fragment: "/glsl/gles2/direct-interior.fs.glsl",
|
||||
vertex: "/glsl/gles2/conservative-interior.vs.glsl",
|
||||
},
|
||||
demo3DDistantGlyph: {
|
||||
fragment: "/glsl/gles2/demo-3d-distant-glyph.fs.glsl",
|
||||
vertex: "/glsl/gles2/demo-3d-distant-glyph.vs.glsl",
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
|
||||
import * as glmatrix from 'gl-matrix';
|
||||
|
||||
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy';
|
||||
import {AntialiasingStrategy, AntialiasingStrategyName, DirectRenderingMode} from './aa-strategy';
|
||||
import {NoAAStrategy} from './aa-strategy';
|
||||
import {SubpixelAAType} from './aa-strategy';
|
||||
import {OrthographicCamera} from "./camera";
|
||||
import {UniformMap} from './gl-utils';
|
||||
|
@ -163,8 +164,9 @@ export abstract class SVGRenderer extends Renderer {
|
|||
return 'directCurve';
|
||||
}
|
||||
|
||||
protected directInteriorProgramName(): keyof ShaderMap<void> {
|
||||
return 'directInterior';
|
||||
protected directInteriorProgramName(renderingMode: DirectRenderingMode):
|
||||
keyof ShaderMap<void> {
|
||||
return renderingMode === 'conservative' ? 'conservativeInterior' : 'directInterior';
|
||||
}
|
||||
|
||||
protected pathColorsForObject(objectIndex: number): Uint8Array {
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
import * as glmatrix from 'gl-matrix';
|
||||
|
||||
export const FLOAT32_SIZE: number = 4;
|
||||
|
||||
export const UINT16_SIZE: number = 2;
|
||||
export const UINT8_SIZE: number = 1;
|
||||
|
||||
export const UINT32_MAX: number = 0xffffffff;
|
||||
export const UINT32_SIZE: number = 4;
|
||||
|
|
|
@ -23,17 +23,17 @@ import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader';
|
|||
import {expectNotNull, PathfinderError, UINT32_SIZE, unwrapNull} from './utils';
|
||||
|
||||
const QUAD_POSITIONS: Float32Array = new Float32Array([
|
||||
0.0, 1.0,
|
||||
1.0, 1.0,
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
0.0, 1.0,
|
||||
1.0, 1.0,
|
||||
]);
|
||||
|
||||
const QUAD_TEX_COORDS: Float32Array = new Float32Array([
|
||||
0.0, 1.0,
|
||||
1.0, 1.0,
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
0.0, 1.0,
|
||||
1.0, 1.0,
|
||||
]);
|
||||
|
||||
export const TIMINGS: {[name: string]: string} = {
|
||||
|
|
|
@ -33,15 +33,33 @@ type Direction = 'upper' | 'lower';
|
|||
|
||||
const DIRECTIONS: Direction[] = ['upper', 'lower'];
|
||||
|
||||
const PATCH_VERTICES: Float32Array = new Float32Array([
|
||||
0.0, 0.0,
|
||||
0.5, 0.0,
|
||||
1.0, 0.0,
|
||||
0.0, 1.0,
|
||||
0.5, 1.0,
|
||||
1.0, 1.0,
|
||||
]);
|
||||
|
||||
const MCAA_PATCH_INDICES: Uint8Array = new Uint8Array([0, 1, 2, 0, 2, 3, 2, 5, 3, 3, 5, 4]);
|
||||
|
||||
const ECAA_CURVE_PATCH_INDICES: Uint8Array = new Uint8Array([0, 1, 2, 0, 2, 3, 2, 5, 3]);
|
||||
|
||||
export abstract class XCAAStrategy extends AntialiasingStrategy {
|
||||
abstract readonly directRenderingMode: DirectRenderingMode;
|
||||
|
||||
protected patchVertexBuffer: WebGLBuffer | null;
|
||||
protected patchIndexBuffer: WebGLBuffer | null;
|
||||
|
||||
get passCount(): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected abstract get usesDilationTransforms(): boolean;
|
||||
|
||||
protected abstract get patchIndices(): Uint8Array;
|
||||
|
||||
protected pathBoundsBufferTextures: PathfinderBufferTexture[];
|
||||
|
||||
protected supersampledFramebufferSize: glmatrix.vec2;
|
||||
|
@ -72,8 +90,22 @@ export abstract class XCAAStrategy extends AntialiasingStrategy {
|
|||
|
||||
attachMeshes(renderer: Renderer): void {
|
||||
const renderContext = renderer.renderContext;
|
||||
const gl = renderContext.gl;
|
||||
|
||||
this.createResolveVAO(renderer);
|
||||
this.pathBoundsBufferTextures = [];
|
||||
|
||||
this.patchVertexBuffer = unwrapNull(gl.createBuffer());
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.patchVertexBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, PATCH_VERTICES.buffer as ArrayBuffer, gl.STATIC_DRAW);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
||||
|
||||
this.patchIndexBuffer = unwrapNull(gl.createBuffer());
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.patchIndexBuffer);
|
||||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,
|
||||
this.patchIndices.buffer as ArrayBuffer,
|
||||
gl.STATIC_DRAW);
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
|
||||
}
|
||||
|
||||
setFramebufferSize(renderer: Renderer): void {
|
||||
|
@ -336,6 +368,10 @@ export abstract class XCAAStrategy extends AntialiasingStrategy {
|
|||
export class MCAAStrategy extends XCAAStrategy {
|
||||
protected vao: WebGLVertexArrayObject | null;
|
||||
|
||||
protected get patchIndices(): Uint8Array {
|
||||
return MCAA_PATCH_INDICES;
|
||||
}
|
||||
|
||||
protected get usesDilationTransforms(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
@ -348,7 +384,10 @@ export class MCAAStrategy extends XCAAStrategy {
|
|||
super.attachMeshes(renderer);
|
||||
|
||||
const renderContext = renderer.renderContext;
|
||||
const gl = renderContext.gl;
|
||||
|
||||
this.vao = renderContext.vertexArrayObjectExt.createVertexArrayOES();
|
||||
|
||||
}
|
||||
|
||||
antialiasObject(renderer: Renderer, objectIndex: number): void {
|
||||
|
@ -372,14 +411,13 @@ export class MCAAStrategy extends XCAAStrategy {
|
|||
}
|
||||
|
||||
protected clearForAA(renderer: Renderer): void {
|
||||
if (!this.usesAAFramebuffer(renderer))
|
||||
return;
|
||||
|
||||
const renderContext = renderer.renderContext;
|
||||
const gl = renderContext.gl;
|
||||
|
||||
if (renderer.isMulticolor)
|
||||
gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
||||
else
|
||||
gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
gl.clearDepth(0.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
@ -388,7 +426,17 @@ export class MCAAStrategy extends XCAAStrategy {
|
|||
const renderContext = renderer.renderContext;
|
||||
const gl = renderContext.gl;
|
||||
|
||||
if (this.directRenderingMode !== 'conservative') {
|
||||
gl.disable(gl.DEPTH_TEST);
|
||||
return;
|
||||
}
|
||||
|
||||
gl.depthFunc(gl.GREATER);
|
||||
gl.depthMask(false);
|
||||
gl.enable(gl.DEPTH_TEST);
|
||||
gl.frontFace(gl.CCW);
|
||||
gl.cullFace(gl.BACK);
|
||||
gl.enable(gl.CULL_FACE);
|
||||
}
|
||||
|
||||
protected clearForResolve(renderer: Renderer): void {
|
||||
|
@ -396,7 +444,7 @@ export class MCAAStrategy extends XCAAStrategy {
|
|||
const gl = renderContext.gl;
|
||||
|
||||
if (!renderer.isMulticolor) {
|
||||
gl.clearColor(1.0, 1.0, 0.0, 1.0);
|
||||
gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
}
|
||||
|
@ -437,53 +485,80 @@ export class MCAAStrategy extends XCAAStrategy {
|
|||
const vao = this.vao;
|
||||
renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao);
|
||||
|
||||
const bQuadRanges = renderer.meshData[meshIndex].bQuadPathRanges;
|
||||
const bQuadRanges = renderer.meshData[meshIndex].bQuadVertexPositionPathRanges;
|
||||
const offset = calculateStartFromIndexRanges(pathRange, bQuadRanges);
|
||||
|
||||
gl.useProgram(shaderProgram.program);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, renderContext.quadPositionsBuffer);
|
||||
gl.vertexAttribPointer(attributes.aQuadPosition, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.patchVertexBuffer);
|
||||
gl.vertexAttribPointer(attributes.aTessCoord, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, renderer.meshes[meshIndex].bQuadVertexPositions);
|
||||
gl.vertexAttribPointer(attributes.aUpperEndpointPositions,
|
||||
4,
|
||||
gl.vertexAttribPointer(attributes.aUpperLeftEndpointPosition,
|
||||
2,
|
||||
gl.FLOAT,
|
||||
false,
|
||||
FLOAT32_SIZE * 12,
|
||||
FLOAT32_SIZE * 12 * offset);
|
||||
gl.vertexAttribPointer(attributes.aLowerEndpointPositions,
|
||||
4,
|
||||
FLOAT32_SIZE * 12 * offset + FLOAT32_SIZE * 0);
|
||||
gl.vertexAttribPointer(attributes.aUpperControlPointPosition,
|
||||
2,
|
||||
gl.FLOAT,
|
||||
false,
|
||||
FLOAT32_SIZE * 12,
|
||||
FLOAT32_SIZE * 12 * offset + FLOAT32_SIZE * 2);
|
||||
gl.vertexAttribPointer(attributes.aUpperRightEndpointPosition,
|
||||
2,
|
||||
gl.FLOAT,
|
||||
false,
|
||||
FLOAT32_SIZE * 12,
|
||||
FLOAT32_SIZE * 12 * offset + FLOAT32_SIZE * 4);
|
||||
gl.vertexAttribPointer(attributes.aControlPointPositions,
|
||||
4,
|
||||
gl.vertexAttribPointer(attributes.aLowerRightEndpointPosition,
|
||||
2,
|
||||
gl.FLOAT,
|
||||
false,
|
||||
FLOAT32_SIZE * 12,
|
||||
FLOAT32_SIZE * 12 * offset + FLOAT32_SIZE * 6);
|
||||
gl.vertexAttribPointer(attributes.aLowerControlPointPosition,
|
||||
2,
|
||||
gl.FLOAT,
|
||||
false,
|
||||
FLOAT32_SIZE * 12,
|
||||
FLOAT32_SIZE * 12 * offset + FLOAT32_SIZE * 8);
|
||||
gl.vertexAttribPointer(attributes.aLowerLeftEndpointPosition,
|
||||
2,
|
||||
gl.FLOAT,
|
||||
false,
|
||||
FLOAT32_SIZE * 12,
|
||||
FLOAT32_SIZE * 12 * offset + FLOAT32_SIZE * 10);
|
||||
renderContext.instancedArraysExt
|
||||
.vertexAttribDivisorANGLE(attributes.aUpperEndpointPositions, 1);
|
||||
.vertexAttribDivisorANGLE(attributes.aUpperLeftEndpointPosition, 1);
|
||||
renderContext.instancedArraysExt
|
||||
.vertexAttribDivisorANGLE(attributes.aLowerEndpointPositions, 1);
|
||||
.vertexAttribDivisorANGLE(attributes.aUpperControlPointPosition, 1);
|
||||
renderContext.instancedArraysExt
|
||||
.vertexAttribDivisorANGLE(attributes.aControlPointPositions, 1);
|
||||
.vertexAttribDivisorANGLE(attributes.aUpperRightEndpointPosition, 1);
|
||||
renderContext.instancedArraysExt
|
||||
.vertexAttribDivisorANGLE(attributes.aLowerRightEndpointPosition, 1);
|
||||
renderContext.instancedArraysExt
|
||||
.vertexAttribDivisorANGLE(attributes.aLowerControlPointPosition, 1);
|
||||
renderContext.instancedArraysExt
|
||||
.vertexAttribDivisorANGLE(attributes.aLowerLeftEndpointPosition, 1);
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, renderer.meshes[meshIndex].edgeBoundingBoxPathIDs);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, renderer.meshes[meshIndex].bQuadVertexPositionPathIDs);
|
||||
gl.vertexAttribPointer(attributes.aPathID,
|
||||
1,
|
||||
gl.UNSIGNED_SHORT,
|
||||
false,
|
||||
0,
|
||||
UINT16_SIZE * 6,
|
||||
UINT16_SIZE * offset);
|
||||
renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1);
|
||||
|
||||
gl.enableVertexAttribArray(attributes.aQuadPosition);
|
||||
gl.enableVertexAttribArray(attributes.aUpperEndpointPositions);
|
||||
gl.enableVertexAttribArray(attributes.aLowerEndpointPositions);
|
||||
gl.enableVertexAttribArray(attributes.aControlPointPositions);
|
||||
gl.enableVertexAttribArray(attributes.aTessCoord);
|
||||
gl.enableVertexAttribArray(attributes.aUpperLeftEndpointPosition);
|
||||
gl.enableVertexAttribArray(attributes.aUpperControlPointPosition);
|
||||
gl.enableVertexAttribArray(attributes.aUpperRightEndpointPosition);
|
||||
gl.enableVertexAttribArray(attributes.aLowerRightEndpointPosition);
|
||||
gl.enableVertexAttribArray(attributes.aLowerControlPointPosition);
|
||||
gl.enableVertexAttribArray(attributes.aLowerLeftEndpointPosition);
|
||||
gl.enableVertexAttribArray(attributes.aPathID);
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer);
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.patchIndexBuffer);
|
||||
|
||||
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
|
||||
}
|
||||
|
@ -516,18 +591,22 @@ export class MCAAStrategy extends XCAAStrategy {
|
|||
renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao);
|
||||
|
||||
this.setBlendModeForAA(renderer);
|
||||
this.setAADepthState(renderer);
|
||||
|
||||
const bQuadRanges = renderer.meshData[meshIndex].bQuadPathRanges;
|
||||
const bQuadRanges = renderer.meshData[meshIndex].bQuadVertexPositionPathRanges;
|
||||
const count = calculateCountFromIndexRanges(pathRange, bQuadRanges);
|
||||
|
||||
renderContext.instancedArraysExt
|
||||
.drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count);
|
||||
.drawElementsInstancedANGLE(gl.TRIANGLES, 12, gl.UNSIGNED_BYTE, 0, count);
|
||||
|
||||
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
|
||||
gl.disable(gl.DEPTH_TEST);
|
||||
gl.disable(gl.CULL_FACE);
|
||||
}
|
||||
|
||||
get directRenderingMode(): DirectRenderingMode {
|
||||
return 'none';
|
||||
// FIXME(pcwalton): Only in multicolor mode?
|
||||
return 'conservative';
|
||||
}
|
||||
|
||||
protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number):
|
||||
|
@ -544,6 +623,10 @@ export class MCAAStrategy extends XCAAStrategy {
|
|||
}
|
||||
|
||||
export class ECAAStrategy extends XCAAStrategy {
|
||||
protected get patchIndices(): Uint8Array {
|
||||
return ECAA_CURVE_PATCH_INDICES;
|
||||
}
|
||||
|
||||
protected get lineShaderProgramNames(): Array<keyof ShaderMap<void>> {
|
||||
return ['ecaaLine'];
|
||||
}
|
||||
|
@ -588,6 +671,11 @@ export class ECAAStrategy extends XCAAStrategy {
|
|||
objectIndex,
|
||||
this.curveShaderProgramNames[0],
|
||||
this.curveShaderProgramNames[1]);
|
||||
|
||||
const renderContext = renderer.renderContext;
|
||||
const gl = renderContext.gl;
|
||||
|
||||
gl.disable(gl.CULL_FACE);
|
||||
}
|
||||
|
||||
protected usesAAFramebuffer(): boolean {
|
||||
|
@ -619,7 +707,13 @@ export class ECAAStrategy extends XCAAStrategy {
|
|||
|
||||
protected setAADepthState(renderer: Renderer): void {
|
||||
const renderContext = renderer.renderContext;
|
||||
renderContext.gl.disable(renderContext.gl.DEPTH_TEST);
|
||||
const gl = renderContext.gl;
|
||||
|
||||
gl.disable(gl.DEPTH_TEST);
|
||||
|
||||
gl.frontFace(gl.CCW);
|
||||
gl.cullFace(gl.BACK);
|
||||
gl.enable(gl.CULL_FACE);
|
||||
}
|
||||
|
||||
protected clearForResolve(renderer: Renderer): void {
|
||||
|
@ -699,7 +793,7 @@ export class ECAAStrategy extends XCAAStrategy {
|
|||
// FIXME(pcwalton): Only render the appropriate instances.
|
||||
const count = renderer.meshData[meshIndex].segmentCurveCount;
|
||||
renderContext.instancedArraysExt
|
||||
.drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count);
|
||||
.drawElementsInstancedANGLE(gl.TRIANGLES, 9, gl.UNSIGNED_BYTE, 0, count);
|
||||
|
||||
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
|
||||
}
|
||||
|
@ -735,7 +829,7 @@ export class ECAAStrategy extends XCAAStrategy {
|
|||
|
||||
gl.useProgram(lineProgram.program);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, renderContext.quadPositionsBuffer);
|
||||
gl.vertexAttribPointer(attributes.aQuadPosition, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.vertexAttribPointer(attributes.aTessCoord, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexPositionsBuffer);
|
||||
gl.vertexAttribPointer(attributes.aLeftPosition,
|
||||
2,
|
||||
|
@ -752,7 +846,7 @@ export class ECAAStrategy extends XCAAStrategy {
|
|||
gl.bindBuffer(gl.ARRAY_BUFFER, linePathIDsBuffer);
|
||||
gl.vertexAttribPointer(attributes.aPathID, 1, gl.UNSIGNED_SHORT, false, 0, 0);
|
||||
|
||||
gl.enableVertexAttribArray(attributes.aQuadPosition);
|
||||
gl.enableVertexAttribArray(attributes.aTessCoord);
|
||||
gl.enableVertexAttribArray(attributes.aLeftPosition);
|
||||
gl.enableVertexAttribArray(attributes.aRightPosition);
|
||||
gl.enableVertexAttribArray(attributes.aPathID);
|
||||
|
@ -805,8 +899,8 @@ export class ECAAStrategy extends XCAAStrategy {
|
|||
const curveNormalsBuffer = renderer.meshes[0].segmentCurveNormals;
|
||||
|
||||
gl.useProgram(curveProgram.program);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, renderContext.quadPositionsBuffer);
|
||||
gl.vertexAttribPointer(attributes.aQuadPosition, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.patchVertexBuffer);
|
||||
gl.vertexAttribPointer(attributes.aTessCoord, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, curveVertexPositionsBuffer);
|
||||
gl.vertexAttribPointer(attributes.aLeftPosition,
|
||||
2,
|
||||
|
@ -829,7 +923,7 @@ export class ECAAStrategy extends XCAAStrategy {
|
|||
gl.bindBuffer(gl.ARRAY_BUFFER, curvePathIDsBuffer);
|
||||
gl.vertexAttribPointer(attributes.aPathID, 1, gl.UNSIGNED_SHORT, false, 0, 0);
|
||||
|
||||
gl.enableVertexAttribArray(attributes.aQuadPosition);
|
||||
gl.enableVertexAttribArray(attributes.aTessCoord);
|
||||
gl.enableVertexAttribArray(attributes.aLeftPosition);
|
||||
gl.enableVertexAttribArray(attributes.aControlPointPosition);
|
||||
gl.enableVertexAttribArray(attributes.aRightPosition);
|
||||
|
@ -857,7 +951,7 @@ export class ECAAStrategy extends XCAAStrategy {
|
|||
.vertexAttribDivisorANGLE(attributes.aNormalAngles, 1);
|
||||
}
|
||||
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer);
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.patchIndexBuffer);
|
||||
|
||||
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
|
||||
|
||||
|
|
|
@ -17,9 +17,6 @@ image = "0.17"
|
|||
lazy_static = "0.2"
|
||||
log = "0.3"
|
||||
lru-cache = "0.1"
|
||||
rocket = "0.3"
|
||||
rocket_codegen = "0.3"
|
||||
rocket_contrib = "0.3"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
|
@ -36,6 +33,15 @@ optional = true
|
|||
[dependencies.fontsan]
|
||||
git = "https://github.com/servo/fontsan.git"
|
||||
|
||||
[dependencies.rocket]
|
||||
git = "https://github.com/SergioBenitez/rocket"
|
||||
|
||||
[dependencies.rocket_codegen]
|
||||
git = "https://github.com/SergioBenitez/rocket"
|
||||
|
||||
[dependencies.rocket_contrib]
|
||||
git = "https://github.com/SergioBenitez/rocket"
|
||||
|
||||
[dependencies.pathfinder_font_renderer]
|
||||
path = "../../font-renderer"
|
||||
|
||||
|
@ -44,3 +50,6 @@ path = "../../partitioner"
|
|||
|
||||
[dependencies.pathfinder_path_utils]
|
||||
path = "../../path-utils"
|
||||
|
||||
[patch.crates-io]
|
||||
ring = { git = "https://github.com/SergioBenitez/ring", branch = "v0.12" }
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
#![feature(plugin)]
|
||||
#![feature(decl_macro, plugin)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate app_units;
|
||||
|
|
|
@ -112,16 +112,6 @@ pub unsafe extern fn pf_partitioner_b_vertex_loop_blinn_data<'a>(
|
|||
b_vertex_loop_blinn_data.as_ptr() as *const BVertexLoopBlinnData
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn pf_partitioner_cover_indices<'a>(partitioner: *const Partitioner<'a>,
|
||||
out_cover_indices: *mut CoverIndices) {
|
||||
let cover_indices = &(*partitioner).library().cover_indices;
|
||||
(*out_cover_indices).interior_indices = cover_indices.interior_indices.as_ptr();
|
||||
(*out_cover_indices).interior_indices_len = cover_indices.interior_indices.len() as u32;
|
||||
(*out_cover_indices).curve_indices = cover_indices.curve_indices.as_ptr();
|
||||
(*out_cover_indices).curve_indices_len = cover_indices.curve_indices.len() as u32;
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn pf_init_env_logger() -> u32 {
|
||||
env_logger::init().is_ok() as u32
|
||||
|
|
|
@ -103,9 +103,12 @@ impl BQuad {
|
|||
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct BQuadVertexPositions {
|
||||
pub upper_endpoint_positions: [Point2D<f32>; 2],
|
||||
pub lower_endpoint_positions: [Point2D<f32>; 2],
|
||||
pub control_point_positions: [Point2D<f32>; 2],
|
||||
pub upper_left_vertex_position: Point2D<f32>,
|
||||
pub upper_control_point_position: Point2D<f32>,
|
||||
pub upper_right_vertex_position: Point2D<f32>,
|
||||
pub lower_right_vertex_position: Point2D<f32>,
|
||||
pub lower_control_point_position: Point2D<f32>,
|
||||
pub lower_left_vertex_position: Point2D<f32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
|
@ -124,7 +127,7 @@ pub(crate) enum BVertexKind {
|
|||
ConcaveControlPoint,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct BVertexLoopBlinnData {
|
||||
pub tex_coord: [u8; 2],
|
||||
|
|
|
@ -26,11 +26,10 @@ pub struct MeshLibrary {
|
|||
pub b_quads: Vec<BQuad>,
|
||||
// FIXME(pcwalton): Merge with `b_vertex_positions` below.
|
||||
pub b_quad_vertex_positions: Vec<BQuadVertexPositions>,
|
||||
pub b_quad_vertex_interior_indices: Vec<u32>,
|
||||
pub b_vertex_positions: Vec<Point2D<f32>>,
|
||||
pub b_vertex_loop_blinn_data: Vec<BVertexLoopBlinnData>,
|
||||
pub b_vertex_normals: Vec<f32>,
|
||||
pub cover_indices: MeshLibraryCoverIndices,
|
||||
pub edge_data: MeshLibraryEdgeData,
|
||||
pub segments: MeshLibrarySegments,
|
||||
pub segment_normals: MeshLibrarySegmentNormals,
|
||||
}
|
||||
|
@ -42,11 +41,10 @@ impl MeshLibrary {
|
|||
path_ranges: vec![],
|
||||
b_quads: vec![],
|
||||
b_quad_vertex_positions: vec![],
|
||||
b_quad_vertex_interior_indices: vec![],
|
||||
b_vertex_positions: vec![],
|
||||
b_vertex_loop_blinn_data: vec![],
|
||||
b_vertex_normals: vec![],
|
||||
cover_indices: MeshLibraryCoverIndices::new(),
|
||||
edge_data: MeshLibraryEdgeData::new(),
|
||||
segments: MeshLibrarySegments::new(),
|
||||
segment_normals: MeshLibrarySegmentNormals::new(),
|
||||
}
|
||||
|
@ -56,11 +54,10 @@ impl MeshLibrary {
|
|||
self.path_ranges.clear();
|
||||
self.b_quads.clear();
|
||||
self.b_quad_vertex_positions.clear();
|
||||
self.b_quad_vertex_interior_indices.clear();
|
||||
self.b_vertex_positions.clear();
|
||||
self.b_vertex_loop_blinn_data.clear();
|
||||
self.b_vertex_normals.clear();
|
||||
self.cover_indices.clear();
|
||||
self.edge_data.clear();
|
||||
self.segments.clear();
|
||||
self.segment_normals.clear();
|
||||
}
|
||||
|
@ -86,85 +83,99 @@ impl MeshLibrary {
|
|||
self.b_quads.push(*b_quad);
|
||||
|
||||
let upper_left_position =
|
||||
&self.b_vertex_positions[b_quad.upper_left_vertex_index as usize];
|
||||
self.b_vertex_positions[b_quad.upper_left_vertex_index as usize];
|
||||
let upper_right_position =
|
||||
&self.b_vertex_positions[b_quad.upper_right_vertex_index as usize];
|
||||
self.b_vertex_positions[b_quad.upper_right_vertex_index as usize];
|
||||
let lower_left_position =
|
||||
&self.b_vertex_positions[b_quad.lower_left_vertex_index as usize];
|
||||
self.b_vertex_positions[b_quad.lower_left_vertex_index as usize];
|
||||
let lower_right_position =
|
||||
&self.b_vertex_positions[b_quad.lower_right_vertex_index as usize];
|
||||
self.b_vertex_positions[b_quad.lower_right_vertex_index as usize];
|
||||
|
||||
let mut b_quad_vertex_positions = BQuadVertexPositions {
|
||||
upper_endpoint_positions: [*upper_left_position, *upper_right_position],
|
||||
lower_endpoint_positions: [*lower_left_position, *lower_right_position],
|
||||
control_point_positions: [Point2D::zero(), Point2D::zero()],
|
||||
upper_left_vertex_position: upper_left_position,
|
||||
upper_control_point_position: upper_left_position,
|
||||
upper_right_vertex_position: upper_right_position,
|
||||
lower_left_vertex_position: lower_left_position,
|
||||
lower_control_point_position: lower_right_position,
|
||||
lower_right_vertex_position: lower_right_position,
|
||||
};
|
||||
|
||||
let upper_left_bounding_box_position =
|
||||
Point2D::new(upper_left_position.x,
|
||||
f32::max(upper_left_position.y, upper_right_position.y));
|
||||
let lower_right_bounding_box_position =
|
||||
Point2D::new(lower_right_position.x,
|
||||
f32::max(lower_left_position.y, lower_right_position.y));
|
||||
|
||||
self.edge_data.bounding_box_vertex_positions.push(EdgeBoundingBoxVertexPositions {
|
||||
upper_left: upper_left_bounding_box_position,
|
||||
lower_right: lower_right_bounding_box_position,
|
||||
});
|
||||
|
||||
if b_quad.upper_control_point_vertex_index == u32::MAX {
|
||||
self.edge_data.upper_line_vertex_positions.push(EdgeLineVertexPositions {
|
||||
left: *upper_left_position,
|
||||
right: *upper_right_position,
|
||||
});
|
||||
} else {
|
||||
if b_quad.upper_control_point_vertex_index != u32::MAX {
|
||||
let upper_control_point_position =
|
||||
&self.b_vertex_positions[b_quad.upper_control_point_vertex_index as usize];
|
||||
b_quad_vertex_positions.control_point_positions[0] = *upper_control_point_position;
|
||||
self.edge_data.upper_curve_vertex_positions.push(EdgeCurveVertexPositions {
|
||||
left: *upper_left_position,
|
||||
control_point: *upper_control_point_position,
|
||||
right: *upper_right_position,
|
||||
});
|
||||
self.b_vertex_positions[b_quad.upper_control_point_vertex_index as usize];
|
||||
b_quad_vertex_positions.upper_control_point_position = upper_control_point_position;
|
||||
}
|
||||
|
||||
if b_quad.lower_control_point_vertex_index == u32::MAX {
|
||||
self.edge_data.lower_line_vertex_positions.push(EdgeLineVertexPositions {
|
||||
left: *lower_left_position,
|
||||
right: *lower_right_position,
|
||||
});
|
||||
} else {
|
||||
if b_quad.lower_control_point_vertex_index != u32::MAX {
|
||||
let lower_control_point_position =
|
||||
&self.b_vertex_positions[b_quad.lower_control_point_vertex_index as usize];
|
||||
b_quad_vertex_positions.control_point_positions[1] = *lower_control_point_position;
|
||||
self.edge_data.lower_curve_vertex_positions.push(EdgeCurveVertexPositions {
|
||||
left: *lower_left_position,
|
||||
control_point: *lower_control_point_position,
|
||||
right: *lower_right_position,
|
||||
});
|
||||
self.b_vertex_positions[b_quad.lower_control_point_vertex_index as usize];
|
||||
b_quad_vertex_positions.lower_control_point_position = lower_control_point_position;
|
||||
}
|
||||
|
||||
let first_b_quad_vertex_position_index = (self.b_quad_vertex_positions.len() as u32) * 6;
|
||||
self.push_b_quad_vertex_position_interior_indices(first_b_quad_vertex_position_index,
|
||||
&b_quad_vertex_positions);
|
||||
|
||||
self.b_quad_vertex_positions.push(b_quad_vertex_positions);
|
||||
}
|
||||
|
||||
fn push_b_quad_vertex_position_interior_indices(&mut self,
|
||||
first_vertex_index: u32,
|
||||
b_quad: &BQuadVertexPositions) {
|
||||
let upper_curve_is_concave =
|
||||
(b_quad.upper_right_vertex_position - b_quad.upper_left_vertex_position).cross(
|
||||
b_quad.upper_control_point_position - b_quad.upper_left_vertex_position) > 0.0;
|
||||
let lower_curve_is_concave =
|
||||
(b_quad.lower_left_vertex_position - b_quad.lower_right_vertex_position).cross(
|
||||
b_quad.lower_control_point_position - b_quad.lower_right_vertex_position) > 0.0;
|
||||
|
||||
let indices: &'static [u32] = match (upper_curve_is_concave, lower_curve_is_concave) {
|
||||
(false, false) => &[UL, UR, LL, UR, LR, LL],
|
||||
(true, false) => &[UL, UC, LL, UC, LR, LL, UR, LR, UC],
|
||||
(false, true) => &[UL, LC, LL, UL, UR, LC, UR, LR, LC],
|
||||
(true, true) => &[UL, UC, LL, UC, LC, LL, UR, LC, UC, UR, LR, LC],
|
||||
};
|
||||
|
||||
self.b_quad_vertex_interior_indices
|
||||
.extend(indices.into_iter().map(|index| index + first_vertex_index));
|
||||
|
||||
const UL: u32 = 0;
|
||||
const UC: u32 = 1;
|
||||
const UR: u32 = 2;
|
||||
const LR: u32 = 3;
|
||||
const LC: u32 = 4;
|
||||
const LL: u32 = 5;
|
||||
}
|
||||
|
||||
/// Reverses interior indices so that they draw front-to-back.
|
||||
///
|
||||
/// This enables early Z optimizations.
|
||||
pub fn optimize(&mut self) {
|
||||
let mut new_interior_indices =
|
||||
Vec::with_capacity(self.cover_indices.interior_indices.len());
|
||||
reverse_indices(&mut self.path_ranges,
|
||||
&mut self.b_quad_vertex_interior_indices,
|
||||
|path_ranges| path_ranges.b_quad_vertex_interior_indices.clone(),
|
||||
|path_ranges, new_range| {
|
||||
path_ranges.b_quad_vertex_interior_indices = new_range
|
||||
});
|
||||
|
||||
for path_range in self.path_ranges.iter_mut().rev() {
|
||||
let old_interior_indices = &self.cover_indices.interior_indices[..];
|
||||
let old_range = path_range.cover_interior_indices.clone();
|
||||
fn reverse_indices<G, S>(path_ranges: &mut [PathRanges],
|
||||
indices: &mut Vec<u32>,
|
||||
mut getter: G,
|
||||
mut setter: S)
|
||||
where G: FnMut(&PathRanges) -> Range<u32>,
|
||||
S: FnMut(&mut PathRanges, Range<u32>) {
|
||||
let mut new_indices = Vec::with_capacity(indices.len());
|
||||
for path_range in path_ranges.iter_mut().rev() {
|
||||
let old_range = getter(path_range);
|
||||
let old_range = (old_range.start as usize)..(old_range.end as usize);
|
||||
let new_start_index = new_interior_indices.len() as u32;
|
||||
new_interior_indices.extend_from_slice(&old_interior_indices[old_range]);
|
||||
let new_end_index = new_interior_indices.len() as u32;
|
||||
path_range.cover_interior_indices = new_start_index..new_end_index;
|
||||
let new_start_index = new_indices.len() as u32;
|
||||
new_indices.extend_from_slice(&indices[old_range]);
|
||||
let new_end_index = new_indices.len() as u32;
|
||||
setter(path_range, new_start_index..new_end_index);
|
||||
}
|
||||
|
||||
self.cover_indices.interior_indices = new_interior_indices
|
||||
*indices = new_indices
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_segments<I>(&mut self, path_id: u16, stream: I)
|
||||
|
@ -220,16 +231,7 @@ impl MeshLibrary {
|
|||
|
||||
try!(write_simple_chunk(writer, b"bqua", &self.b_quads));
|
||||
try!(write_simple_chunk(writer, b"bqvp", &self.b_quad_vertex_positions));
|
||||
try!(write_simple_chunk(writer, b"bvpo", &self.b_vertex_positions));
|
||||
try!(write_simple_chunk(writer, b"bvlb", &self.b_vertex_loop_blinn_data));
|
||||
try!(write_simple_chunk(writer, b"bvno", &self.b_vertex_normals));
|
||||
try!(write_simple_chunk(writer, b"cvii", &self.cover_indices.interior_indices));
|
||||
try!(write_simple_chunk(writer, b"cvci", &self.cover_indices.curve_indices));
|
||||
try!(write_simple_chunk(writer, b"ebbv", &self.edge_data.bounding_box_vertex_positions));
|
||||
try!(write_simple_chunk(writer, b"eulv", &self.edge_data.upper_line_vertex_positions));
|
||||
try!(write_simple_chunk(writer, b"ellv", &self.edge_data.lower_line_vertex_positions));
|
||||
try!(write_simple_chunk(writer, b"eucv", &self.edge_data.upper_curve_vertex_positions));
|
||||
try!(write_simple_chunk(writer, b"elcv", &self.edge_data.lower_curve_vertex_positions));
|
||||
try!(write_simple_chunk(writer, b"bqii", &self.b_quad_vertex_interior_indices));
|
||||
try!(write_simple_chunk(writer, b"slin", &self.segments.lines));
|
||||
try!(write_simple_chunk(writer, b"scur", &self.segments.curves));
|
||||
try!(write_simple_chunk(writer, b"snli", &self.segment_normals.line_normals));
|
||||
|
@ -274,35 +276,11 @@ impl MeshLibrary {
|
|||
b"bqvp",
|
||||
path_ranges,
|
||||
|ranges| &ranges.b_quad_vertex_positions));
|
||||
try!(write_path_range(writer,
|
||||
b"bqii",
|
||||
path_ranges,
|
||||
|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"cvii",
|
||||
path_ranges,
|
||||
|ranges| &ranges.cover_interior_indices));
|
||||
try!(write_path_range(writer,
|
||||
b"cvci",
|
||||
path_ranges,
|
||||
|ranges| &ranges.cover_curve_indices));
|
||||
try!(write_path_range(writer,
|
||||
b"ebbo",
|
||||
path_ranges,
|
||||
|ranges| &ranges.edge_bounding_box_indices));
|
||||
try!(write_path_range(writer,
|
||||
b"euli",
|
||||
path_ranges,
|
||||
|ranges| &ranges.edge_upper_line_indices));
|
||||
try!(write_path_range(writer,
|
||||
b"euci",
|
||||
path_ranges,
|
||||
|ranges| &ranges.edge_upper_curve_indices));
|
||||
try!(write_path_range(writer,
|
||||
b"elli",
|
||||
path_ranges,
|
||||
|ranges| &ranges.edge_lower_line_indices));
|
||||
try!(write_path_range(writer,
|
||||
b"elci",
|
||||
path_ranges,
|
||||
|ranges| &ranges.edge_lower_curve_indices));
|
||||
try!(write_path_range(writer, b"slin", path_ranges, |ranges| &ranges.segment_lines));
|
||||
try!(write_path_range(writer, b"scur", path_ranges, |ranges| &ranges.segment_curves));
|
||||
Ok(())
|
||||
|
@ -329,14 +307,8 @@ impl MeshLibrary {
|
|||
MeshLibraryLengths {
|
||||
b_quads: self.b_quads.len() as u32,
|
||||
b_quad_vertex_positions: self.b_quad_vertex_positions.len() as u32,
|
||||
b_quad_vertex_interior_indices: self.b_quad_vertex_interior_indices.len() as u32,
|
||||
b_vertices: self.b_vertex_positions.len() as u32,
|
||||
cover_interior_indices: self.cover_indices.interior_indices.len() as u32,
|
||||
cover_curve_indices: self.cover_indices.curve_indices.len() as u32,
|
||||
edge_bounding_box_indices: self.edge_data.bounding_box_vertex_positions.len() as u32,
|
||||
edge_upper_line_indices: self.edge_data.upper_line_vertex_positions.len() as u32,
|
||||
edge_upper_curve_indices: self.edge_data.upper_curve_vertex_positions.len() as u32,
|
||||
edge_lower_line_indices: self.edge_data.lower_line_vertex_positions.len() as u32,
|
||||
edge_lower_curve_indices: self.edge_data.lower_curve_vertex_positions.len() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -347,46 +319,19 @@ pub struct MeshLibraryCoverIndices {
|
|||
pub curve_indices: Vec<u32>,
|
||||
}
|
||||
|
||||
impl MeshLibraryCoverIndices {
|
||||
#[inline]
|
||||
fn new() -> MeshLibraryCoverIndices {
|
||||
MeshLibraryCoverIndices {
|
||||
interior_indices: vec![],
|
||||
curve_indices: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.interior_indices.clear();
|
||||
self.curve_indices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MeshLibraryLengths {
|
||||
b_quads: u32,
|
||||
pub(crate) b_quads: u32,
|
||||
b_quad_vertex_positions: u32,
|
||||
b_quad_vertex_interior_indices: u32,
|
||||
b_vertices: u32,
|
||||
cover_interior_indices: u32,
|
||||
cover_curve_indices: u32,
|
||||
edge_bounding_box_indices: u32,
|
||||
edge_upper_line_indices: u32,
|
||||
edge_upper_curve_indices: u32,
|
||||
edge_lower_line_indices: u32,
|
||||
edge_lower_curve_indices: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PathRanges {
|
||||
pub b_quads: Range<u32>,
|
||||
pub b_quad_vertex_positions: Range<u32>,
|
||||
pub b_quad_vertex_interior_indices: Range<u32>,
|
||||
pub b_vertices: Range<u32>,
|
||||
pub cover_interior_indices: Range<u32>,
|
||||
pub cover_curve_indices: Range<u32>,
|
||||
pub edge_bounding_box_indices: Range<u32>,
|
||||
pub edge_upper_line_indices: Range<u32>,
|
||||
pub edge_upper_curve_indices: Range<u32>,
|
||||
pub edge_lower_line_indices: Range<u32>,
|
||||
pub edge_lower_curve_indices: Range<u32>,
|
||||
pub segment_lines: Range<u32>,
|
||||
pub segment_curves: Range<u32>,
|
||||
}
|
||||
|
@ -396,14 +341,8 @@ impl PathRanges {
|
|||
PathRanges {
|
||||
b_quads: 0..0,
|
||||
b_quad_vertex_positions: 0..0,
|
||||
b_quad_vertex_interior_indices: 0..0,
|
||||
b_vertices: 0..0,
|
||||
cover_interior_indices: 0..0,
|
||||
cover_curve_indices: 0..0,
|
||||
edge_bounding_box_indices: 0..0,
|
||||
edge_upper_line_indices: 0..0,
|
||||
edge_upper_curve_indices: 0..0,
|
||||
edge_lower_line_indices: 0..0,
|
||||
edge_lower_curve_indices: 0..0,
|
||||
segment_lines: 0..0,
|
||||
segment_curves: 0..0,
|
||||
}
|
||||
|
@ -414,46 +353,9 @@ impl PathRanges {
|
|||
end: &MeshLibraryLengths) {
|
||||
self.b_quads = start.b_quads..end.b_quads;
|
||||
self.b_quad_vertex_positions = start.b_quad_vertex_positions..end.b_quad_vertex_positions;
|
||||
self.b_quad_vertex_interior_indices =
|
||||
start.b_quad_vertex_interior_indices..end.b_quad_vertex_interior_indices;
|
||||
self.b_vertices = start.b_vertices..end.b_vertices;
|
||||
self.cover_interior_indices = start.cover_interior_indices..end.cover_interior_indices;
|
||||
self.cover_curve_indices = start.cover_curve_indices..end.cover_curve_indices;
|
||||
self.edge_bounding_box_indices =
|
||||
start.edge_bounding_box_indices..end.edge_bounding_box_indices;
|
||||
self.edge_upper_line_indices = start.edge_upper_line_indices..end.edge_upper_line_indices;
|
||||
self.edge_upper_curve_indices =
|
||||
start.edge_upper_curve_indices..end.edge_upper_curve_indices;
|
||||
self.edge_lower_line_indices = start.edge_lower_line_indices..end.edge_lower_line_indices;
|
||||
self.edge_lower_curve_indices =
|
||||
start.edge_lower_curve_indices..end.edge_lower_curve_indices;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MeshLibraryEdgeData {
|
||||
pub bounding_box_vertex_positions: Vec<EdgeBoundingBoxVertexPositions>,
|
||||
pub upper_line_vertex_positions: Vec<EdgeLineVertexPositions>,
|
||||
pub lower_line_vertex_positions: Vec<EdgeLineVertexPositions>,
|
||||
pub upper_curve_vertex_positions: Vec<EdgeCurveVertexPositions>,
|
||||
pub lower_curve_vertex_positions: Vec<EdgeCurveVertexPositions>,
|
||||
}
|
||||
|
||||
impl MeshLibraryEdgeData {
|
||||
fn new() -> MeshLibraryEdgeData {
|
||||
MeshLibraryEdgeData {
|
||||
bounding_box_vertex_positions: vec![],
|
||||
upper_line_vertex_positions: vec![],
|
||||
lower_line_vertex_positions: vec![],
|
||||
upper_curve_vertex_positions: vec![],
|
||||
lower_curve_vertex_positions: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.bounding_box_vertex_positions.clear();
|
||||
self.upper_line_vertex_positions.clear();
|
||||
self.upper_curve_vertex_positions.clear();
|
||||
self.lower_line_vertex_positions.clear();
|
||||
self.lower_curve_vertex_positions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -497,25 +399,6 @@ impl MeshLibrarySegmentNormals {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct EdgeBoundingBoxVertexPositions {
|
||||
pub upper_left: Point2D<f32>,
|
||||
pub lower_right: Point2D<f32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct EdgeLineVertexPositions {
|
||||
pub left: Point2D<f32>,
|
||||
pub right: Point2D<f32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct EdgeCurveVertexPositions {
|
||||
pub left: Point2D<f32>,
|
||||
pub control_point: Point2D<f32>,
|
||||
pub right: Point2D<f32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct LineSegment {
|
||||
pub endpoint_0: Point2D<f32>,
|
||||
|
|
|
@ -177,24 +177,10 @@ impl<'a> Partitioner<'a> {
|
|||
let next_active_edge_index = self.find_point_between_active_edges(endpoint_index);
|
||||
|
||||
let endpoint = &self.endpoints[endpoint_index as usize];
|
||||
let emission_result = self.emit_b_quads_around_active_edge(next_active_edge_index,
|
||||
endpoint.position.x);
|
||||
self.emit_b_quads_around_active_edge(next_active_edge_index, endpoint.position.x);
|
||||
|
||||
self.add_new_edges_for_min_point(endpoint_index, next_active_edge_index);
|
||||
|
||||
// Add supporting interior triangles if necessary.
|
||||
match emission_result {
|
||||
BQuadEmissionResult::BQuadEmittedAbove | BQuadEmissionResult::BQuadEmittedAround => {
|
||||
self.add_supporting_interior_triangle(next_active_edge_index,
|
||||
next_active_edge_index - 1,
|
||||
next_active_edge_index + 2);
|
||||
self.add_supporting_interior_triangle(next_active_edge_index + 1,
|
||||
next_active_edge_index - 1,
|
||||
next_active_edge_index + 2);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let prev_endpoint_index = self.prev_endpoint_of(endpoint_index);
|
||||
let next_endpoint_index = self.next_endpoint_of(endpoint_index);
|
||||
let new_point = self.create_point_from_endpoint(next_endpoint_index);
|
||||
|
@ -291,29 +277,10 @@ impl<'a> Partitioner<'a> {
|
|||
|
||||
// TODO(pcwalton): Collapse the two duplicate endpoints that this will create together if
|
||||
// possible (i.e. if they have the same parity).
|
||||
let b_quad_emission_results = [
|
||||
self.emit_b_quads_around_active_edge(active_edge_indices[0], endpoint.position.x),
|
||||
self.emit_b_quads_around_active_edge(active_edge_indices[1], endpoint.position.x),
|
||||
];
|
||||
self.emit_b_quads_around_active_edge(active_edge_indices[0], endpoint.position.x);
|
||||
self.emit_b_quads_around_active_edge(active_edge_indices[1], endpoint.position.x);
|
||||
|
||||
// Add supporting interior triangles if necessary.
|
||||
match b_quad_emission_results[0] {
|
||||
BQuadEmissionResult::BQuadEmittedAbove | BQuadEmissionResult::BQuadEmittedAround => {
|
||||
self.add_supporting_interior_triangle(active_edge_indices[0],
|
||||
active_edge_indices[0] - 1,
|
||||
active_edge_indices[0] + 2)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match b_quad_emission_results[1] {
|
||||
BQuadEmissionResult::BQuadEmittedBelow | BQuadEmissionResult::BQuadEmittedAround => {
|
||||
self.add_supporting_interior_triangle(active_edge_indices[1],
|
||||
active_edge_indices[1] - 2,
|
||||
active_edge_indices[1] + 1)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.heap.pop();
|
||||
|
||||
// FIXME(pcwalton): This is twice as slow as it needs to be.
|
||||
|
@ -360,6 +327,7 @@ impl<'a> Partitioner<'a> {
|
|||
}
|
||||
|
||||
fn write_normals_to_library(&mut self) {
|
||||
// Write B-vertex normals.
|
||||
for (b_vertex_index, vertex_normal) in self.vertex_normals.iter().enumerate() {
|
||||
debug_assert!(b_vertex_index <= self.library.b_vertex_normals.len());
|
||||
|
||||
|
@ -695,115 +663,6 @@ impl<'a> Partitioner<'a> {
|
|||
lower_active_edge.toggle_parity();
|
||||
}
|
||||
|
||||
match (upper_shape, lower_shape) {
|
||||
(Shape::Flat, Shape::Flat) |
|
||||
(Shape::Flat, Shape::Convex) |
|
||||
(Shape::Convex, Shape::Flat) |
|
||||
(Shape::Convex, Shape::Convex) => {
|
||||
self.library.cover_indices.interior_indices.extend([
|
||||
upper_subdivision.left_curve_left,
|
||||
upper_subdivision.middle_point,
|
||||
lower_subdivision.left_curve_left,
|
||||
lower_subdivision.middle_point,
|
||||
lower_subdivision.left_curve_left,
|
||||
upper_subdivision.middle_point,
|
||||
].into_iter());
|
||||
if upper_shape != Shape::Flat {
|
||||
self.library.cover_indices.curve_indices.extend([
|
||||
upper_subdivision.left_curve_control_point,
|
||||
upper_subdivision.middle_point,
|
||||
upper_subdivision.left_curve_left,
|
||||
].into_iter())
|
||||
}
|
||||
if lower_shape != Shape::Flat {
|
||||
self.library.cover_indices.curve_indices.extend([
|
||||
lower_subdivision.left_curve_control_point,
|
||||
lower_subdivision.left_curve_left,
|
||||
lower_subdivision.middle_point,
|
||||
].into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
(Shape::Concave, Shape::Flat) |
|
||||
(Shape::Concave, Shape::Convex) => {
|
||||
self.library.cover_indices.interior_indices.extend([
|
||||
upper_subdivision.left_curve_left,
|
||||
upper_subdivision.left_curve_control_point,
|
||||
lower_subdivision.left_curve_left,
|
||||
upper_subdivision.middle_point,
|
||||
lower_subdivision.middle_point,
|
||||
upper_subdivision.left_curve_control_point,
|
||||
lower_subdivision.middle_point,
|
||||
lower_subdivision.left_curve_left,
|
||||
upper_subdivision.left_curve_control_point,
|
||||
].into_iter());
|
||||
self.library.cover_indices.curve_indices.extend([
|
||||
upper_subdivision.left_curve_control_point,
|
||||
upper_subdivision.left_curve_left,
|
||||
upper_subdivision.middle_point,
|
||||
].into_iter());
|
||||
if lower_shape != Shape::Flat {
|
||||
self.library.cover_indices.curve_indices.extend([
|
||||
lower_subdivision.left_curve_control_point,
|
||||
lower_subdivision.left_curve_left,
|
||||
lower_subdivision.middle_point,
|
||||
].into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
(Shape::Flat, Shape::Concave) |
|
||||
(Shape::Convex, Shape::Concave) => {
|
||||
self.library.cover_indices.interior_indices.extend([
|
||||
upper_subdivision.left_curve_left,
|
||||
upper_subdivision.middle_point,
|
||||
lower_subdivision.left_curve_control_point,
|
||||
upper_subdivision.middle_point,
|
||||
lower_subdivision.middle_point,
|
||||
lower_subdivision.left_curve_control_point,
|
||||
upper_subdivision.left_curve_left,
|
||||
lower_subdivision.left_curve_control_point,
|
||||
lower_subdivision.left_curve_left,
|
||||
].into_iter());
|
||||
self.library.cover_indices.curve_indices.extend([
|
||||
lower_subdivision.left_curve_control_point,
|
||||
lower_subdivision.middle_point,
|
||||
lower_subdivision.left_curve_left,
|
||||
].into_iter());
|
||||
if upper_shape != Shape::Flat {
|
||||
self.library.cover_indices.curve_indices.extend([
|
||||
upper_subdivision.left_curve_control_point,
|
||||
upper_subdivision.middle_point,
|
||||
upper_subdivision.left_curve_left,
|
||||
].into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
(Shape::Concave, Shape::Concave) => {
|
||||
self.library.cover_indices.interior_indices.extend([
|
||||
upper_subdivision.left_curve_left,
|
||||
upper_subdivision.left_curve_control_point,
|
||||
lower_subdivision.left_curve_left,
|
||||
lower_subdivision.left_curve_left,
|
||||
upper_subdivision.left_curve_control_point,
|
||||
lower_subdivision.left_curve_control_point,
|
||||
upper_subdivision.middle_point,
|
||||
lower_subdivision.left_curve_control_point,
|
||||
upper_subdivision.left_curve_control_point,
|
||||
upper_subdivision.middle_point,
|
||||
lower_subdivision.middle_point,
|
||||
lower_subdivision.left_curve_control_point,
|
||||
].into_iter());
|
||||
self.library.cover_indices.curve_indices.extend([
|
||||
upper_subdivision.left_curve_control_point,
|
||||
upper_subdivision.left_curve_left,
|
||||
upper_subdivision.middle_point,
|
||||
lower_subdivision.left_curve_control_point,
|
||||
lower_subdivision.middle_point,
|
||||
lower_subdivision.left_curve_left,
|
||||
].into_iter());
|
||||
}
|
||||
}
|
||||
|
||||
let b_quad = BQuad::new(upper_subdivision.left_curve_left,
|
||||
upper_subdivision.left_curve_control_point,
|
||||
upper_subdivision.middle_point,
|
||||
|
@ -872,17 +731,6 @@ impl<'a> Partitioner<'a> {
|
|||
self.subdivide_active_edge_again_at_t(subdivision, t, bottom)
|
||||
}
|
||||
|
||||
fn add_supporting_interior_triangle(&mut self,
|
||||
active_edge_index: u32,
|
||||
upper_active_edge_index: u32,
|
||||
lower_active_edge_index: u32) {
|
||||
self.library.cover_indices.interior_indices.extend([
|
||||
self.active_edges[active_edge_index as usize].left_vertex_index,
|
||||
self.active_edges[upper_active_edge_index as usize].left_vertex_index,
|
||||
self.active_edges[lower_active_edge_index as usize].left_vertex_index,
|
||||
].into_iter());
|
||||
}
|
||||
|
||||
fn already_visited_point(&self, point: &Point) -> bool {
|
||||
// FIXME(pcwalton): This makes the visited vector too big.
|
||||
let index = point.endpoint_index as usize;
|
||||
|
|
|
@ -117,6 +117,12 @@ vec2 dilatePosition(vec2 position, float normalAngle, vec2 amount) {
|
|||
return position + vec2(cos(normalAngle), -sin(normalAngle)) * amount;
|
||||
}
|
||||
|
||||
vec2 offsetPositionVertically(vec2 position, ivec2 framebufferSize, bool roundUp) {
|
||||
position = convertClipToScreenSpace(position, framebufferSize);
|
||||
position.y = roundUp ? ceil(position.y + 1.0) : floor(position.y - 1.0);
|
||||
return convertScreenToClipSpace(position, framebufferSize);
|
||||
}
|
||||
|
||||
vec2 computeMCAAPosition(vec2 position,
|
||||
vec4 hints,
|
||||
vec4 localTransformST,
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
// pathfinder/shaders/gles2/direct-interior.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.
|
||||
|
||||
//! Renders polygonal portions of a mesh, only filling pixels that are fully
|
||||
//! covered.
|
||||
//!
|
||||
//! Typically, you will run this shader before running XCAA.
|
||||
//! Remember to enable the depth test with a `GREATER` depth function for optimal
|
||||
//! performance.
|
||||
|
||||
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 colors texture in texels.
|
||||
uniform ivec2 uPathColorsDimensions;
|
||||
/// The fill color for each path.
|
||||
uniform sampler2D uPathColors;
|
||||
/// 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 2D position of this point.
|
||||
attribute vec2 aPosition;
|
||||
/// The path ID, starting from 1.
|
||||
attribute float aPathID;
|
||||
/// The vertex ID. In OpenGL 3.0+, this can be omitted in favor of `gl_VertexID`.
|
||||
attribute float aVertexID;
|
||||
|
||||
/// The color of this path.
|
||||
varying vec4 vColor;
|
||||
|
||||
void main() {
|
||||
int pathID = int(aPathID);
|
||||
int vertexID = int(aVertexID);
|
||||
|
||||
vec2 pathTransformExt;
|
||||
vec4 pathTransformST = fetchPathAffineTransform(pathTransformExt,
|
||||
uPathTransformST,
|
||||
uPathTransformSTDimensions,
|
||||
uPathTransformExt,
|
||||
uPathTransformExtDimensions,
|
||||
pathID);
|
||||
|
||||
vec2 position = hintPosition(aPosition, uHints);
|
||||
position = transformVertexPositionAffine(position, pathTransformST, pathTransformExt);
|
||||
position = transformVertexPosition(position, uTransform);
|
||||
position = offsetPositionVertically(position, uFramebufferSize, imod(vertexID, 6) < 3);
|
||||
|
||||
float depth = convertPathIndexToViewportDepthValue(pathID);
|
||||
gl_Position = vec4(position, depth, 1.0);
|
||||
|
||||
vColor = fetchFloat4Data(uPathColors, pathID, uPathColorsDimensions);
|
||||
}
|
|
@ -35,24 +35,21 @@ uniform vec2 uEmboldenAmount;
|
|||
|
||||
/// The 2D position of this point.
|
||||
attribute vec2 aPosition;
|
||||
/// The abstract Loop-Blinn texture coordinate for this point.
|
||||
attribute vec2 aTexCoord;
|
||||
/// The path ID, starting from 1.
|
||||
attribute float aPathID;
|
||||
/// Specifies whether this is a concave or convex curve.
|
||||
attribute float aSign;
|
||||
/// The angle of the 2D normal for this point.
|
||||
attribute float aNormalAngle;
|
||||
/// The vertex ID. In OpenGL 3.0+, this can be omitted in favor of `gl_VertexID`.
|
||||
attribute float aVertexID;
|
||||
/// The path ID, starting from 1.
|
||||
attribute float aPathID;
|
||||
|
||||
/// The fill color of this path.
|
||||
varying vec4 vColor;
|
||||
/// The outgoing abstract Loop-Blinn texture coordinate.
|
||||
varying vec2 vTexCoord;
|
||||
/// Specifies whether this is a concave or convex curve.
|
||||
varying float vSign;
|
||||
|
||||
void main() {
|
||||
int pathID = int(aPathID);
|
||||
int vertexID = int(aVertexID);
|
||||
|
||||
vec2 pathTransformExt;
|
||||
vec4 pathTransformST = fetchPathAffineTransform(pathTransformExt,
|
||||
|
@ -67,7 +64,9 @@ void main() {
|
|||
|
||||
gl_Position = uTransform * vec4(position, 0.0, 1.0);
|
||||
|
||||
int vertexIndex = imod(vertexID, 3);
|
||||
vec2 texCoord = vec2(float(vertexIndex) * 0.5, float(vertexIndex == 2));
|
||||
|
||||
vColor = fetchFloat4Data(uPathColors, pathID, uPathColorsDimensions);
|
||||
vTexCoord = vec2(aTexCoord) / 2.0;
|
||||
vSign = aSign;
|
||||
vTexCoord = texCoord;
|
||||
}
|
||||
|
|
|
@ -25,11 +25,11 @@ precision highp float;
|
|||
varying vec4 vColor;
|
||||
/// The abstract Loop-Blinn texture coordinate.
|
||||
varying vec2 vTexCoord;
|
||||
/// Specifies whether this is a concave or convex curve.
|
||||
varying float vSign;
|
||||
|
||||
void main() {
|
||||
float side = vTexCoord.x * vTexCoord.x - vTexCoord.y;
|
||||
float alpha = float(sign(side) == sign(vSign));
|
||||
float side = sign(vTexCoord.x * vTexCoord.x - vTexCoord.y);
|
||||
float winding = gl_FrontFacing ? -1.0 : 1.0;
|
||||
float alpha = float(side == winding);
|
||||
//float alpha = mod(gl_FragCoord.x, 2.0) < 1.0 ? 1.0 : 0.0;
|
||||
gl_FragColor = alpha * vColor;
|
||||
}
|
||||
|
|
|
@ -37,29 +37,22 @@ uniform sampler2D uPathTransformST;
|
|||
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 2D position of this point.
|
||||
attribute vec2 aPosition;
|
||||
/// The abstract Loop-Blinn texture coordinate for this point.
|
||||
attribute vec2 aTexCoord;
|
||||
/// The vertex ID. In OpenGL 3.0+, this can be omitted in favor of `gl_VertexID`.
|
||||
attribute float aVertexID;
|
||||
/// The path ID, starting from 1.
|
||||
attribute float aPathID;
|
||||
/// Specifies whether this is a concave or convex curve.
|
||||
attribute float aSign;
|
||||
/// The angle of the 2D normal for this point.
|
||||
attribute float aNormalAngle;
|
||||
|
||||
/// The fill color of this path.
|
||||
varying vec4 vColor;
|
||||
/// The outgoing abstract Loop-Blinn texture coordinate.
|
||||
varying vec2 vTexCoord;
|
||||
/// Specifies whether this is a concave or convex curve.
|
||||
varying float vSign;
|
||||
|
||||
void main() {
|
||||
int pathID = int(aPathID);
|
||||
int vertexID = int(aVertexID);
|
||||
|
||||
vec2 pathTransformExt;
|
||||
vec4 pathTransformST = fetchPathAffineTransform(pathTransformExt,
|
||||
|
@ -69,15 +62,16 @@ void main() {
|
|||
uPathTransformExtDimensions,
|
||||
pathID);
|
||||
|
||||
vec2 position = dilatePosition(aPosition, aNormalAngle, uEmboldenAmount);
|
||||
position = hintPosition(position, uHints);
|
||||
vec2 position = hintPosition(aPosition, uHints);
|
||||
position = transformVertexPositionAffine(position, pathTransformST, pathTransformExt);
|
||||
position = transformVertexPosition(position, uTransform);
|
||||
|
||||
float depth = convertPathIndexToViewportDepthValue(pathID);
|
||||
gl_Position = vec4(position, depth, 1.0);
|
||||
|
||||
int vertexIndex = imod(vertexID, 3);
|
||||
vec2 texCoord = vec2(float(vertexIndex) * 0.5, float(vertexIndex == 2));
|
||||
|
||||
vColor = fetchFloat4Data(uPathColors, pathID, uPathColorsDimensions);
|
||||
vTexCoord = vec2(aTexCoord) / 2.0;
|
||||
vSign = aSign;
|
||||
vTexCoord = texCoord;
|
||||
}
|
||||
|
|
|
@ -32,12 +32,11 @@ uniform sampler2D uPathTransformST;
|
|||
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 2D position of this point.
|
||||
attribute vec2 aPosition;
|
||||
/// The path ID, starting from 1.
|
||||
attribute float aPathID;
|
||||
attribute float aNormalAngle;
|
||||
|
||||
/// The color of this path.
|
||||
varying vec4 vColor;
|
||||
|
@ -53,8 +52,7 @@ void main() {
|
|||
uPathTransformExtDimensions,
|
||||
pathID);
|
||||
|
||||
vec2 position = dilatePosition(aPosition, aNormalAngle, uEmboldenAmount);
|
||||
position = hintPosition(position, uHints);
|
||||
vec2 position = hintPosition(aPosition, uHints);
|
||||
position = transformVertexPositionAffine(position, pathTransformST, pathTransformExt);
|
||||
position = transformVertexPosition(position, uTransform);
|
||||
|
||||
|
|
|
@ -47,8 +47,8 @@ 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 aQuadPosition;
|
||||
/// 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.
|
||||
|
@ -82,31 +82,63 @@ void main() {
|
|||
vec4 bounds = fetchFloat4Data(uPathBounds, pathID, uPathBoundsDimensions);
|
||||
|
||||
// Transform the points, and compute the position of this vertex.
|
||||
vec2 position;
|
||||
float winding;
|
||||
if (computeECAAQuadPosition(position,
|
||||
winding,
|
||||
leftPosition,
|
||||
rightPosition,
|
||||
aQuadPosition,
|
||||
uFramebufferSize,
|
||||
pathTransformST,
|
||||
pathTransformExt,
|
||||
uTransform,
|
||||
uHints,
|
||||
bounds,
|
||||
leftRightNormalAngles,
|
||||
uEmboldenAmount)) {
|
||||
controlPointPosition = computeECAAPosition(controlPointPosition,
|
||||
controlPointNormalAngle,
|
||||
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);
|
||||
|
|
|
@ -46,7 +46,7 @@ uniform sampler2D uPathTransformExt;
|
|||
uniform vec2 uEmboldenAmount;
|
||||
|
||||
/// The abstract quad position: (0.0, 0.0) to (1.0, 1.0).
|
||||
attribute vec2 aQuadPosition;
|
||||
attribute vec2 aTessCoord;
|
||||
/// The position of the left endpoint.
|
||||
attribute vec2 aLeftPosition;
|
||||
/// The position of the right endpoint.
|
||||
|
@ -75,22 +75,53 @@ void main() {
|
|||
vec4 bounds = fetchFloat4Data(uPathBounds, pathID, uPathBoundsDimensions);
|
||||
|
||||
// Transform the points, and compute the position of this vertex.
|
||||
vec2 position;
|
||||
float winding;
|
||||
computeECAAQuadPosition(position,
|
||||
winding,
|
||||
leftPosition,
|
||||
rightPosition,
|
||||
aQuadPosition,
|
||||
uFramebufferSize,
|
||||
leftPosition = computeECAAPosition(leftPosition,
|
||||
aNormalAngles.x,
|
||||
uEmboldenAmount,
|
||||
uHints,
|
||||
pathTransformST,
|
||||
pathTransformExt,
|
||||
uTransform,
|
||||
uFramebufferSize);
|
||||
rightPosition = computeECAAPosition(rightPosition,
|
||||
aNormalAngles.y,
|
||||
uEmboldenAmount,
|
||||
uHints,
|
||||
bounds,
|
||||
leftRightNormalAngles,
|
||||
uEmboldenAmount);
|
||||
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);
|
||||
|
|
|
@ -57,7 +57,7 @@ uniform vec2 uEmboldenAmount;
|
|||
uniform int uPassIndex;
|
||||
|
||||
/// The abstract quad position: (0.0, 0.0) to (1.0, 1.0).
|
||||
attribute vec2 aQuadPosition;
|
||||
attribute vec2 aTessCoord;
|
||||
/// The position of the left endpoint.
|
||||
attribute vec2 aLeftPosition;
|
||||
/// The position of the control point.
|
||||
|
@ -126,16 +126,33 @@ void main() {
|
|||
return;
|
||||
}
|
||||
|
||||
vec2 position = computeECAAQuadPositionFromTransformedPositions(leftPosition,
|
||||
rightPosition,
|
||||
aQuadPosition,
|
||||
uFramebufferSize,
|
||||
pathTransformST,
|
||||
pathTransformExt,
|
||||
uTransform,
|
||||
bounds,
|
||||
leftTopRightEdges);
|
||||
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);
|
||||
|
|
|
@ -53,10 +53,13 @@ uniform sampler2D uPathColors;
|
|||
/// If this is true, then points will be snapped to the nearest pixel.
|
||||
uniform bool uMulticolor;
|
||||
|
||||
attribute vec2 aQuadPosition;
|
||||
attribute vec4 aUpperEndpointPositions;
|
||||
attribute vec4 aLowerEndpointPositions;
|
||||
attribute vec4 aControlPointPositions;
|
||||
attribute vec2 aTessCoord;
|
||||
attribute vec2 aUpperLeftEndpointPosition;
|
||||
attribute vec2 aUpperControlPointPosition;
|
||||
attribute vec2 aUpperRightEndpointPosition;
|
||||
attribute vec2 aLowerRightEndpointPosition;
|
||||
attribute vec2 aLowerControlPointPosition;
|
||||
attribute vec2 aLowerLeftEndpointPosition;
|
||||
attribute float aPathID;
|
||||
|
||||
varying vec4 vUpperEndpoints;
|
||||
|
@ -65,13 +68,13 @@ varying vec4 vControlPoints;
|
|||
varying vec4 vColor;
|
||||
|
||||
void main() {
|
||||
vec2 tlPosition = aUpperEndpointPositions.xy;
|
||||
vec2 tcPosition = aControlPointPositions.xy;
|
||||
vec2 trPosition = aUpperEndpointPositions.zw;
|
||||
vec2 blPosition = aLowerEndpointPositions.xy;
|
||||
vec2 bcPosition = aControlPointPositions.zw;
|
||||
vec2 brPosition = aLowerEndpointPositions.zw;
|
||||
vec2 quadPosition = aQuadPosition;
|
||||
vec2 tlPosition = aUpperLeftEndpointPosition;
|
||||
vec2 tcPosition = aUpperControlPointPosition;
|
||||
vec2 trPosition = aUpperRightEndpointPosition;
|
||||
vec2 blPosition = aLowerLeftEndpointPosition;
|
||||
vec2 bcPosition = aLowerControlPointPosition;
|
||||
vec2 brPosition = aLowerRightEndpointPosition;
|
||||
vec2 tessCoord = aTessCoord;
|
||||
int pathID = int(floor(aPathID));
|
||||
|
||||
vec4 transformST = fetchFloat4Data(uPathTransformST, pathID, uPathTransformSTDimensions);
|
||||
|
@ -129,19 +132,33 @@ void main() {
|
|||
|
||||
float depth = convertPathIndexToViewportDepthValue(pathID);
|
||||
|
||||
// Use the same side--in this case, the top--or else floating point error during partitioning
|
||||
// can occasionally cause inconsistent rounding, resulting in cracks.
|
||||
// Use the same side--in this case, the top--or else floating point error during
|
||||
// partitioning can occasionally cause inconsistent rounding, resulting in cracks.
|
||||
vec2 position;
|
||||
|
||||
if (uMulticolor)
|
||||
position.x = quadPosition.x < 0.5 ? tlPosition.x : trPosition.x;
|
||||
if (tessCoord.y < 0.5) {
|
||||
if (tessCoord.x < 0.25)
|
||||
position = tlPosition;
|
||||
else if (tessCoord.x < 0.75)
|
||||
position = tcPosition;
|
||||
else
|
||||
position.x = quadPosition.x < 0.5 ? floor(tlPosition.x) : ceil(trPosition.x);
|
||||
|
||||
if (quadPosition.y < 0.5)
|
||||
position.y = floor(min(tlPosition.y, trPosition.y));
|
||||
position = trPosition;
|
||||
position.y = floor(position.y - 0.5);
|
||||
} else {
|
||||
if (tessCoord.x < 0.25)
|
||||
position = blPosition;
|
||||
else if (tessCoord.x < 0.75)
|
||||
position = bcPosition;
|
||||
else
|
||||
position.y = ceil(max(blPosition.y, brPosition.y));
|
||||
position = brPosition;
|
||||
position.y = ceil(position.y + 0.5);
|
||||
}
|
||||
|
||||
if (!uMulticolor) {
|
||||
if (tessCoord.x < 0.25)
|
||||
position.x = floor(position.x);
|
||||
else if (tessCoord.x >= 0.75)
|
||||
position.x = ceil(position.x);
|
||||
}
|
||||
|
||||
position = convertScreenToClipSpace(position, uFramebufferSize);
|
||||
|
||||
|
|
Loading…
Reference in New Issue