TSLint the demo
This commit is contained in:
parent
6217577674
commit
e8135fbfe0
|
@ -31,5 +31,9 @@
|
||||||
"ts-loader": "^2.3.7",
|
"ts-loader": "^2.3.7",
|
||||||
"typescript": "^2.5.2",
|
"typescript": "^2.5.2",
|
||||||
"webpack": "^3.5.6"
|
"webpack": "^3.5.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"tslint": "^5.7.0",
|
||||||
|
"tslint-loader": "^3.5.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,21 +9,21 @@
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
|
import * as _ from "lodash";
|
||||||
import * as opentype from "opentype.js";
|
import * as opentype from "opentype.js";
|
||||||
|
|
||||||
|
import {mat4, vec2} from "gl-matrix";
|
||||||
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
||||||
import {DemoAppController} from "./app-controller";
|
import {DemoAppController} from "./app-controller";
|
||||||
|
import PathfinderBufferTexture from "./buffer-texture";
|
||||||
import {PerspectiveCamera} from "./camera";
|
import {PerspectiveCamera} from "./camera";
|
||||||
import {mat4, vec2} from "gl-matrix";
|
|
||||||
import {PathfinderMeshData} from "./meshes";
|
import {PathfinderMeshData} from "./meshes";
|
||||||
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
|
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
|
||||||
import {BUILTIN_FONT_URI, ExpandedMeshData} from "./text";
|
|
||||||
import { Hint, TextFrame, TextRun, GlyphStore, PathfinderFont } from "./text";
|
|
||||||
import {PathfinderError, assert, panic, unwrapNull} from "./utils";
|
|
||||||
import {PathfinderDemoView, Timings} from "./view";
|
|
||||||
import SSAAStrategy from "./ssaa-strategy";
|
import SSAAStrategy from "./ssaa-strategy";
|
||||||
import * as _ from "lodash";
|
import {BUILTIN_FONT_URI, ExpandedMeshData} from "./text";
|
||||||
import PathfinderBufferTexture from "./buffer-texture";
|
import {GlyphStore, Hint, PathfinderFont, TextFrame, TextRun} from "./text";
|
||||||
|
import {assert, panic, PathfinderError, unwrapNull} from "./utils";
|
||||||
|
import {PathfinderDemoView, Timings} from "./view";
|
||||||
|
|
||||||
const TEXT_AVAILABLE_WIDTH: number = 150000;
|
const TEXT_AVAILABLE_WIDTH: number = 150000;
|
||||||
const TEXT_PADDING: number = 2000;
|
const TEXT_PADDING: number = 2000;
|
||||||
|
@ -94,6 +94,14 @@ interface MonumentSide {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThreeDController extends DemoAppController<ThreeDView> {
|
class ThreeDController extends DemoAppController<ThreeDView> {
|
||||||
|
textFrames: TextFrame[];
|
||||||
|
glyphStore: GlyphStore;
|
||||||
|
|
||||||
|
private baseMeshes: PathfinderMeshData;
|
||||||
|
private expandedMeshes: ExpandedMeshData[];
|
||||||
|
|
||||||
|
private monumentPromise: Promise<MonumentSide[]>;
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
@ -104,15 +112,34 @@ class ThreeDController extends DemoAppController<ThreeDView> {
|
||||||
this.loadInitialFile(this.builtinFileURI);
|
this.loadInitialFile(this.builtinFileURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fileLoaded(fileData: ArrayBuffer): void {
|
||||||
|
const font = new PathfinderFont(fileData);
|
||||||
|
this.monumentPromise.then(monument => this.layoutMonument(font, fileData, monument));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createView(): ThreeDView {
|
||||||
|
return new ThreeDView(this,
|
||||||
|
unwrapNull(this.commonShaderSource),
|
||||||
|
unwrapNull(this.shaderSources));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get builtinFileURI(): string {
|
||||||
|
return BUILTIN_FONT_URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get defaultFile(): string {
|
||||||
|
return FONT;
|
||||||
|
}
|
||||||
|
|
||||||
private parseTextData(textData: any): MonumentSide[] {
|
private parseTextData(textData: any): MonumentSide[] {
|
||||||
const sides = [];
|
const sides = [];
|
||||||
for (let sideIndex = 0; sideIndex < 4; sideIndex++)
|
for (let sideIndex = 0; sideIndex < 4; sideIndex++)
|
||||||
sides[sideIndex] = { upper: { lines: [] }, lower: { lines: [] } };
|
sides[sideIndex] = { upper: { lines: [] }, lower: { lines: [] } };
|
||||||
|
|
||||||
for (const nameData of textData.monument) {
|
for (const nameData of textData.monument) {
|
||||||
const side = parseInt(nameData.side) - 1;
|
const side = parseInt(nameData.side, 10) - 1;
|
||||||
const row = parseInt(nameData.row) - 1;
|
const row = parseInt(nameData.row, 10) - 1;
|
||||||
const number = parseInt(nameData.number) - 1;
|
const index = parseInt(nameData.number, 10) - 1;
|
||||||
|
|
||||||
if (sides[side] == null)
|
if (sides[side] == null)
|
||||||
continue;
|
continue;
|
||||||
|
@ -121,29 +148,24 @@ class ThreeDController extends DemoAppController<ThreeDView> {
|
||||||
if (lines[row] == null)
|
if (lines[row] == null)
|
||||||
lines[row] = { names: [] };
|
lines[row] = { names: [] };
|
||||||
|
|
||||||
lines[row].names[number] = nameData.name;
|
lines[row].names[index] = nameData.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sides.map(side => ({ lines: side.upper.lines.concat(side.lower.lines) }));
|
return sides.map(side => ({ lines: side.upper.lines.concat(side.lower.lines) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fileLoaded(fileData: ArrayBuffer): void {
|
|
||||||
const font = new PathfinderFont(fileData);
|
|
||||||
this.monumentPromise.then(monument => this.layoutMonument(font, fileData, monument));
|
|
||||||
}
|
|
||||||
|
|
||||||
private layoutMonument(font: PathfinderFont, fileData: ArrayBuffer, monument: MonumentSide[]) {
|
private layoutMonument(font: PathfinderFont, fileData: ArrayBuffer, monument: MonumentSide[]) {
|
||||||
this.textFrames = [];
|
this.textFrames = [];
|
||||||
let glyphsNeeded: number[] = [];
|
let glyphsNeeded: number[] = [];
|
||||||
|
|
||||||
for (const monumentSide of monument) {
|
for (const monumentSide of monument) {
|
||||||
let textRuns = [];
|
const textRuns = [];
|
||||||
for (let lineNumber = 0; lineNumber < monumentSide.lines.length; lineNumber++) {
|
for (let lineNumber = 0; lineNumber < monumentSide.lines.length; lineNumber++) {
|
||||||
const line = monumentSide.lines[lineNumber];
|
const line = monumentSide.lines[lineNumber];
|
||||||
|
|
||||||
const lineY = -lineNumber * font.opentypeFont.lineHeight();
|
const lineY = -lineNumber * font.opentypeFont.lineHeight();
|
||||||
const lineGlyphs = line.names.map(string => {
|
const lineGlyphs = line.names.map(name => {
|
||||||
const glyphs = font.opentypeFont.stringToGlyphs(string);
|
const glyphs = font.opentypeFont.stringToGlyphs(name);
|
||||||
const glyphIDs = glyphs.map(glyph => (glyph as any).index);
|
const glyphIDs = glyphs.map(glyph => (glyph as any).index);
|
||||||
const width = _.sumBy(glyphs, glyph => glyph.advanceWidth);
|
const width = _.sumBy(glyphs, glyph => glyph.advanceWidth);
|
||||||
return { glyphs: glyphIDs, width: width };
|
return { glyphs: glyphIDs, width: width };
|
||||||
|
@ -184,31 +206,27 @@ class ThreeDController extends DemoAppController<ThreeDView> {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createView(): ThreeDView {
|
|
||||||
return new ThreeDView(this,
|
|
||||||
unwrapNull(this.commonShaderSource),
|
|
||||||
unwrapNull(this.shaderSources));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get builtinFileURI(): string {
|
|
||||||
return BUILTIN_FONT_URI;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get defaultFile(): string {
|
|
||||||
return FONT;
|
|
||||||
}
|
|
||||||
|
|
||||||
textFrames: TextFrame[];
|
|
||||||
glyphStore: GlyphStore;
|
|
||||||
|
|
||||||
private baseMeshes: PathfinderMeshData;
|
|
||||||
private expandedMeshes: ExpandedMeshData[];
|
|
||||||
|
|
||||||
private monumentPromise: Promise<MonumentSide[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThreeDView extends PathfinderDemoView {
|
class ThreeDView extends PathfinderDemoView {
|
||||||
|
destFramebuffer: WebGLFramebuffer | null = null;
|
||||||
|
|
||||||
|
camera: PerspectiveCamera;
|
||||||
|
|
||||||
|
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.clone([1.0, 1.0]);
|
||||||
|
|
||||||
|
protected directCurveProgramName: keyof ShaderMap<void> = 'direct3DCurve';
|
||||||
|
protected directInteriorProgramName: keyof ShaderMap<void> = 'direct3DInterior';
|
||||||
|
|
||||||
|
protected depthFunction: number = this.gl.LESS;
|
||||||
|
|
||||||
|
private _scale: number;
|
||||||
|
|
||||||
|
private appController: ThreeDController;
|
||||||
|
|
||||||
|
private cubeVertexPositionBuffer: WebGLBuffer;
|
||||||
|
private cubeIndexBuffer: WebGLBuffer;
|
||||||
|
|
||||||
constructor(appController: ThreeDController,
|
constructor(appController: ThreeDController,
|
||||||
commonShaderSource: string,
|
commonShaderSource: string,
|
||||||
shaderSources: ShaderMap<ShaderProgramSource>) {
|
shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||||
|
@ -316,18 +334,40 @@ class ThreeDView extends PathfinderDemoView {
|
||||||
this.appController.newTimingsReceived(_.pick(this.lastTimings, ['rendering']));
|
this.appController.newTimingsReceived(_.pick(this.lastTimings, ['rendering']));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected clearForDirectRendering(): void {
|
||||||
|
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
||||||
|
this.gl.clearDepth(1.0);
|
||||||
|
this.gl.depthMask(true);
|
||||||
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getModelviewTransform(objectIndex: number): glmatrix.mat4 {
|
||||||
|
const transform = glmatrix.mat4.create();
|
||||||
|
glmatrix.mat4.rotateY(transform, transform, Math.PI / 2.0 * objectIndex);
|
||||||
|
glmatrix.mat4.translate(transform, transform, TEXT_TRANSLATION);
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cheap but effective backface culling.
|
||||||
|
protected shouldRenderObject(objectIndex: number): boolean {
|
||||||
|
const translation = this.camera.translation;
|
||||||
|
const extent = TEXT_TRANSLATION[2] * TEXT_SCALE[2];
|
||||||
|
switch (objectIndex) {
|
||||||
|
case 0: return translation[2] < -extent;
|
||||||
|
case 1: return translation[0] < -extent;
|
||||||
|
case 2: return translation[2] > extent;
|
||||||
|
default: return translation[0] > extent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get destAllocatedSize(): glmatrix.vec2 {
|
get destAllocatedSize(): glmatrix.vec2 {
|
||||||
return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height);
|
return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
destFramebuffer: WebGLFramebuffer | null = null;
|
|
||||||
|
|
||||||
get destUsedSize(): glmatrix.vec2 {
|
get destUsedSize(): glmatrix.vec2 {
|
||||||
return this.destAllocatedSize;
|
return this.destAllocatedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.clone([1.0, 1.0]);
|
|
||||||
|
|
||||||
private calculateWorldTransform(modelviewTranslation: glmatrix.vec3,
|
private calculateWorldTransform(modelviewTranslation: glmatrix.vec3,
|
||||||
modelviewScale: glmatrix.vec3):
|
modelviewScale: glmatrix.vec3):
|
||||||
glmatrix.mat4 {
|
glmatrix.mat4 {
|
||||||
|
@ -349,49 +389,9 @@ class ThreeDView extends PathfinderDemoView {
|
||||||
return transform;
|
return transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected clearForDirectRendering(): void {
|
|
||||||
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
|
||||||
this.gl.clearDepth(1.0);
|
|
||||||
this.gl.depthMask(true);
|
|
||||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get worldTransform() {
|
protected get worldTransform() {
|
||||||
return this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE);
|
return this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getModelviewTransform(objectIndex: number): glmatrix.mat4 {
|
|
||||||
const transform = glmatrix.mat4.create();
|
|
||||||
glmatrix.mat4.rotateY(transform, transform, Math.PI / 2.0 * objectIndex);
|
|
||||||
glmatrix.mat4.translate(transform, transform, TEXT_TRANSLATION);
|
|
||||||
return transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cheap but effective backface culling.
|
|
||||||
protected shouldRenderObject(objectIndex: number): boolean {
|
|
||||||
const translation = this.camera.translation;
|
|
||||||
const extent = TEXT_TRANSLATION[2] * TEXT_SCALE[2];
|
|
||||||
switch (objectIndex) {
|
|
||||||
case 0: return translation[2] < -extent;
|
|
||||||
case 1: return translation[0] < -extent;
|
|
||||||
case 2: return translation[2] > extent;
|
|
||||||
default: return translation[0] > extent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected directCurveProgramName: keyof ShaderMap<void> = 'direct3DCurve';
|
|
||||||
protected directInteriorProgramName: keyof ShaderMap<void> = 'direct3DInterior';
|
|
||||||
|
|
||||||
protected depthFunction: number = this.gl.LESS;
|
|
||||||
|
|
||||||
private _scale: number;
|
|
||||||
|
|
||||||
private appController: ThreeDController;
|
|
||||||
|
|
||||||
private cubeVertexPositionBuffer: WebGLBuffer;
|
|
||||||
private cubeIndexBuffer: WebGLBuffer;
|
|
||||||
|
|
||||||
camera: PerspectiveCamera;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
|
|
|
@ -15,6 +15,9 @@ import {PathfinderDemoView} from './view';
|
||||||
export type AntialiasingStrategyName = 'none' | 'ssaa' | 'ecaa';
|
export type AntialiasingStrategyName = 'none' | 'ssaa' | 'ecaa';
|
||||||
|
|
||||||
export abstract class AntialiasingStrategy {
|
export abstract class AntialiasingStrategy {
|
||||||
|
// True if direct rendering should occur.
|
||||||
|
shouldRenderDirect: boolean;
|
||||||
|
|
||||||
// Prepares any OpenGL data. This is only called on startup and canvas resize.
|
// Prepares any OpenGL data. This is only called on startup and canvas resize.
|
||||||
init(view: PathfinderDemoView): void {
|
init(view: PathfinderDemoView): void {
|
||||||
this.setFramebufferSize(view);
|
this.setFramebufferSize(view);
|
||||||
|
@ -43,12 +46,11 @@ export abstract class AntialiasingStrategy {
|
||||||
//
|
//
|
||||||
// This usually blits to the real framebuffer.
|
// This usually blits to the real framebuffer.
|
||||||
abstract resolve(view: PathfinderDemoView): void;
|
abstract resolve(view: PathfinderDemoView): void;
|
||||||
|
|
||||||
// True if direct rendering should occur.
|
|
||||||
shouldRenderDirect: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NoAAStrategy extends AntialiasingStrategy {
|
export class NoAAStrategy extends AntialiasingStrategy {
|
||||||
|
framebufferSize: glmatrix.vec2;
|
||||||
|
|
||||||
constructor(level: number, subpixelAA: boolean) {
|
constructor(level: number, subpixelAA: boolean) {
|
||||||
super();
|
super();
|
||||||
this.framebufferSize = glmatrix.vec2.create();
|
this.framebufferSize = glmatrix.vec2.create();
|
||||||
|
@ -77,6 +79,4 @@ export class NoAAStrategy extends AntialiasingStrategy {
|
||||||
get shouldRenderDirect() {
|
get shouldRenderDirect() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
framebufferSize: glmatrix.vec2;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,16 @@
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
import {AntialiasingStrategyName} from "./aa-strategy";
|
import {AntialiasingStrategyName} from "./aa-strategy";
|
||||||
|
import {FilePickerView} from "./file-picker";
|
||||||
import {ShaderLoader, ShaderMap, ShaderProgramSource} from './shader-loader';
|
import {ShaderLoader, ShaderMap, ShaderProgramSource} from './shader-loader';
|
||||||
import {expectNotNull, unwrapUndef, unwrapNull} from './utils';
|
import {expectNotNull, unwrapNull, unwrapUndef} from './utils';
|
||||||
import {PathfinderDemoView, Timings, TIMINGS} from "./view";
|
import {PathfinderDemoView, Timings, TIMINGS} from "./view";
|
||||||
import { FilePickerView } from "./file-picker";
|
|
||||||
|
|
||||||
export abstract class AppController {
|
export abstract class AppController {
|
||||||
|
protected canvas: HTMLCanvasElement;
|
||||||
|
|
||||||
|
protected screenshotButton: HTMLButtonElement | null;
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
const canvas = document.getElementById('pf-canvas') as HTMLCanvasElement;
|
const canvas = document.getElementById('pf-canvas') as HTMLCanvasElement;
|
||||||
}
|
}
|
||||||
|
@ -36,16 +40,25 @@ export abstract class AppController {
|
||||||
.then(data => this.fileLoaded(data));
|
.then(data => this.fileLoaded(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected canvas: HTMLCanvasElement;
|
|
||||||
|
|
||||||
protected screenshotButton: HTMLButtonElement | null;
|
|
||||||
|
|
||||||
protected abstract fileLoaded(data: ArrayBuffer): void;
|
protected abstract fileLoaded(data: ArrayBuffer): void;
|
||||||
|
|
||||||
protected abstract get defaultFile(): string;
|
protected abstract get defaultFile(): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class DemoAppController<View extends PathfinderDemoView> extends AppController {
|
export abstract class DemoAppController<View extends PathfinderDemoView> extends AppController {
|
||||||
|
view: Promise<View>;
|
||||||
|
|
||||||
|
protected abstract readonly builtinFileURI: string;
|
||||||
|
|
||||||
|
protected filePickerView: FilePickerView | null;
|
||||||
|
|
||||||
|
protected commonShaderSource: string | null;
|
||||||
|
protected shaderSources: ShaderMap<ShaderProgramSource> | null;
|
||||||
|
|
||||||
|
private aaLevelSelect: HTMLSelectElement | null;
|
||||||
|
private subpixelAASwitch: HTMLInputElement | null;
|
||||||
|
private fpsLabel: HTMLElement | null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@ -164,13 +177,15 @@ export abstract class DemoAppController<View extends PathfinderDemoView> extends
|
||||||
this.fpsLabel.classList.remove('invisible');
|
this.fpsLabel.classList.remove('invisible');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract createView(): View;
|
||||||
|
|
||||||
private updateAALevel() {
|
private updateAALevel() {
|
||||||
let aaType: AntialiasingStrategyName, aaLevel: number;
|
let aaType: AntialiasingStrategyName, aaLevel: number;
|
||||||
if (this.aaLevelSelect != null) {
|
if (this.aaLevelSelect != null) {
|
||||||
const selectedOption = this.aaLevelSelect.selectedOptions[0];
|
const selectedOption = this.aaLevelSelect.selectedOptions[0];
|
||||||
const aaValues = unwrapNull(/^([a-z-]+)(?:-([0-9]+))?$/.exec(selectedOption.value));
|
const aaValues = unwrapNull(/^([a-z-]+)(?:-([0-9]+))?$/.exec(selectedOption.value));
|
||||||
aaType = aaValues[1] as AntialiasingStrategyName;
|
aaType = aaValues[1] as AntialiasingStrategyName;
|
||||||
aaLevel = aaValues[2] === "" ? 1 : parseInt(aaValues[2]);
|
aaLevel = aaValues[2] === "" ? 1 : parseInt(aaValues[2], 10);
|
||||||
} else {
|
} else {
|
||||||
aaType = 'none';
|
aaType = 'none';
|
||||||
aaLevel = 0;
|
aaLevel = 0;
|
||||||
|
@ -204,19 +219,4 @@ export abstract class DemoAppController<View extends PathfinderDemoView> extends
|
||||||
// Fetch the file.
|
// Fetch the file.
|
||||||
this.fetchFile(selectedOption.value, this.builtinFileURI);
|
this.fetchFile(selectedOption.value, this.builtinFileURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract createView(): View;
|
|
||||||
|
|
||||||
protected abstract readonly builtinFileURI: string;
|
|
||||||
|
|
||||||
view: Promise<View>;
|
|
||||||
|
|
||||||
protected filePickerView: FilePickerView | null;
|
|
||||||
|
|
||||||
protected commonShaderSource: string | null;
|
|
||||||
protected shaderSources: ShaderMap<ShaderProgramSource> | null;
|
|
||||||
|
|
||||||
private aaLevelSelect: HTMLSelectElement | null;
|
|
||||||
private subpixelAASwitch: HTMLInputElement | null;
|
|
||||||
private fpsLabel: HTMLElement | null;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,17 +11,18 @@
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
import * as opentype from "opentype.js";
|
import * as opentype from "opentype.js";
|
||||||
|
|
||||||
import { AppController, DemoAppController } from "./app-controller";
|
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
||||||
import {PathfinderMeshData} from "./meshes";
|
import {AppController, DemoAppController} from "./app-controller";
|
||||||
import { BUILTIN_FONT_URI, TextFrame, TextRun, ExpandedMeshData, GlyphStore, PathfinderFont } from "./text";
|
|
||||||
import { assert, unwrapNull, PathfinderError } from "./utils";
|
|
||||||
import { PathfinderDemoView, Timings, MonochromePathfinderView } from "./view";
|
|
||||||
import { ShaderMap, ShaderProgramSource } from "./shader-loader";
|
|
||||||
import { AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy } from "./aa-strategy";
|
|
||||||
import SSAAStrategy from './ssaa-strategy';
|
|
||||||
import { OrthographicCamera } from './camera';
|
|
||||||
import { ECAAStrategy, ECAAMonochromeStrategy } from './ecaa-strategy';
|
|
||||||
import PathfinderBufferTexture from './buffer-texture';
|
import PathfinderBufferTexture from './buffer-texture';
|
||||||
|
import {OrthographicCamera} from './camera';
|
||||||
|
import {ECAAMonochromeStrategy, ECAAStrategy} from './ecaa-strategy';
|
||||||
|
import {PathfinderMeshData} from "./meshes";
|
||||||
|
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
|
||||||
|
import SSAAStrategy from './ssaa-strategy';
|
||||||
|
import {BUILTIN_FONT_URI, ExpandedMeshData, GlyphStore, PathfinderFont, TextFrame} from "./text";
|
||||||
|
import {TextRun} from "./text";
|
||||||
|
import {assert, PathfinderError, unwrapNull} from "./utils";
|
||||||
|
import {MonochromePathfinderView, PathfinderDemoView, Timings } from "./view";
|
||||||
|
|
||||||
const STRING: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
const STRING: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
||||||
|
@ -33,9 +34,9 @@ const MIN_FONT_SIZE: number = 6;
|
||||||
const MAX_FONT_SIZE: number = 200;
|
const MAX_FONT_SIZE: number = 200;
|
||||||
|
|
||||||
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
||||||
|
ecaa: ECAAMonochromeStrategy,
|
||||||
none: NoAAStrategy,
|
none: NoAAStrategy,
|
||||||
ssaa: SSAAStrategy,
|
ssaa: SSAAStrategy,
|
||||||
ecaa: ECAAMonochromeStrategy,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ElapsedTime {
|
interface ElapsedTime {
|
||||||
|
@ -50,6 +51,24 @@ interface AntialiasingStrategyTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
||||||
|
font: PathfinderFont | null;
|
||||||
|
textRun: TextRun | null;
|
||||||
|
|
||||||
|
protected readonly defaultFile: string = FONT;
|
||||||
|
protected readonly builtinFileURI: string = BUILTIN_FONT_URI;
|
||||||
|
|
||||||
|
private resultsModal: HTMLDivElement;
|
||||||
|
private resultsTableBody: HTMLTableSectionElement;
|
||||||
|
private resultsPartitioningTimeLabel: HTMLSpanElement;
|
||||||
|
|
||||||
|
private glyphStore: GlyphStore;
|
||||||
|
private baseMeshes: PathfinderMeshData;
|
||||||
|
private expandedMeshes: ExpandedMeshData;
|
||||||
|
|
||||||
|
private pixelsPerEm: number;
|
||||||
|
private elapsedTimes: ElapsedTime[];
|
||||||
|
private partitionTime: number;
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
@ -104,8 +123,8 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
||||||
view.uploadPathTransforms(1);
|
view.uploadPathTransforms(1);
|
||||||
view.uploadHints();
|
view.uploadHints();
|
||||||
view.attachMeshes([expandedMeshes.meshes]);
|
view.attachMeshes([expandedMeshes.meshes]);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createView(): BenchmarkTestView {
|
protected createView(): BenchmarkTestView {
|
||||||
|
@ -128,7 +147,7 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
||||||
renderedPromise.then(elapsedTime => {
|
renderedPromise.then(elapsedTime => {
|
||||||
this.elapsedTimes.push({ size: this.pixelsPerEm, time: elapsedTime });
|
this.elapsedTimes.push({ size: this.pixelsPerEm, time: elapsedTime });
|
||||||
|
|
||||||
if (this.pixelsPerEm == MAX_FONT_SIZE) {
|
if (this.pixelsPerEm === MAX_FONT_SIZE) {
|
||||||
this.showResults();
|
this.showResults();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -156,27 +175,29 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
||||||
|
|
||||||
window.jQuery(this.resultsModal).modal();
|
window.jQuery(this.resultsModal).modal();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly defaultFile: string = FONT;
|
|
||||||
protected readonly builtinFileURI: string = BUILTIN_FONT_URI;
|
|
||||||
|
|
||||||
private resultsModal: HTMLDivElement;
|
|
||||||
private resultsTableBody: HTMLTableSectionElement;
|
|
||||||
private resultsPartitioningTimeLabel: HTMLSpanElement;
|
|
||||||
|
|
||||||
private glyphStore: GlyphStore;
|
|
||||||
private baseMeshes: PathfinderMeshData;
|
|
||||||
private expandedMeshes: ExpandedMeshData;
|
|
||||||
|
|
||||||
private pixelsPerEm: number;
|
|
||||||
private elapsedTimes: ElapsedTime[];
|
|
||||||
private partitionTime: number;
|
|
||||||
|
|
||||||
font: PathfinderFont | null;
|
|
||||||
textRun: TextRun | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class BenchmarkTestView extends MonochromePathfinderView {
|
class BenchmarkTestView extends MonochromePathfinderView {
|
||||||
|
destFramebuffer: WebGLFramebuffer | null = null;
|
||||||
|
|
||||||
|
renderingPromiseCallback: ((time: number) => void) | null;
|
||||||
|
|
||||||
|
readonly bgColor: glmatrix.vec4 = glmatrix.vec4.clone([1.0, 1.0, 1.0, 0.0]);
|
||||||
|
readonly fgColor: glmatrix.vec4 = glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
|
||||||
|
|
||||||
|
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.clone([1.0, 1.0]);
|
||||||
|
|
||||||
|
protected directCurveProgramName: keyof ShaderMap<void> = 'directCurve';
|
||||||
|
protected directInteriorProgramName: keyof ShaderMap<void> = 'directInterior';
|
||||||
|
|
||||||
|
protected depthFunction: number = this.gl.GREATER;
|
||||||
|
|
||||||
|
protected camera: OrthographicCamera;
|
||||||
|
|
||||||
|
private _pixelsPerEm: number = 32.0;
|
||||||
|
|
||||||
|
private readonly appController: BenchmarkAppController;
|
||||||
|
|
||||||
constructor(appController: BenchmarkAppController,
|
constructor(appController: BenchmarkAppController,
|
||||||
commonShaderSource: string,
|
commonShaderSource: string,
|
||||||
shaderSources: ShaderMap<ShaderProgramSource>) {
|
shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||||
|
@ -189,6 +210,15 @@ class BenchmarkTestView extends MonochromePathfinderView {
|
||||||
this.camera.onZoom = () => this.setDirty();
|
this.camera.onZoom = () => this.setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadHints(): void {
|
||||||
|
const glyphCount = unwrapNull(this.appController.textRun).glyphIDs.length;
|
||||||
|
const pathHints = new Float32Array((glyphCount + 1) * 4);
|
||||||
|
|
||||||
|
const pathHintsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathHints');
|
||||||
|
pathHintsBufferTexture.upload(this.gl, pathHints);
|
||||||
|
this.pathHintsBufferTexture = pathHintsBufferTexture;
|
||||||
|
}
|
||||||
|
|
||||||
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
||||||
aaLevel: number,
|
aaLevel: number,
|
||||||
subpixelAA: boolean):
|
subpixelAA: boolean):
|
||||||
|
@ -241,17 +271,6 @@ class BenchmarkTestView extends MonochromePathfinderView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadHints(): void {
|
|
||||||
const glyphCount = unwrapNull(this.appController.textRun).glyphIDs.length;
|
|
||||||
const pathHints = new Float32Array((glyphCount + 1) * 4);
|
|
||||||
|
|
||||||
const pathHintsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathHints');
|
|
||||||
pathHintsBufferTexture.upload(this.gl, pathHints);
|
|
||||||
this.pathHintsBufferTexture = pathHintsBufferTexture;
|
|
||||||
}
|
|
||||||
|
|
||||||
destFramebuffer: WebGLFramebuffer | null = null;
|
|
||||||
|
|
||||||
get destAllocatedSize(): glmatrix.vec2 {
|
get destAllocatedSize(): glmatrix.vec2 {
|
||||||
return glmatrix.vec2.clone([this.canvas.width, this.canvas.height]);
|
return glmatrix.vec2.clone([this.canvas.width, this.canvas.height]);
|
||||||
}
|
}
|
||||||
|
@ -260,10 +279,6 @@ class BenchmarkTestView extends MonochromePathfinderView {
|
||||||
return this.destAllocatedSize;
|
return this.destAllocatedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly appController: BenchmarkAppController;
|
|
||||||
|
|
||||||
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.clone([1.0, 1.0]);
|
|
||||||
|
|
||||||
protected get worldTransform() {
|
protected get worldTransform() {
|
||||||
const transform = glmatrix.mat4.create();
|
const transform = glmatrix.mat4.create();
|
||||||
const translation = this.camera.translation;
|
const translation = this.camera.translation;
|
||||||
|
@ -293,20 +308,6 @@ class BenchmarkTestView extends MonochromePathfinderView {
|
||||||
this.uploadPathTransforms(1);
|
this.uploadPathTransforms(1);
|
||||||
this.setDirty();
|
this.setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderingPromiseCallback: ((time: number) => void) | null;
|
|
||||||
|
|
||||||
private _pixelsPerEm: number = 32.0;
|
|
||||||
|
|
||||||
readonly bgColor: glmatrix.vec4 = glmatrix.vec4.clone([1.0, 1.0, 1.0, 0.0]);
|
|
||||||
readonly fgColor: glmatrix.vec4 = glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
|
|
||||||
|
|
||||||
protected directCurveProgramName: keyof ShaderMap<void> = 'directCurve';
|
|
||||||
protected directInteriorProgramName: keyof ShaderMap<void> = 'directInterior';
|
|
||||||
|
|
||||||
protected depthFunction: number = this.gl.GREATER;
|
|
||||||
|
|
||||||
protected camera: OrthographicCamera;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
|
|
|
@ -14,6 +14,13 @@ import {setTextureParameters, UniformMap} from './gl-utils';
|
||||||
import {expectNotNull} from './utils';
|
import {expectNotNull} from './utils';
|
||||||
|
|
||||||
export default class PathfinderBufferTexture {
|
export default class PathfinderBufferTexture {
|
||||||
|
readonly texture: WebGLTexture;
|
||||||
|
readonly uniformName: string;
|
||||||
|
|
||||||
|
private size: glmatrix.vec2;
|
||||||
|
private capacity: glmatrix.vec2;
|
||||||
|
private glType: number;
|
||||||
|
|
||||||
constructor(gl: WebGLRenderingContext, uniformName: string) {
|
constructor(gl: WebGLRenderingContext, uniformName: string) {
|
||||||
this.texture = expectNotNull(gl.createTexture(), "Failed to create buffer texture!");
|
this.texture = expectNotNull(gl.createTexture(), "Failed to create buffer texture!");
|
||||||
this.size = glmatrix.vec2.create();
|
this.size = glmatrix.vec2.create();
|
||||||
|
@ -28,7 +35,7 @@ export default class PathfinderBufferTexture {
|
||||||
|
|
||||||
const glType = data instanceof Float32Array ? gl.FLOAT : gl.UNSIGNED_BYTE;
|
const glType = data instanceof Float32Array ? gl.FLOAT : gl.UNSIGNED_BYTE;
|
||||||
const area = Math.ceil(data.length / 4);
|
const area = Math.ceil(data.length / 4);
|
||||||
if (glType != this.glType || area > this.capacityArea) {
|
if (glType !== this.glType || area > this.capacityArea) {
|
||||||
const width = Math.ceil(Math.sqrt(area));
|
const width = Math.ceil(Math.sqrt(area));
|
||||||
const height = Math.ceil(area / width);
|
const height = Math.ceil(area / width);
|
||||||
this.size = glmatrix.vec2.fromValues(width, height);
|
this.size = glmatrix.vec2.fromValues(width, height);
|
||||||
|
@ -65,7 +72,7 @@ export default class PathfinderBufferTexture {
|
||||||
// Round data up to a multiple of 4 elements if necessary.
|
// Round data up to a multiple of 4 elements if necessary.
|
||||||
let remainderLength = data.length - splitIndex;
|
let remainderLength = data.length - splitIndex;
|
||||||
let remainder: Float32Array | Uint8Array;
|
let remainder: Float32Array | Uint8Array;
|
||||||
if (remainderLength % 4 == 0) {
|
if (remainderLength % 4 === 0) {
|
||||||
remainder = data.slice(splitIndex);
|
remainder = data.slice(splitIndex);
|
||||||
} else {
|
} else {
|
||||||
remainderLength += 4 - remainderLength % 4;
|
remainderLength += 4 - remainderLength % 4;
|
||||||
|
@ -101,11 +108,4 @@ export default class PathfinderBufferTexture {
|
||||||
private get capacityArea() {
|
private get capacityArea() {
|
||||||
return this.capacity[0] * this.capacity[1];
|
return this.capacity[0] * this.capacity[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly texture: WebGLTexture;
|
|
||||||
readonly uniformName: string;
|
|
||||||
private size: glmatrix.vec2;
|
|
||||||
private capacity: glmatrix.vec2;
|
|
||||||
private glType: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,17 +64,30 @@ interface PerspectiveMovementKeys {
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Camera {
|
export abstract class Camera {
|
||||||
|
protected canvas: HTMLCanvasElement;
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement) {
|
constructor(canvas: HTMLCanvasElement) {
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract zoomIn(): void;
|
abstract zoomIn(): void;
|
||||||
abstract zoomOut(): void;
|
abstract zoomOut(): void;
|
||||||
|
|
||||||
protected canvas: HTMLCanvasElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OrthographicCamera extends Camera {
|
export class OrthographicCamera extends Camera {
|
||||||
|
onPan: (() => void) | null;
|
||||||
|
onZoom: (() => void) | null;
|
||||||
|
|
||||||
|
translation: glmatrix.vec2;
|
||||||
|
scale: number;
|
||||||
|
|
||||||
|
private _bounds: glmatrix.vec4;
|
||||||
|
|
||||||
|
private readonly minScale: number;
|
||||||
|
private readonly maxScale: number;
|
||||||
|
private readonly scaleBounds: boolean;
|
||||||
|
private readonly ignoreBounds: boolean;
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement, options?: OrthographicCameraOptions) {
|
constructor(canvas: HTMLCanvasElement, options?: OrthographicCameraOptions) {
|
||||||
super(canvas);
|
super(canvas);
|
||||||
|
|
||||||
|
@ -122,6 +135,31 @@ export class OrthographicCamera extends Camera {
|
||||||
this.zoom(scale, mouseLocation);
|
this.zoom(scale, mouseLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zoomToFit(): void {
|
||||||
|
const upperLeft = glmatrix.vec2.clone([this._bounds[0], this._bounds[1]]);
|
||||||
|
const lowerRight = glmatrix.vec2.clone([this._bounds[2], this._bounds[3]]);
|
||||||
|
const width = this._bounds[2] - this._bounds[0];
|
||||||
|
const height = Math.abs(this._bounds[1] - this._bounds[3]);
|
||||||
|
|
||||||
|
// Scale appropriately.
|
||||||
|
this.scale = Math.min(this.canvas.width / width, this.canvas.height / height);
|
||||||
|
|
||||||
|
// Center.
|
||||||
|
this.translation = glmatrix.vec2.create();
|
||||||
|
glmatrix.vec2.lerp(this.translation, upperLeft, lowerRight, 0.5);
|
||||||
|
glmatrix.vec2.scale(this.translation, this.translation, -this.scale);
|
||||||
|
this.translation[0] += this.canvas.width * 0.5;
|
||||||
|
this.translation[1] += this.canvas.height * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomIn(): void {
|
||||||
|
this.zoom(ORTHOGRAPHIC_ZOOM_IN_FACTOR, this.centerPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomOut(): void {
|
||||||
|
this.zoom(ORTHOGRAPHIC_ZOOM_OUT_FACTOR, this.centerPoint);
|
||||||
|
}
|
||||||
|
|
||||||
private onMouseDown(event: MouseEvent): void {
|
private onMouseDown(event: MouseEvent): void {
|
||||||
this.canvas.classList.add('pf-grabbing');
|
this.canvas.classList.add('pf-grabbing');
|
||||||
}
|
}
|
||||||
|
@ -172,31 +210,6 @@ export class OrthographicCamera extends Camera {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomToFit(): void {
|
|
||||||
const upperLeft = glmatrix.vec2.clone([this._bounds[0], this._bounds[1]]);
|
|
||||||
const lowerRight = glmatrix.vec2.clone([this._bounds[2], this._bounds[3]]);
|
|
||||||
const width = this._bounds[2] - this._bounds[0];
|
|
||||||
const height = Math.abs(this._bounds[1] - this._bounds[3]);
|
|
||||||
|
|
||||||
// Scale appropriately.
|
|
||||||
this.scale = Math.min(this.canvas.width / width, this.canvas.height / height);
|
|
||||||
|
|
||||||
// Center.
|
|
||||||
this.translation = glmatrix.vec2.create();
|
|
||||||
glmatrix.vec2.lerp(this.translation, upperLeft, lowerRight, 0.5);
|
|
||||||
glmatrix.vec2.scale(this.translation, this.translation, -this.scale);
|
|
||||||
this.translation[0] += this.canvas.width * 0.5;
|
|
||||||
this.translation[1] += this.canvas.height * 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
zoomIn(): void {
|
|
||||||
this.zoom(ORTHOGRAPHIC_ZOOM_IN_FACTOR, this.centerPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
zoomOut(): void {
|
|
||||||
this.zoom(ORTHOGRAPHIC_ZOOM_OUT_FACTOR, this.centerPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
private zoom(scale: number, point: glmatrix.vec2): void {
|
private zoom(scale: number, point: glmatrix.vec2): void {
|
||||||
const absoluteTranslation = glmatrix.vec2.create();
|
const absoluteTranslation = glmatrix.vec2.create();
|
||||||
glmatrix.vec2.sub(absoluteTranslation, this.translation, point);
|
glmatrix.vec2.sub(absoluteTranslation, this.translation, point);
|
||||||
|
@ -227,22 +240,23 @@ export class OrthographicCamera extends Camera {
|
||||||
set bounds(newBounds: glmatrix.vec4) {
|
set bounds(newBounds: glmatrix.vec4) {
|
||||||
this._bounds = glmatrix.vec4.clone(newBounds);
|
this._bounds = glmatrix.vec4.clone(newBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPan: (() => void) | null;
|
|
||||||
onZoom: (() => void) | null;
|
|
||||||
|
|
||||||
private _bounds: glmatrix.vec4;
|
|
||||||
|
|
||||||
translation: glmatrix.vec2;
|
|
||||||
scale: number;
|
|
||||||
|
|
||||||
private readonly minScale: number;
|
|
||||||
private readonly maxScale: number;
|
|
||||||
private readonly scaleBounds: boolean;
|
|
||||||
private readonly ignoreBounds: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PerspectiveCamera extends Camera {
|
export class PerspectiveCamera extends Camera {
|
||||||
|
onChange: (() => void) | null;
|
||||||
|
|
||||||
|
translation: glmatrix.vec3;
|
||||||
|
|
||||||
|
/// Yaw and pitch Euler angles.
|
||||||
|
rotation: glmatrix.vec2;
|
||||||
|
|
||||||
|
private movementDelta: glmatrix.vec3;
|
||||||
|
// If W, A, S, D are pressed
|
||||||
|
private wasdPress: PerspectiveMovementKeys;
|
||||||
|
private movementInterval: number | null;
|
||||||
|
|
||||||
|
private readonly innerCollisionExtent: number;
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement, options?: PerspectiveCameraOptions) {
|
constructor(canvas: HTMLCanvasElement, options?: PerspectiveCameraOptions) {
|
||||||
super(canvas);
|
super(canvas);
|
||||||
|
|
||||||
|
@ -272,6 +286,14 @@ export class PerspectiveCamera extends Camera {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zoomIn(): void {
|
||||||
|
// TODO(pcwalton)
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomOut(): void {
|
||||||
|
// TODO(pcwalton)
|
||||||
|
}
|
||||||
|
|
||||||
private onMouseDown(event: MouseEvent): void {
|
private onMouseDown(event: MouseEvent): void {
|
||||||
if (document.pointerLockElement !== this.canvas) {
|
if (document.pointerLockElement !== this.canvas) {
|
||||||
this.canvas.requestPointerLock();
|
this.canvas.requestPointerLock();
|
||||||
|
@ -391,26 +413,4 @@ export class PerspectiveCamera extends Camera {
|
||||||
glmatrix.mat4.rotateY(matrix, matrix, this.rotation[0]);
|
glmatrix.mat4.rotateY(matrix, matrix, this.rotation[0]);
|
||||||
return matrix;
|
return matrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomIn(): void {
|
|
||||||
// TODO(pcwalton)
|
|
||||||
}
|
|
||||||
|
|
||||||
zoomOut(): void {
|
|
||||||
// TODO(pcwalton)
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange: (() => void) | null;
|
|
||||||
|
|
||||||
translation: glmatrix.vec3;
|
|
||||||
|
|
||||||
/// Yaw and pitch Euler angles.
|
|
||||||
rotation: glmatrix.vec2;
|
|
||||||
|
|
||||||
private movementDelta: glmatrix.vec3;
|
|
||||||
// If W, A, S, D are pressed
|
|
||||||
private wasdPress: PerspectiveMovementKeys;
|
|
||||||
private movementInterval: number | null;
|
|
||||||
|
|
||||||
private readonly innerCollisionExtent: number;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
|
|
||||||
import {AntialiasingStrategy} from './aa-strategy';
|
import {AntialiasingStrategy} from './aa-strategy';
|
||||||
|
import PathfinderBufferTexture from './buffer-texture';
|
||||||
import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
|
import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
|
||||||
import {createFramebufferDepthTexture, setTextureParameters, UniformMap} from './gl-utils';
|
import {createFramebufferDepthTexture, setTextureParameters, UniformMap} from './gl-utils';
|
||||||
import {WebGLVertexArrayObject} from './gl-utils';
|
import {WebGLVertexArrayObject} from './gl-utils';
|
||||||
|
@ -18,7 +19,6 @@ import {B_QUAD_LOWER_INDICES_OFFSET, B_QUAD_SIZE, B_QUAD_UPPER_INDICES_OFFSET} f
|
||||||
import {PathfinderShaderProgram} from './shader-loader';
|
import {PathfinderShaderProgram} from './shader-loader';
|
||||||
import {UINT32_SIZE, unwrapNull} from './utils';
|
import {UINT32_SIZE, unwrapNull} from './utils';
|
||||||
import {MonochromePathfinderView} from './view';
|
import {MonochromePathfinderView} from './view';
|
||||||
import PathfinderBufferTexture from './buffer-texture';
|
|
||||||
|
|
||||||
interface UpperAndLower<T> {
|
interface UpperAndLower<T> {
|
||||||
upper: T;
|
upper: T;
|
||||||
|
@ -26,6 +26,27 @@ interface UpperAndLower<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class ECAAStrategy extends AntialiasingStrategy {
|
export abstract class ECAAStrategy extends AntialiasingStrategy {
|
||||||
|
abstract shouldRenderDirect: boolean;
|
||||||
|
|
||||||
|
protected directColorTexture: WebGLTexture;
|
||||||
|
protected directPathIDTexture: WebGLTexture;
|
||||||
|
protected aaDepthTexture: WebGLTexture;
|
||||||
|
|
||||||
|
protected supersampledFramebufferSize: glmatrix.vec2;
|
||||||
|
protected destFramebufferSize: glmatrix.vec2;
|
||||||
|
|
||||||
|
protected subpixelAA: boolean;
|
||||||
|
|
||||||
|
private bVertexPositionBufferTexture: PathfinderBufferTexture;
|
||||||
|
private bVertexPathIDBufferTexture: PathfinderBufferTexture;
|
||||||
|
private directFramebuffer: WebGLFramebuffer;
|
||||||
|
private aaAlphaTexture: WebGLTexture;
|
||||||
|
private aaFramebuffer: WebGLFramebuffer;
|
||||||
|
private coverVAO: WebGLVertexArrayObject;
|
||||||
|
private lineVAOs: UpperAndLower<WebGLVertexArrayObject>;
|
||||||
|
private curveVAOs: UpperAndLower<WebGLVertexArrayObject>;
|
||||||
|
private resolveVAO: WebGLVertexArrayObject;
|
||||||
|
|
||||||
constructor(level: number, subpixelAA: boolean) {
|
constructor(level: number, subpixelAA: boolean) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -67,6 +88,58 @@ export abstract class ECAAStrategy extends AntialiasingStrategy {
|
||||||
view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, null);
|
view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepare(view: MonochromePathfinderView) {
|
||||||
|
const usedSize = this.supersampledUsedSize(view);
|
||||||
|
view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, this.directFramebuffer);
|
||||||
|
view.gl.viewport(0,
|
||||||
|
0,
|
||||||
|
this.supersampledFramebufferSize[0],
|
||||||
|
this.supersampledFramebufferSize[1]);
|
||||||
|
view.gl.scissor(0, 0, usedSize[0], usedSize[1]);
|
||||||
|
view.gl.enable(view.gl.SCISSOR_TEST);
|
||||||
|
|
||||||
|
// Clear out the color and depth textures.
|
||||||
|
view.drawBuffersExt.drawBuffersWEBGL([
|
||||||
|
view.drawBuffersExt.COLOR_ATTACHMENT0_WEBGL,
|
||||||
|
view.drawBuffersExt.NONE,
|
||||||
|
]);
|
||||||
|
view.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
||||||
|
view.gl.clearDepth(0.0);
|
||||||
|
view.gl.depthMask(true);
|
||||||
|
view.gl.clear(view.gl.COLOR_BUFFER_BIT | view.gl.DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
// Clear out the path ID texture.
|
||||||
|
view.drawBuffersExt.drawBuffersWEBGL([
|
||||||
|
view.drawBuffersExt.NONE,
|
||||||
|
view.drawBuffersExt.COLOR_ATTACHMENT1_WEBGL,
|
||||||
|
]);
|
||||||
|
view.gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
||||||
|
view.gl.clear(view.gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
// Render to both textures.
|
||||||
|
view.drawBuffersExt.drawBuffersWEBGL([
|
||||||
|
view.drawBuffersExt.COLOR_ATTACHMENT0_WEBGL,
|
||||||
|
view.drawBuffersExt.COLOR_ATTACHMENT1_WEBGL,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
antialias(view: MonochromePathfinderView) {
|
||||||
|
// Detect edges if necessary.
|
||||||
|
this.detectEdgesIfNecessary(view);
|
||||||
|
|
||||||
|
// Conservatively cover.
|
||||||
|
this.cover(view);
|
||||||
|
|
||||||
|
// Antialias.
|
||||||
|
this.antialiasLines(view);
|
||||||
|
this.antialiasCurves(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(view: MonochromePathfinderView) {
|
||||||
|
// Resolve the antialiasing.
|
||||||
|
this.resolveAA(view);
|
||||||
|
}
|
||||||
|
|
||||||
get transform(): glmatrix.mat4 {
|
get transform(): glmatrix.mat4 {
|
||||||
return glmatrix.mat4.create();
|
return glmatrix.mat4.create();
|
||||||
}
|
}
|
||||||
|
@ -82,6 +155,30 @@ export abstract class ECAAStrategy extends AntialiasingStrategy {
|
||||||
this.directDepthTexture);
|
this.directDepthTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected setCoverDepthState(view: MonochromePathfinderView): void {
|
||||||
|
view.gl.disable(view.gl.DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setResolveDepthState(view: MonochromePathfinderView): void {
|
||||||
|
view.gl.disable(view.gl.DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected supersampledUsedSize(view: MonochromePathfinderView): glmatrix.vec2 {
|
||||||
|
const usedSize = glmatrix.vec2.create();
|
||||||
|
glmatrix.vec2.mul(usedSize, view.destUsedSize, this.supersampleScale);
|
||||||
|
return usedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract getResolveProgram(view: MonochromePathfinderView): PathfinderShaderProgram;
|
||||||
|
protected abstract initEdgeDetectFramebuffer(view: MonochromePathfinderView): void;
|
||||||
|
protected abstract createEdgeDetectVAO(view: MonochromePathfinderView): void;
|
||||||
|
protected abstract detectEdgesIfNecessary(view: MonochromePathfinderView): void;
|
||||||
|
protected abstract clearForCover(view: MonochromePathfinderView): void;
|
||||||
|
protected abstract setAADepthState(view: MonochromePathfinderView): void;
|
||||||
|
protected abstract clearForResolve(view: MonochromePathfinderView): void;
|
||||||
|
protected abstract setResolveUniforms(view: MonochromePathfinderView,
|
||||||
|
program: PathfinderShaderProgram): void;
|
||||||
|
|
||||||
private initAAAlphaFramebuffer(view: MonochromePathfinderView) {
|
private initAAAlphaFramebuffer(view: MonochromePathfinderView) {
|
||||||
this.aaAlphaTexture = unwrapNull(view.gl.createTexture());
|
this.aaAlphaTexture = unwrapNull(view.gl.createTexture());
|
||||||
view.gl.activeTexture(view.gl.TEXTURE0);
|
view.gl.activeTexture(view.gl.TEXTURE0);
|
||||||
|
@ -148,8 +245,8 @@ export abstract class ECAAStrategy extends AntialiasingStrategy {
|
||||||
view.vertexArrayObjectExt.bindVertexArrayOES(vaos[direction]);
|
view.vertexArrayObjectExt.bindVertexArrayOES(vaos[direction]);
|
||||||
|
|
||||||
const lineIndexBuffer = {
|
const lineIndexBuffer = {
|
||||||
upper: view.meshes[0].edgeUpperLineIndices,
|
|
||||||
lower: view.meshes[0].edgeLowerLineIndices,
|
lower: view.meshes[0].edgeLowerLineIndices,
|
||||||
|
upper: view.meshes[0].edgeUpperLineIndices,
|
||||||
}[direction];
|
}[direction];
|
||||||
|
|
||||||
view.gl.useProgram(lineProgram.program);
|
view.gl.useProgram(lineProgram.program);
|
||||||
|
@ -183,8 +280,8 @@ export abstract class ECAAStrategy extends AntialiasingStrategy {
|
||||||
view.vertexArrayObjectExt.bindVertexArrayOES(vaos[direction]);
|
view.vertexArrayObjectExt.bindVertexArrayOES(vaos[direction]);
|
||||||
|
|
||||||
const curveIndexBuffer = {
|
const curveIndexBuffer = {
|
||||||
upper: view.meshes[0].edgeUpperCurveIndices,
|
|
||||||
lower: view.meshes[0].edgeLowerCurveIndices,
|
lower: view.meshes[0].edgeLowerCurveIndices,
|
||||||
|
upper: view.meshes[0].edgeUpperCurveIndices,
|
||||||
}[direction];
|
}[direction];
|
||||||
|
|
||||||
view.gl.useProgram(curveProgram.program);
|
view.gl.useProgram(curveProgram.program);
|
||||||
|
@ -227,58 +324,6 @@ export abstract class ECAAStrategy extends AntialiasingStrategy {
|
||||||
view.vertexArrayObjectExt.bindVertexArrayOES(null);
|
view.vertexArrayObjectExt.bindVertexArrayOES(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare(view: MonochromePathfinderView) {
|
|
||||||
const usedSize = this.supersampledUsedSize(view);;
|
|
||||||
view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, this.directFramebuffer);
|
|
||||||
view.gl.viewport(0,
|
|
||||||
0,
|
|
||||||
this.supersampledFramebufferSize[0],
|
|
||||||
this.supersampledFramebufferSize[1]);
|
|
||||||
view.gl.scissor(0, 0, usedSize[0], usedSize[1]);
|
|
||||||
view.gl.enable(view.gl.SCISSOR_TEST);
|
|
||||||
|
|
||||||
// Clear out the color and depth textures.
|
|
||||||
view.drawBuffersExt.drawBuffersWEBGL([
|
|
||||||
view.drawBuffersExt.COLOR_ATTACHMENT0_WEBGL,
|
|
||||||
view.drawBuffersExt.NONE,
|
|
||||||
]);
|
|
||||||
view.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
|
||||||
view.gl.clearDepth(0.0);
|
|
||||||
view.gl.depthMask(true);
|
|
||||||
view.gl.clear(view.gl.COLOR_BUFFER_BIT | view.gl.DEPTH_BUFFER_BIT);
|
|
||||||
|
|
||||||
// Clear out the path ID texture.
|
|
||||||
view.drawBuffersExt.drawBuffersWEBGL([
|
|
||||||
view.drawBuffersExt.NONE,
|
|
||||||
view.drawBuffersExt.COLOR_ATTACHMENT1_WEBGL,
|
|
||||||
]);
|
|
||||||
view.gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
|
||||||
view.gl.clear(view.gl.COLOR_BUFFER_BIT);
|
|
||||||
|
|
||||||
// Render to both textures.
|
|
||||||
view.drawBuffersExt.drawBuffersWEBGL([
|
|
||||||
view.drawBuffersExt.COLOR_ATTACHMENT0_WEBGL,
|
|
||||||
view.drawBuffersExt.COLOR_ATTACHMENT1_WEBGL,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
antialias(view: MonochromePathfinderView) {
|
|
||||||
// Detect edges if necessary.
|
|
||||||
this.detectEdgesIfNecessary(view);
|
|
||||||
|
|
||||||
// Conservatively cover.
|
|
||||||
this.cover(view);
|
|
||||||
|
|
||||||
// Antialias.
|
|
||||||
this.antialiasLines(view);
|
|
||||||
this.antialiasCurves(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(view: MonochromePathfinderView) {
|
|
||||||
// Resolve the antialiasing.
|
|
||||||
this.resolveAA(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
private cover(view: MonochromePathfinderView) {
|
private cover(view: MonochromePathfinderView) {
|
||||||
// Set state for conservative coverage.
|
// Set state for conservative coverage.
|
||||||
const coverProgram = view.shaderPrograms.ecaaCover;
|
const coverProgram = view.shaderPrograms.ecaaCover;
|
||||||
|
@ -350,8 +395,8 @@ export abstract class ECAAStrategy extends AntialiasingStrategy {
|
||||||
view.vertexArrayObjectExt.bindVertexArrayOES(this.lineVAOs[direction]);
|
view.vertexArrayObjectExt.bindVertexArrayOES(this.lineVAOs[direction]);
|
||||||
view.gl.uniform1i(uniforms.uLowerPart, direction === 'lower' ? 1 : 0);
|
view.gl.uniform1i(uniforms.uLowerPart, direction === 'lower' ? 1 : 0);
|
||||||
const count = {
|
const count = {
|
||||||
upper: view.meshData[0].edgeUpperLineIndexCount,
|
|
||||||
lower: view.meshData[0].edgeLowerLineIndexCount,
|
lower: view.meshData[0].edgeLowerLineIndexCount,
|
||||||
|
upper: view.meshData[0].edgeUpperLineIndexCount,
|
||||||
}[direction];
|
}[direction];
|
||||||
view.instancedArraysExt.drawElementsInstancedANGLE(view.gl.TRIANGLES,
|
view.instancedArraysExt.drawElementsInstancedANGLE(view.gl.TRIANGLES,
|
||||||
6,
|
6,
|
||||||
|
@ -375,8 +420,8 @@ export abstract class ECAAStrategy extends AntialiasingStrategy {
|
||||||
view.vertexArrayObjectExt.bindVertexArrayOES(this.curveVAOs[direction]);
|
view.vertexArrayObjectExt.bindVertexArrayOES(this.curveVAOs[direction]);
|
||||||
view.gl.uniform1i(uniforms.uLowerPart, direction === 'lower' ? 1 : 0);
|
view.gl.uniform1i(uniforms.uLowerPart, direction === 'lower' ? 1 : 0);
|
||||||
const count = {
|
const count = {
|
||||||
upper: view.meshData[0].edgeUpperCurveIndexCount,
|
|
||||||
lower: view.meshData[0].edgeLowerCurveIndexCount,
|
lower: view.meshData[0].edgeLowerCurveIndexCount,
|
||||||
|
upper: view.meshData[0].edgeUpperCurveIndexCount,
|
||||||
}[direction];
|
}[direction];
|
||||||
view.instancedArraysExt.drawElementsInstancedANGLE(view.gl.TRIANGLES,
|
view.instancedArraysExt.drawElementsInstancedANGLE(view.gl.TRIANGLES,
|
||||||
6,
|
6,
|
||||||
|
@ -422,30 +467,6 @@ export abstract class ECAAStrategy extends AntialiasingStrategy {
|
||||||
view.vertexArrayObjectExt.bindVertexArrayOES(null);
|
view.vertexArrayObjectExt.bindVertexArrayOES(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected setCoverDepthState(view: MonochromePathfinderView): void {
|
|
||||||
view.gl.disable(view.gl.DEPTH_TEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected setResolveDepthState(view: MonochromePathfinderView): void {
|
|
||||||
view.gl.disable(view.gl.DEPTH_TEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected supersampledUsedSize(view: MonochromePathfinderView): glmatrix.vec2 {
|
|
||||||
const usedSize = glmatrix.vec2.create();
|
|
||||||
glmatrix.vec2.mul(usedSize, view.destUsedSize, this.supersampleScale);
|
|
||||||
return usedSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract getResolveProgram(view: MonochromePathfinderView): PathfinderShaderProgram;
|
|
||||||
protected abstract initEdgeDetectFramebuffer(view: MonochromePathfinderView): void;
|
|
||||||
protected abstract createEdgeDetectVAO(view: MonochromePathfinderView): void;
|
|
||||||
protected abstract detectEdgesIfNecessary(view: MonochromePathfinderView): void;
|
|
||||||
protected abstract clearForCover(view: MonochromePathfinderView): void;
|
|
||||||
protected abstract setAADepthState(view: MonochromePathfinderView): void;
|
|
||||||
protected abstract clearForResolve(view: MonochromePathfinderView): void;
|
|
||||||
protected abstract setResolveUniforms(view: MonochromePathfinderView,
|
|
||||||
program: PathfinderShaderProgram): void;
|
|
||||||
|
|
||||||
protected get directDepthTexture(): WebGLTexture | null {
|
protected get directDepthTexture(): WebGLTexture | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -453,27 +474,6 @@ export abstract class ECAAStrategy extends AntialiasingStrategy {
|
||||||
protected get supersampleScale(): glmatrix.vec2 {
|
protected get supersampleScale(): glmatrix.vec2 {
|
||||||
return glmatrix.vec2.fromValues(this.subpixelAA ? 3.0 : 1.0, 1.0);
|
return glmatrix.vec2.fromValues(this.subpixelAA ? 3.0 : 1.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract shouldRenderDirect: boolean;
|
|
||||||
|
|
||||||
private bVertexPositionBufferTexture: PathfinderBufferTexture;
|
|
||||||
private bVertexPathIDBufferTexture: PathfinderBufferTexture;
|
|
||||||
private directFramebuffer: WebGLFramebuffer;
|
|
||||||
private aaAlphaTexture: WebGLTexture;
|
|
||||||
private aaFramebuffer: WebGLFramebuffer;
|
|
||||||
private coverVAO: WebGLVertexArrayObject;
|
|
||||||
private lineVAOs: UpperAndLower<WebGLVertexArrayObject>;
|
|
||||||
private curveVAOs: UpperAndLower<WebGLVertexArrayObject>;
|
|
||||||
private resolveVAO: WebGLVertexArrayObject;
|
|
||||||
|
|
||||||
protected directColorTexture: WebGLTexture;
|
|
||||||
protected directPathIDTexture: WebGLTexture;
|
|
||||||
protected aaDepthTexture: WebGLTexture;
|
|
||||||
|
|
||||||
protected supersampledFramebufferSize: glmatrix.vec2;
|
|
||||||
protected destFramebufferSize: glmatrix.vec2;
|
|
||||||
|
|
||||||
protected subpixelAA: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ECAAMonochromeStrategy extends ECAAStrategy {
|
export class ECAAMonochromeStrategy extends ECAAStrategy {
|
||||||
|
@ -515,6 +515,13 @@ export class ECAAMonochromeStrategy extends ECAAStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ECAAMulticolorStrategy extends ECAAStrategy {
|
export class ECAAMulticolorStrategy extends ECAAStrategy {
|
||||||
|
private _directDepthTexture: WebGLTexture;
|
||||||
|
|
||||||
|
private edgeDetectFramebuffer: WebGLFramebuffer;
|
||||||
|
private edgeDetectVAO: WebGLVertexArrayObject;
|
||||||
|
private bgColorTexture: WebGLTexture;
|
||||||
|
private fgColorTexture: WebGLTexture;
|
||||||
|
|
||||||
protected getResolveProgram(view: MonochromePathfinderView): PathfinderShaderProgram {
|
protected getResolveProgram(view: MonochromePathfinderView): PathfinderShaderProgram {
|
||||||
return view.shaderPrograms.ecaaMultiResolve;
|
return view.shaderPrograms.ecaaMultiResolve;
|
||||||
}
|
}
|
||||||
|
@ -627,11 +634,4 @@ export class ECAAMulticolorStrategy extends ECAAStrategy {
|
||||||
protected get directDepthTexture(): WebGLTexture {
|
protected get directDepthTexture(): WebGLTexture {
|
||||||
return this._directDepthTexture;
|
return this._directDepthTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _directDepthTexture: WebGLTexture;
|
|
||||||
|
|
||||||
private edgeDetectFramebuffer: WebGLFramebuffer;
|
|
||||||
private edgeDetectVAO: WebGLVertexArrayObject;
|
|
||||||
private bgColorTexture: WebGLTexture;
|
|
||||||
private fgColorTexture: WebGLTexture;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,17 +11,21 @@
|
||||||
import {expectNotNull} from "./utils";
|
import {expectNotNull} from "./utils";
|
||||||
|
|
||||||
export class FilePickerView {
|
export class FilePickerView {
|
||||||
|
static create(): FilePickerView | null {
|
||||||
|
const element = document.getElementById('pf-file-select') as (HTMLInputElement | null);
|
||||||
|
return element == null ? null : new FilePickerView(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFileLoaded: ((fileData: ArrayBuffer) => void) | null;
|
||||||
|
|
||||||
|
private readonly element: HTMLInputElement;
|
||||||
|
|
||||||
private constructor(element: HTMLInputElement) {
|
private constructor(element: HTMLInputElement) {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this.onFileLoaded = null;
|
this.onFileLoaded = null;
|
||||||
element.addEventListener('change', event => this.loadFile(event), false);
|
element.addEventListener('change', event => this.loadFile(event), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(): FilePickerView | null {
|
|
||||||
const element = document.getElementById('pf-file-select') as (HTMLInputElement | null);
|
|
||||||
return element == null ? null : new FilePickerView(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
open() {
|
open() {
|
||||||
this.element.click();
|
this.element.click();
|
||||||
}
|
}
|
||||||
|
@ -36,8 +40,4 @@ export class FilePickerView {
|
||||||
}, false);
|
}, false);
|
||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFileLoaded: ((fileData: ArrayBuffer) => void) | null;
|
|
||||||
|
|
||||||
private readonly element: HTMLInputElement;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
|
|
||||||
import {UINT32_SIZE, assert, unwrapNull} from './utils';
|
import {assert, UINT32_SIZE, unwrapNull} from './utils';
|
||||||
|
|
||||||
export type WebGLVertexArrayObject = any;
|
export type WebGLVertexArrayObject = any;
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ export function createFramebuffer(gl: WebGLRenderingContext,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE,
|
assert(gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE,
|
||||||
"Framebuffer was incomplete!");
|
"Framebuffer was incomplete!");
|
||||||
return framebuffer;
|
return framebuffer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,20 +11,20 @@
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
import * as opentype from "opentype.js";
|
import * as opentype from "opentype.js";
|
||||||
|
|
||||||
|
import {Font} from 'opentype.js';
|
||||||
import {AppController} from "./app-controller";
|
import {AppController} from "./app-controller";
|
||||||
import {OrthographicCamera} from "./camera";
|
import {OrthographicCamera} from "./camera";
|
||||||
import {FilePickerView} from './file-picker';
|
import {FilePickerView} from './file-picker';
|
||||||
import {B_QUAD_SIZE, B_QUAD_UPPER_LEFT_VERTEX_OFFSET} from "./meshes";
|
|
||||||
import {B_QUAD_UPPER_RIGHT_VERTEX_OFFSET} from "./meshes";
|
import {B_QUAD_UPPER_RIGHT_VERTEX_OFFSET} from "./meshes";
|
||||||
import {B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET, B_QUAD_LOWER_LEFT_VERTEX_OFFSET} from "./meshes";
|
import {B_QUAD_LOWER_LEFT_VERTEX_OFFSET, B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET} from "./meshes";
|
||||||
import {B_QUAD_LOWER_RIGHT_VERTEX_OFFSET} from "./meshes";
|
import {B_QUAD_LOWER_RIGHT_VERTEX_OFFSET} from "./meshes";
|
||||||
import {B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET, PathfinderMeshData} from "./meshes";
|
import {B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET, PathfinderMeshData} from "./meshes";
|
||||||
import {SVGLoader, BUILTIN_SVG_URI} from './svg-loader';
|
import {B_QUAD_SIZE, B_QUAD_UPPER_LEFT_VERTEX_OFFSET} from "./meshes";
|
||||||
|
import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
|
||||||
import {BUILTIN_FONT_URI, TextRun} from "./text";
|
import {BUILTIN_FONT_URI, TextRun} from "./text";
|
||||||
import { GlyphStore, TextFrame, PathfinderFont } from "./text";
|
import {GlyphStore, PathfinderFont, TextFrame} from "./text";
|
||||||
import {unwrapNull, UINT32_SIZE, UINT32_MAX, assert} from "./utils";
|
import {assert, UINT32_MAX, UINT32_SIZE, unwrapNull} from "./utils";
|
||||||
import {PathfinderView} from "./view";
|
import {PathfinderView} from "./view";
|
||||||
import {Font} from 'opentype.js';
|
|
||||||
|
|
||||||
const CHARACTER: string = 'A';
|
const CHARACTER: string = 'A';
|
||||||
|
|
||||||
|
@ -49,6 +49,22 @@ const SVG_SCALE: number = 1.0;
|
||||||
type FileType = 'font' | 'svg';
|
type FileType = 'font' | 'svg';
|
||||||
|
|
||||||
class MeshDebuggerAppController extends AppController {
|
class MeshDebuggerAppController extends AppController {
|
||||||
|
meshes: PathfinderMeshData | null;
|
||||||
|
|
||||||
|
protected readonly defaultFile: string = FONT;
|
||||||
|
|
||||||
|
private file: PathfinderFont | SVGLoader | null;
|
||||||
|
private fileType: FileType;
|
||||||
|
private fileData: ArrayBuffer | null;
|
||||||
|
|
||||||
|
private openModal: HTMLElement;
|
||||||
|
private openFileSelect: HTMLSelectElement;
|
||||||
|
private fontPathSelectGroup: HTMLElement;
|
||||||
|
private fontPathSelect: HTMLSelectElement;
|
||||||
|
|
||||||
|
private filePicker: FilePickerView;
|
||||||
|
private view: MeshDebuggerView;
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
@ -78,6 +94,43 @@ class MeshDebuggerAppController extends AppController {
|
||||||
this.loadInitialFile(BUILTIN_FONT_URI);
|
this.loadInitialFile(BUILTIN_FONT_URI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fileLoaded(fileData: ArrayBuffer): void {
|
||||||
|
while (this.fontPathSelect.lastChild != null)
|
||||||
|
this.fontPathSelect.removeChild(this.fontPathSelect.lastChild);
|
||||||
|
|
||||||
|
this.fontPathSelectGroup.classList.remove('pf-display-none');
|
||||||
|
|
||||||
|
if (this.fileType === 'font')
|
||||||
|
this.fontLoaded(fileData);
|
||||||
|
else if (this.fileType === 'svg')
|
||||||
|
this.svgLoaded(fileData);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected loadPath(opentypeGlyph?: opentype.Glyph | null) {
|
||||||
|
window.jQuery(this.openModal).modal('hide');
|
||||||
|
|
||||||
|
let promise: Promise<PathfinderMeshData>;
|
||||||
|
|
||||||
|
if (this.file instanceof PathfinderFont && this.fileData != null) {
|
||||||
|
if (opentypeGlyph == null) {
|
||||||
|
const glyphIndex = parseInt(this.fontPathSelect.selectedOptions[0].value, 10);
|
||||||
|
opentypeGlyph = this.file.opentypeFont.glyphs.get(glyphIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
const glyphStorage = new GlyphStore(this.file, [(opentypeGlyph as any).index]);
|
||||||
|
promise = glyphStorage.partition().then(result => result.meshes);
|
||||||
|
} else if (this.file instanceof SVGLoader) {
|
||||||
|
promise = this.file.partition(this.fontPathSelect.selectedIndex);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.then(meshes => {
|
||||||
|
this.meshes = meshes;
|
||||||
|
this.view.attachMeshes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private showOpenDialog(): void {
|
private showOpenDialog(): void {
|
||||||
window.jQuery(this.openModal).modal();
|
window.jQuery(this.openModal).modal();
|
||||||
}
|
}
|
||||||
|
@ -98,18 +151,6 @@ class MeshDebuggerAppController extends AppController {
|
||||||
this.fetchFile(results[2], BUILTIN_URIS[this.fileType]);
|
this.fetchFile(results[2], BUILTIN_URIS[this.fileType]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fileLoaded(fileData: ArrayBuffer): void {
|
|
||||||
while (this.fontPathSelect.lastChild != null)
|
|
||||||
this.fontPathSelect.removeChild(this.fontPathSelect.lastChild);
|
|
||||||
|
|
||||||
this.fontPathSelectGroup.classList.remove('pf-display-none');
|
|
||||||
|
|
||||||
if (this.fileType === 'font')
|
|
||||||
this.fontLoaded(fileData);
|
|
||||||
else if (this.fileType === 'svg')
|
|
||||||
this.svgLoaded(fileData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private fontLoaded(fileData: ArrayBuffer): void {
|
private fontLoaded(fileData: ArrayBuffer): void {
|
||||||
this.file = new PathfinderFont(fileData);
|
this.file = new PathfinderFont(fileData);
|
||||||
this.fileData = fileData;
|
this.fileData = fileData;
|
||||||
|
@ -141,50 +182,13 @@ class MeshDebuggerAppController extends AppController {
|
||||||
this.fontPathSelect.appendChild(newOption);
|
this.fontPathSelect.appendChild(newOption);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected loadPath(opentypeGlyph?: opentype.Glyph | null) {
|
|
||||||
window.jQuery(this.openModal).modal('hide');
|
|
||||||
|
|
||||||
let promise: Promise<PathfinderMeshData>;
|
|
||||||
|
|
||||||
if (this.file instanceof PathfinderFont && this.fileData != null) {
|
|
||||||
if (opentypeGlyph == null) {
|
|
||||||
const glyphIndex = parseInt(this.fontPathSelect.selectedOptions[0].value);
|
|
||||||
opentypeGlyph = this.file.opentypeFont.glyphs.get(glyphIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
const glyphStorage = new GlyphStore(this.file, [(opentypeGlyph as any).index]);
|
|
||||||
promise = glyphStorage.partition().then(result => result.meshes);
|
|
||||||
} else if (this.file instanceof SVGLoader) {
|
|
||||||
promise = this.file.partition(this.fontPathSelect.selectedIndex);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
promise.then(meshes => {
|
|
||||||
this.meshes = meshes;
|
|
||||||
this.view.attachMeshes();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly defaultFile: string = FONT;
|
|
||||||
|
|
||||||
private file: PathfinderFont | SVGLoader | null;
|
|
||||||
private fileType: FileType;
|
|
||||||
private fileData: ArrayBuffer | null;
|
|
||||||
|
|
||||||
meshes: PathfinderMeshData | null;
|
|
||||||
|
|
||||||
private openModal: HTMLElement;
|
|
||||||
private openFileSelect: HTMLSelectElement;
|
|
||||||
private fontPathSelectGroup: HTMLElement;
|
|
||||||
private fontPathSelect: HTMLSelectElement;
|
|
||||||
|
|
||||||
private filePicker: FilePickerView;
|
|
||||||
private view: MeshDebuggerView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MeshDebuggerView extends PathfinderView {
|
class MeshDebuggerView extends PathfinderView {
|
||||||
|
camera: OrthographicCamera;
|
||||||
|
|
||||||
|
private appController: MeshDebuggerAppController;
|
||||||
|
|
||||||
constructor(appController: MeshDebuggerAppController) {
|
constructor(appController: MeshDebuggerAppController) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -310,14 +314,10 @@ class MeshDebuggerView extends PathfinderView {
|
||||||
|
|
||||||
context.restore();
|
context.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
private appController: MeshDebuggerAppController;
|
|
||||||
|
|
||||||
camera: OrthographicCamera;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPosition(positions: Float32Array, vertexIndex: number): Float32Array | null {
|
function getPosition(positions: Float32Array, vertexIndex: number): Float32Array | null {
|
||||||
if (vertexIndex == UINT32_MAX)
|
if (vertexIndex === UINT32_MAX)
|
||||||
return null;
|
return null;
|
||||||
return new Float32Array([positions[vertexIndex * 2 + 0], -positions[vertexIndex * 2 + 1]]);
|
return new Float32Array([positions[vertexIndex * 2 + 0], -positions[vertexIndex * 2 + 1]]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,20 +10,20 @@
|
||||||
|
|
||||||
import * as base64js from 'base64-js';
|
import * as base64js from 'base64-js';
|
||||||
|
|
||||||
import { PathfinderError, expectNotNull, panic, UINT32_SIZE, UINT32_MAX } from './utils';
|
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
import { expectNotNull, panic, PathfinderError, UINT32_MAX, UINT32_SIZE } from './utils';
|
||||||
|
|
||||||
const BUFFER_TYPES: Meshes<BufferType> = {
|
const BUFFER_TYPES: Meshes<BufferType> = {
|
||||||
bQuads: 'ARRAY_BUFFER',
|
bQuads: 'ARRAY_BUFFER',
|
||||||
bVertexPositions: 'ARRAY_BUFFER',
|
|
||||||
bVertexPathIDs: 'ARRAY_BUFFER',
|
|
||||||
bVertexLoopBlinnData: 'ARRAY_BUFFER',
|
bVertexLoopBlinnData: 'ARRAY_BUFFER',
|
||||||
coverInteriorIndices: 'ELEMENT_ARRAY_BUFFER',
|
bVertexPathIDs: 'ARRAY_BUFFER',
|
||||||
|
bVertexPositions: 'ARRAY_BUFFER',
|
||||||
coverCurveIndices: 'ELEMENT_ARRAY_BUFFER',
|
coverCurveIndices: 'ELEMENT_ARRAY_BUFFER',
|
||||||
edgeUpperLineIndices: 'ARRAY_BUFFER',
|
coverInteriorIndices: 'ELEMENT_ARRAY_BUFFER',
|
||||||
|
edgeLowerCurveIndices: 'ARRAY_BUFFER',
|
||||||
edgeLowerLineIndices: 'ARRAY_BUFFER',
|
edgeLowerLineIndices: 'ARRAY_BUFFER',
|
||||||
edgeUpperCurveIndices: 'ARRAY_BUFFER',
|
edgeUpperCurveIndices: 'ARRAY_BUFFER',
|
||||||
edgeLowerCurveIndices: 'ARRAY_BUFFER',
|
edgeUpperLineIndices: 'ARRAY_BUFFER',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const B_QUAD_SIZE: number = 4 * 8;
|
export const B_QUAD_SIZE: number = 4 * 8;
|
||||||
|
@ -54,6 +54,23 @@ export interface Meshes<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PathfinderMeshData implements Meshes<ArrayBuffer> {
|
export class PathfinderMeshData implements Meshes<ArrayBuffer> {
|
||||||
|
readonly bQuads: ArrayBuffer;
|
||||||
|
readonly bVertexPositions: ArrayBuffer;
|
||||||
|
readonly bVertexPathIDs: ArrayBuffer;
|
||||||
|
readonly bVertexLoopBlinnData: ArrayBuffer;
|
||||||
|
readonly coverInteriorIndices: ArrayBuffer;
|
||||||
|
readonly coverCurveIndices: ArrayBuffer;
|
||||||
|
readonly edgeUpperLineIndices: ArrayBuffer;
|
||||||
|
readonly edgeLowerLineIndices: ArrayBuffer;
|
||||||
|
readonly edgeUpperCurveIndices: ArrayBuffer;
|
||||||
|
readonly edgeLowerCurveIndices: ArrayBuffer;
|
||||||
|
|
||||||
|
readonly bQuadCount: number;
|
||||||
|
readonly edgeUpperLineIndexCount: number;
|
||||||
|
readonly edgeLowerLineIndexCount: number;
|
||||||
|
readonly edgeUpperCurveIndexCount: number;
|
||||||
|
readonly edgeLowerCurveIndexCount: number;
|
||||||
|
|
||||||
constructor(meshes: Meshes<string | ArrayBuffer>) {
|
constructor(meshes: Meshes<string | ArrayBuffer>) {
|
||||||
for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof Meshes<void>>) {
|
for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof Meshes<void>>) {
|
||||||
const meshBuffer = meshes[bufferName];
|
const meshBuffer = meshes[bufferName];
|
||||||
|
@ -177,53 +194,26 @@ export class PathfinderMeshData implements Meshes<ArrayBuffer> {
|
||||||
|
|
||||||
return new PathfinderMeshData({
|
return new PathfinderMeshData({
|
||||||
bQuads: new Uint32Array(expandedBQuads).buffer as ArrayBuffer,
|
bQuads: new Uint32Array(expandedBQuads).buffer as ArrayBuffer,
|
||||||
bVertexPositions: new Float32Array(expandedBVertexPositions).buffer as ArrayBuffer,
|
|
||||||
bVertexPathIDs: new Uint16Array(expandedBVertexPathIDs).buffer as ArrayBuffer,
|
|
||||||
bVertexLoopBlinnData: new Uint32Array(expandedBVertexLoopBlinnData).buffer as
|
bVertexLoopBlinnData: new Uint32Array(expandedBVertexLoopBlinnData).buffer as
|
||||||
ArrayBuffer,
|
ArrayBuffer,
|
||||||
coverInteriorIndices: new Uint32Array(expandedCoverInteriorIndices).buffer as
|
bVertexPathIDs: new Uint16Array(expandedBVertexPathIDs).buffer as ArrayBuffer,
|
||||||
ArrayBuffer,
|
bVertexPositions: new Float32Array(expandedBVertexPositions).buffer as ArrayBuffer,
|
||||||
coverCurveIndices: new Uint32Array(expandedCoverCurveIndices).buffer as ArrayBuffer,
|
coverCurveIndices: new Uint32Array(expandedCoverCurveIndices).buffer as ArrayBuffer,
|
||||||
edgeUpperCurveIndices: new Uint32Array(expandedEdgeUpperCurveIndices).buffer as
|
coverInteriorIndices: new Uint32Array(expandedCoverInteriorIndices).buffer as
|
||||||
ArrayBuffer,
|
|
||||||
edgeUpperLineIndices: new Uint32Array(expandedEdgeUpperLineIndices).buffer as
|
|
||||||
ArrayBuffer,
|
ArrayBuffer,
|
||||||
edgeLowerCurveIndices: new Uint32Array(expandedEdgeLowerCurveIndices).buffer as
|
edgeLowerCurveIndices: new Uint32Array(expandedEdgeLowerCurveIndices).buffer as
|
||||||
ArrayBuffer,
|
ArrayBuffer,
|
||||||
edgeLowerLineIndices: new Uint32Array(expandedEdgeLowerLineIndices).buffer as
|
edgeLowerLineIndices: new Uint32Array(expandedEdgeLowerLineIndices).buffer as
|
||||||
ArrayBuffer,
|
ArrayBuffer,
|
||||||
})
|
edgeUpperCurveIndices: new Uint32Array(expandedEdgeUpperCurveIndices).buffer as
|
||||||
|
ArrayBuffer,
|
||||||
|
edgeUpperLineIndices: new Uint32Array(expandedEdgeUpperLineIndices).buffer as
|
||||||
|
ArrayBuffer,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly bQuads: ArrayBuffer;
|
|
||||||
readonly bVertexPositions: ArrayBuffer;
|
|
||||||
readonly bVertexPathIDs: ArrayBuffer;
|
|
||||||
readonly bVertexLoopBlinnData: ArrayBuffer;
|
|
||||||
readonly coverInteriorIndices: ArrayBuffer;
|
|
||||||
readonly coverCurveIndices: ArrayBuffer;
|
|
||||||
readonly edgeUpperLineIndices: ArrayBuffer;
|
|
||||||
readonly edgeLowerLineIndices: ArrayBuffer;
|
|
||||||
readonly edgeUpperCurveIndices: ArrayBuffer;
|
|
||||||
readonly edgeLowerCurveIndices: ArrayBuffer;
|
|
||||||
|
|
||||||
readonly bQuadCount: number;
|
|
||||||
readonly edgeUpperLineIndexCount: number;
|
|
||||||
readonly edgeLowerLineIndexCount: number;
|
|
||||||
readonly edgeUpperCurveIndexCount: number;
|
|
||||||
readonly edgeLowerCurveIndexCount: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PathfinderMeshBuffers implements Meshes<WebGLBuffer> {
|
export class PathfinderMeshBuffers implements Meshes<WebGLBuffer> {
|
||||||
constructor(gl: WebGLRenderingContext, meshData: PathfinderMeshData) {
|
|
||||||
for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof PathfinderMeshBuffers>) {
|
|
||||||
const bufferType = gl[BUFFER_TYPES[bufferName]];
|
|
||||||
const buffer = expectNotNull(gl.createBuffer(), "Failed to create buffer!");
|
|
||||||
gl.bindBuffer(bufferType, buffer);
|
|
||||||
gl.bufferData(bufferType, meshData[bufferName], gl.STATIC_DRAW);
|
|
||||||
this[bufferName] = buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly bQuads: WebGLBuffer;
|
readonly bQuads: WebGLBuffer;
|
||||||
readonly bVertexPositions: WebGLBuffer;
|
readonly bVertexPositions: WebGLBuffer;
|
||||||
readonly bVertexPathIDs: WebGLBuffer;
|
readonly bVertexPathIDs: WebGLBuffer;
|
||||||
|
@ -234,6 +224,16 @@ export class PathfinderMeshBuffers implements Meshes<WebGLBuffer> {
|
||||||
readonly edgeUpperCurveIndices: WebGLBuffer;
|
readonly edgeUpperCurveIndices: WebGLBuffer;
|
||||||
readonly edgeLowerLineIndices: WebGLBuffer;
|
readonly edgeLowerLineIndices: WebGLBuffer;
|
||||||
readonly edgeLowerCurveIndices: WebGLBuffer;
|
readonly edgeLowerCurveIndices: WebGLBuffer;
|
||||||
|
|
||||||
|
constructor(gl: WebGLRenderingContext, meshData: PathfinderMeshData) {
|
||||||
|
for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof PathfinderMeshBuffers>) {
|
||||||
|
const bufferType = gl[BUFFER_TYPES[bufferName]];
|
||||||
|
const buffer = expectNotNull(gl.createBuffer(), "Failed to create buffer!");
|
||||||
|
gl.bindBuffer(bufferType, buffer);
|
||||||
|
gl.bufferData(bufferType, meshData[bufferName], gl.STATIC_DRAW);
|
||||||
|
this[bufferName] = buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyIndices(destIndices: number[],
|
function copyIndices(destIndices: number[],
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
import {AttributeMap, UniformMap} from './gl-utils';
|
import {AttributeMap, UniformMap} from './gl-utils';
|
||||||
import {PathfinderError, expectNotNull, unwrapNull} from './utils';
|
import {expectNotNull, PathfinderError, unwrapNull} from './utils';
|
||||||
|
|
||||||
export interface UnlinkedShaderProgram {
|
export interface UnlinkedShaderProgram {
|
||||||
vertex: WebGLShader;
|
vertex: WebGLShader;
|
||||||
|
@ -37,60 +37,60 @@ export const SHADER_NAMES: Array<keyof ShaderMap<void>> = [
|
||||||
|
|
||||||
const SHADER_URLS: ShaderMap<ShaderProgramURLs> = {
|
const SHADER_URLS: ShaderMap<ShaderProgramURLs> = {
|
||||||
blit: {
|
blit: {
|
||||||
vertex: "/glsl/gles2/blit.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/blit.fs.glsl",
|
fragment: "/glsl/gles2/blit.fs.glsl",
|
||||||
},
|
vertex: "/glsl/gles2/blit.vs.glsl",
|
||||||
directCurve: {
|
|
||||||
vertex: "/glsl/gles2/direct-curve.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/direct-curve.fs.glsl",
|
|
||||||
},
|
|
||||||
directInterior: {
|
|
||||||
vertex: "/glsl/gles2/direct-interior.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/direct-interior.fs.glsl",
|
|
||||||
},
|
|
||||||
direct3DCurve: {
|
|
||||||
vertex: "/glsl/gles2/direct-3d-curve.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/direct-curve.fs.glsl",
|
|
||||||
},
|
|
||||||
direct3DInterior: {
|
|
||||||
vertex: "/glsl/gles2/direct-3d-interior.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/direct-interior.fs.glsl",
|
|
||||||
},
|
|
||||||
ssaaSubpixelResolve: {
|
|
||||||
vertex: "/glsl/gles2/ssaa-subpixel-resolve.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/ssaa-subpixel-resolve.fs.glsl",
|
|
||||||
},
|
|
||||||
ecaaEdgeDetect: {
|
|
||||||
vertex: "/glsl/gles2/ecaa-edge-detect.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/ecaa-edge-detect.fs.glsl",
|
|
||||||
},
|
|
||||||
ecaaCover: {
|
|
||||||
vertex: "/glsl/gles2/ecaa-cover.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/ecaa-cover.fs.glsl",
|
|
||||||
},
|
|
||||||
ecaaLine: {
|
|
||||||
vertex: "/glsl/gles2/ecaa-line.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/ecaa-line.fs.glsl",
|
|
||||||
},
|
|
||||||
ecaaCurve: {
|
|
||||||
vertex: "/glsl/gles2/ecaa-curve.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/ecaa-curve.fs.glsl",
|
|
||||||
},
|
|
||||||
ecaaMonoResolve: {
|
|
||||||
vertex: "/glsl/gles2/ecaa-mono-resolve.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/ecaa-mono-resolve.fs.glsl",
|
|
||||||
},
|
|
||||||
ecaaMonoSubpixelResolve: {
|
|
||||||
vertex: "/glsl/gles2/ecaa-mono-subpixel-resolve.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/ecaa-mono-subpixel-resolve.fs.glsl",
|
|
||||||
},
|
|
||||||
ecaaMultiResolve: {
|
|
||||||
vertex: "/glsl/gles2/ecaa-multi-resolve.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/ecaa-multi-resolve.fs.glsl",
|
|
||||||
},
|
},
|
||||||
demo3DMonument: {
|
demo3DMonument: {
|
||||||
vertex: "/glsl/gles2/demo-3d-monument.vs.glsl",
|
|
||||||
fragment: "/glsl/gles2/demo-3d-monument.fs.glsl",
|
fragment: "/glsl/gles2/demo-3d-monument.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/demo-3d-monument.vs.glsl",
|
||||||
|
},
|
||||||
|
direct3DCurve: {
|
||||||
|
fragment: "/glsl/gles2/direct-curve.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/direct-3d-curve.vs.glsl",
|
||||||
|
},
|
||||||
|
direct3DInterior: {
|
||||||
|
fragment: "/glsl/gles2/direct-interior.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/direct-3d-interior.vs.glsl",
|
||||||
|
},
|
||||||
|
directCurve: {
|
||||||
|
fragment: "/glsl/gles2/direct-curve.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/direct-curve.vs.glsl",
|
||||||
|
},
|
||||||
|
directInterior: {
|
||||||
|
fragment: "/glsl/gles2/direct-interior.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/direct-interior.vs.glsl",
|
||||||
|
},
|
||||||
|
ecaaCover: {
|
||||||
|
fragment: "/glsl/gles2/ecaa-cover.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/ecaa-cover.vs.glsl",
|
||||||
|
},
|
||||||
|
ecaaCurve: {
|
||||||
|
fragment: "/glsl/gles2/ecaa-curve.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/ecaa-curve.vs.glsl",
|
||||||
|
},
|
||||||
|
ecaaEdgeDetect: {
|
||||||
|
fragment: "/glsl/gles2/ecaa-edge-detect.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/ecaa-edge-detect.vs.glsl",
|
||||||
|
},
|
||||||
|
ecaaLine: {
|
||||||
|
fragment: "/glsl/gles2/ecaa-line.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/ecaa-line.vs.glsl",
|
||||||
|
},
|
||||||
|
ecaaMonoResolve: {
|
||||||
|
fragment: "/glsl/gles2/ecaa-mono-resolve.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/ecaa-mono-resolve.vs.glsl",
|
||||||
|
},
|
||||||
|
ecaaMonoSubpixelResolve: {
|
||||||
|
fragment: "/glsl/gles2/ecaa-mono-subpixel-resolve.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/ecaa-mono-subpixel-resolve.vs.glsl",
|
||||||
|
},
|
||||||
|
ecaaMultiResolve: {
|
||||||
|
fragment: "/glsl/gles2/ecaa-multi-resolve.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/ecaa-multi-resolve.vs.glsl",
|
||||||
|
},
|
||||||
|
ssaaSubpixelResolve: {
|
||||||
|
fragment: "/glsl/gles2/ssaa-subpixel-resolve.fs.glsl",
|
||||||
|
vertex: "/glsl/gles2/ssaa-subpixel-resolve.vs.glsl",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -122,31 +122,35 @@ interface ShaderProgramURLs {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ShaderLoader {
|
export class ShaderLoader {
|
||||||
|
common: Promise<string>;
|
||||||
|
shaders: Promise<ShaderMap<ShaderProgramSource>>;
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
this.common = window.fetch(COMMON_SHADER_URL).then(response => response.text());
|
this.common = window.fetch(COMMON_SHADER_URL).then(response => response.text());
|
||||||
|
|
||||||
const shaderKeys = Object.keys(SHADER_URLS) as Array<keyof ShaderMap<string>>;
|
const shaderKeys = Object.keys(SHADER_URLS) as Array<keyof ShaderMap<string>>;
|
||||||
let promises = [];
|
const promises = [];
|
||||||
for (const shaderKey of shaderKeys) {
|
for (const shaderKey of shaderKeys) {
|
||||||
promises.push(Promise.all([
|
promises.push(Promise.all([
|
||||||
window.fetch(SHADER_URLS[shaderKey].vertex).then(response => response.text()),
|
window.fetch(SHADER_URLS[shaderKey].vertex).then(response => response.text()),
|
||||||
window.fetch(SHADER_URLS[shaderKey].fragment).then(response => response.text()),
|
window.fetch(SHADER_URLS[shaderKey].fragment).then(response => response.text()),
|
||||||
]).then(results => { return { vertex: results[0], fragment: results[1] } }));
|
]).then(results => ({ vertex: results[0], fragment: results[1] })));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.shaders = Promise.all(promises).then(promises => {
|
this.shaders = Promise.all(promises).then(promises => {
|
||||||
let shaderMap: Partial<ShaderMap<ShaderProgramSource>> = {};
|
const shaderMap: Partial<ShaderMap<ShaderProgramSource>> = {};
|
||||||
for (let keyIndex = 0; keyIndex < shaderKeys.length; keyIndex++)
|
for (let keyIndex = 0; keyIndex < shaderKeys.length; keyIndex++)
|
||||||
shaderMap[shaderKeys[keyIndex]] = promises[keyIndex];
|
shaderMap[shaderKeys[keyIndex]] = promises[keyIndex];
|
||||||
return shaderMap as ShaderMap<ShaderProgramSource>;
|
return shaderMap as ShaderMap<ShaderProgramSource>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
common: Promise<string>;
|
|
||||||
shaders: Promise<ShaderMap<ShaderProgramSource>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PathfinderShaderProgram {
|
export class PathfinderShaderProgram {
|
||||||
|
readonly uniforms: UniformMap;
|
||||||
|
readonly attributes: AttributeMap;
|
||||||
|
readonly program: WebGLProgram;
|
||||||
|
|
||||||
constructor(gl: WebGLRenderingContext,
|
constructor(gl: WebGLRenderingContext,
|
||||||
programName: string,
|
programName: string,
|
||||||
unlinkedShaderProgram: UnlinkedShaderProgram) {
|
unlinkedShaderProgram: UnlinkedShaderProgram) {
|
||||||
|
@ -155,7 +159,7 @@ export class PathfinderShaderProgram {
|
||||||
gl.attachShader(this.program, compiledShader);
|
gl.attachShader(this.program, compiledShader);
|
||||||
gl.linkProgram(this.program);
|
gl.linkProgram(this.program);
|
||||||
|
|
||||||
if (gl.getProgramParameter(this.program, gl.LINK_STATUS) == 0) {
|
if (gl.getProgramParameter(this.program, gl.LINK_STATUS) === 0) {
|
||||||
const infoLog = gl.getProgramInfoLog(this.program);
|
const infoLog = gl.getProgramInfoLog(this.program);
|
||||||
throw new PathfinderError(`Failed to link program "${programName}":\n${infoLog}`);
|
throw new PathfinderError(`Failed to link program "${programName}":\n${infoLog}`);
|
||||||
}
|
}
|
||||||
|
@ -163,8 +167,8 @@ export class PathfinderShaderProgram {
|
||||||
const uniformCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
|
const uniformCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
|
||||||
const attributeCount = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES);
|
const attributeCount = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES);
|
||||||
|
|
||||||
let uniforms: UniformMap = {};
|
const uniforms: UniformMap = {};
|
||||||
let attributes: AttributeMap = {};
|
const attributes: AttributeMap = {};
|
||||||
|
|
||||||
for (let uniformIndex = 0; uniformIndex < uniformCount; uniformIndex++) {
|
for (let uniformIndex = 0; uniformIndex < uniformCount; uniformIndex++) {
|
||||||
const uniformName = unwrapNull(gl.getActiveUniform(this.program, uniformIndex)).name;
|
const uniformName = unwrapNull(gl.getActiveUniform(this.program, uniformIndex)).name;
|
||||||
|
@ -179,8 +183,4 @@ export class PathfinderShaderProgram {
|
||||||
this.uniforms = uniforms;
|
this.uniforms = uniforms;
|
||||||
this.attributes = attributes;
|
this.attributes = attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly uniforms: UniformMap;
|
|
||||||
readonly attributes: AttributeMap;
|
|
||||||
readonly program: WebGLProgram;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,20 @@
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
|
|
||||||
import {AntialiasingStrategy} from './aa-strategy';
|
import {AntialiasingStrategy} from './aa-strategy';
|
||||||
import {createFramebufferDepthTexture, createFramebuffer, setTextureParameters} from './gl-utils';
|
import {createFramebuffer, createFramebufferDepthTexture, setTextureParameters} from './gl-utils';
|
||||||
import {unwrapNull} from './utils';
|
import {unwrapNull} from './utils';
|
||||||
import {PathfinderDemoView} from './view';
|
import {PathfinderDemoView} from './view';
|
||||||
|
|
||||||
export default class SSAAStrategy extends AntialiasingStrategy {
|
export default class SSAAStrategy extends AntialiasingStrategy {
|
||||||
|
private level: number;
|
||||||
|
private subpixelAA: boolean;
|
||||||
|
|
||||||
|
private destFramebufferSize: glmatrix.vec2;
|
||||||
|
private supersampledFramebufferSize: glmatrix.vec2;
|
||||||
|
private supersampledColorTexture: WebGLTexture;
|
||||||
|
private supersampledDepthTexture: WebGLTexture;
|
||||||
|
private supersampledFramebuffer: WebGLFramebuffer;
|
||||||
|
|
||||||
constructor(level: number, subpixelAA: boolean) {
|
constructor(level: number, subpixelAA: boolean) {
|
||||||
super();
|
super();
|
||||||
this.level = level;
|
this.level = level;
|
||||||
|
@ -111,7 +120,7 @@ export default class SSAAStrategy extends AntialiasingStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private get supersampleScale(): glmatrix.vec2 {
|
private get supersampleScale(): glmatrix.vec2 {
|
||||||
return glmatrix.vec2.fromValues(this.subpixelAA ? 3 : 2, this.level == 2 ? 1 : 2);
|
return glmatrix.vec2.fromValues(this.subpixelAA ? 3 : 2, this.level === 2 ? 1 : 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private usedSupersampledFramebufferSize(view: PathfinderDemoView): glmatrix.vec2 {
|
private usedSupersampledFramebufferSize(view: PathfinderDemoView): glmatrix.vec2 {
|
||||||
|
@ -119,14 +128,4 @@ export default class SSAAStrategy extends AntialiasingStrategy {
|
||||||
glmatrix.vec2.mul(result, view.destUsedSize, this.supersampleScale);
|
glmatrix.vec2.mul(result, view.destUsedSize, this.supersampleScale);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private level: number;
|
|
||||||
private subpixelAA: boolean;
|
|
||||||
|
|
||||||
private destFramebufferSize: glmatrix.vec2;
|
|
||||||
private supersampledFramebufferSize: glmatrix.vec2;
|
|
||||||
private supersampledColorTexture: WebGLTexture;
|
|
||||||
private supersampledDepthTexture: WebGLTexture;
|
|
||||||
private supersampledFramebuffer: WebGLFramebuffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,17 +11,17 @@
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import {DemoAppController} from './app-controller';
|
|
||||||
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
||||||
|
import {DemoAppController} from './app-controller';
|
||||||
|
import PathfinderBufferTexture from "./buffer-texture";
|
||||||
import {OrthographicCamera} from "./camera";
|
import {OrthographicCamera} from "./camera";
|
||||||
import {ECAAStrategy, ECAAMulticolorStrategy} from "./ecaa-strategy";
|
import {ECAAMulticolorStrategy, ECAAStrategy} from "./ecaa-strategy";
|
||||||
import {PathfinderMeshData} from "./meshes";
|
import {PathfinderMeshData} from "./meshes";
|
||||||
import {ShaderMap, ShaderProgramSource} from './shader-loader';
|
import {ShaderMap, ShaderProgramSource} from './shader-loader';
|
||||||
import { SVGLoader, BUILTIN_SVG_URI } from './svg-loader';
|
import SSAAStrategy from "./ssaa-strategy";
|
||||||
|
import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
|
||||||
import {panic, unwrapNull} from './utils';
|
import {panic, unwrapNull} from './utils';
|
||||||
import {PathfinderDemoView, Timings} from './view';
|
import {PathfinderDemoView, Timings} from './view';
|
||||||
import SSAAStrategy from "./ssaa-strategy";
|
|
||||||
import PathfinderBufferTexture from "./buffer-texture";
|
|
||||||
|
|
||||||
const parseColor = require('parse-color');
|
const parseColor = require('parse-color');
|
||||||
|
|
||||||
|
@ -30,9 +30,9 @@ const SVG_NS: string = "http://www.w3.org/2000/svg";
|
||||||
const DEFAULT_FILE: string = 'tiger';
|
const DEFAULT_FILE: string = 'tiger';
|
||||||
|
|
||||||
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
||||||
|
ecaa: ECAAMulticolorStrategy,
|
||||||
none: NoAAStrategy,
|
none: NoAAStrategy,
|
||||||
ssaa: SSAAStrategy,
|
ssaa: SSAAStrategy,
|
||||||
ecaa: ECAAMulticolorStrategy,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface AntialiasingStrategyTable {
|
interface AntialiasingStrategyTable {
|
||||||
|
@ -42,6 +42,12 @@ interface AntialiasingStrategyTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SVGDemoController extends DemoAppController<SVGDemoView> {
|
class SVGDemoController extends DemoAppController<SVGDemoView> {
|
||||||
|
loader: SVGLoader;
|
||||||
|
|
||||||
|
protected readonly builtinFileURI: string = BUILTIN_SVG_URI;
|
||||||
|
|
||||||
|
private meshes: PathfinderMeshData;
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
@ -55,7 +61,7 @@ class SVGDemoController extends DemoAppController<SVGDemoView> {
|
||||||
this.loader.partition().then(meshes => {
|
this.loader.partition().then(meshes => {
|
||||||
this.meshes = meshes;
|
this.meshes = meshes;
|
||||||
this.meshesReceived();
|
this.meshesReceived();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createView() {
|
protected createView() {
|
||||||
|
@ -64,8 +70,6 @@ class SVGDemoController extends DemoAppController<SVGDemoView> {
|
||||||
unwrapNull(this.shaderSources));
|
unwrapNull(this.shaderSources));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly builtinFileURI: string = BUILTIN_SVG_URI;
|
|
||||||
|
|
||||||
protected get defaultFile(): string {
|
protected get defaultFile(): string {
|
||||||
return DEFAULT_FILE;
|
return DEFAULT_FILE;
|
||||||
}
|
}
|
||||||
|
@ -78,15 +82,19 @@ class SVGDemoController extends DemoAppController<SVGDemoView> {
|
||||||
|
|
||||||
view.camera.bounds = this.loader.bounds;
|
view.camera.bounds = this.loader.bounds;
|
||||||
view.camera.zoomToFit();
|
view.camera.zoomToFit();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loader: SVGLoader;
|
|
||||||
|
|
||||||
private meshes: PathfinderMeshData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SVGDemoView extends PathfinderDemoView {
|
class SVGDemoView extends PathfinderDemoView {
|
||||||
|
camera: OrthographicCamera;
|
||||||
|
|
||||||
|
protected depthFunction: number = this.gl.GREATER;
|
||||||
|
|
||||||
|
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.fromValues(1.0, 1.0);
|
||||||
|
|
||||||
|
private appController: SVGDemoController;
|
||||||
|
|
||||||
constructor(appController: SVGDemoController,
|
constructor(appController: SVGDemoController,
|
||||||
commonShaderSource: string,
|
commonShaderSource: string,
|
||||||
shaderSources: ShaderMap<ShaderProgramSource>) {
|
shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||||
|
@ -156,8 +164,6 @@ class SVGDemoView extends PathfinderDemoView {
|
||||||
this.appController.newTimingsReceived(_.pick(this.lastTimings, ['rendering']));
|
this.appController.newTimingsReceived(_.pick(this.lastTimings, ['rendering']));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.fromValues(1.0, 1.0);
|
|
||||||
|
|
||||||
protected get worldTransform() {
|
protected get worldTransform() {
|
||||||
const transform = glmatrix.mat4.create();
|
const transform = glmatrix.mat4.create();
|
||||||
const translation = this.camera.translation;
|
const translation = this.camera.translation;
|
||||||
|
@ -177,12 +183,6 @@ class SVGDemoView extends PathfinderDemoView {
|
||||||
protected get directInteriorProgramName(): keyof ShaderMap<void> {
|
protected get directInteriorProgramName(): keyof ShaderMap<void> {
|
||||||
return 'directInterior';
|
return 'directInterior';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected depthFunction: number = this.gl.GREATER;
|
|
||||||
|
|
||||||
private appController: SVGDemoController;
|
|
||||||
|
|
||||||
camera: OrthographicCamera;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
|
|
|
@ -12,8 +12,8 @@ import * as glmatrix from 'gl-matrix';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import 'path-data-polyfill.js';
|
import 'path-data-polyfill.js';
|
||||||
import {panic, unwrapNull} from "./utils";
|
|
||||||
import {PathfinderMeshData} from "./meshes";
|
import {PathfinderMeshData} from "./meshes";
|
||||||
|
import {panic, unwrapNull} from "./utils";
|
||||||
|
|
||||||
export const BUILTIN_SVG_URI: string = "/svg/demo";
|
export const BUILTIN_SVG_URI: string = "/svg/demo";
|
||||||
|
|
||||||
|
@ -39,6 +39,15 @@ export interface PathInstance {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SVGLoader {
|
export class SVGLoader {
|
||||||
|
pathInstances: PathInstance[];
|
||||||
|
scale: number;
|
||||||
|
bounds: glmatrix.vec4;
|
||||||
|
|
||||||
|
private svg: SVGSVGElement;
|
||||||
|
private fileData: ArrayBuffer;
|
||||||
|
|
||||||
|
private paths: any[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.scale = 1.0;
|
this.scale = 1.0;
|
||||||
this.svg = unwrapNull(document.getElementById('pf-svg')) as Element as SVGSVGElement;
|
this.svg = unwrapNull(document.getElementById('pf-svg')) as Element as SVGSVGElement;
|
||||||
|
@ -57,6 +66,22 @@ export class SVGLoader {
|
||||||
this.attachSVG(svgElement);
|
this.attachSVG(svgElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
partition(pathIndex?: number | undefined): Promise<PathfinderMeshData> {
|
||||||
|
// Make the request.
|
||||||
|
const paths = pathIndex == null ? this.paths : [this.paths[pathIndex]];
|
||||||
|
return window.fetch(PARTITION_SVG_PATHS_ENDPOINT_URL, {
|
||||||
|
body: JSON.stringify({ paths: paths }),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
method: 'POST',
|
||||||
|
}).then(response => response.text()).then(responseText => {
|
||||||
|
const response = JSON.parse(responseText);
|
||||||
|
if (!('Ok' in response))
|
||||||
|
panic("Failed to partition the font!");
|
||||||
|
const meshes = response.Ok.pathData;
|
||||||
|
return new PathfinderMeshData(meshes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private attachSVG(svgElement: SVGSVGElement) {
|
private attachSVG(svgElement: SVGSVGElement) {
|
||||||
// Clear out the current document.
|
// Clear out the current document.
|
||||||
let kid;
|
let kid;
|
||||||
|
@ -69,7 +94,7 @@ export class SVGLoader {
|
||||||
|
|
||||||
// Scan for geometry elements.
|
// Scan for geometry elements.
|
||||||
this.pathInstances.length = 0;
|
this.pathInstances.length = 0;
|
||||||
const queue: Array<Element> = [this.svg];
|
const queue: Element[] = [this.svg];
|
||||||
let element;
|
let element;
|
||||||
while ((element = queue.pop()) != null) {
|
while ((element = queue.pop()) != null) {
|
||||||
let kid = element.lastChild;
|
let kid = element.lastChild;
|
||||||
|
@ -86,7 +111,7 @@ export class SVGLoader {
|
||||||
if (style.stroke !== 'none') {
|
if (style.stroke !== 'none') {
|
||||||
this.pathInstances.push({
|
this.pathInstances.push({
|
||||||
element: element,
|
element: element,
|
||||||
stroke: parseInt(style.strokeWidth!),
|
stroke: parseInt(style.strokeWidth!, 10),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,28 +159,4 @@ export class SVGLoader {
|
||||||
|
|
||||||
this.bounds = glmatrix.vec4.clone([minX, minY, maxX, maxY]);
|
this.bounds = glmatrix.vec4.clone([minX, minY, maxX, maxY]);
|
||||||
}
|
}
|
||||||
|
|
||||||
partition(pathIndex?: number | undefined): Promise<PathfinderMeshData> {
|
|
||||||
// Make the request.
|
|
||||||
const paths = pathIndex == null ? this.paths : [this.paths[pathIndex]];
|
|
||||||
return window.fetch(PARTITION_SVG_PATHS_ENDPOINT_URL, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify({ paths: paths }),
|
|
||||||
}).then(response => response.text()).then(responseText => {
|
|
||||||
const response = JSON.parse(responseText);
|
|
||||||
if (!('Ok' in response))
|
|
||||||
panic("Failed to partition the font!");
|
|
||||||
const meshes = response.Ok.pathData;
|
|
||||||
return new PathfinderMeshData(meshes);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private svg: SVGSVGElement;
|
|
||||||
private fileData: ArrayBuffer;
|
|
||||||
scale: number;
|
|
||||||
|
|
||||||
pathInstances: PathInstance[];
|
|
||||||
private paths: any[];
|
|
||||||
bounds: glmatrix.vec4;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,15 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
import * as _ from 'lodash';
|
|
||||||
import * as base64js from 'base64-js';
|
import * as base64js from 'base64-js';
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
|
import * as _ from 'lodash';
|
||||||
import * as opentype from 'opentype.js';
|
import * as opentype from 'opentype.js';
|
||||||
|
|
||||||
|
import {Metrics} from 'opentype.js';
|
||||||
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy';
|
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy';
|
||||||
import {DemoAppController} from './app-controller';
|
import {DemoAppController} from './app-controller';
|
||||||
|
import PathfinderBufferTexture from './buffer-texture';
|
||||||
import {OrthographicCamera} from "./camera";
|
import {OrthographicCamera} from "./camera";
|
||||||
import {ECAAMonochromeStrategy, ECAAStrategy} from './ecaa-strategy';
|
import {ECAAMonochromeStrategy, ECAAStrategy} from './ecaa-strategy';
|
||||||
import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
|
import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
|
||||||
|
@ -22,14 +24,12 @@ import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from
|
||||||
import {UniformMap} from './gl-utils';
|
import {UniformMap} from './gl-utils';
|
||||||
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
||||||
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
|
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
|
||||||
import {BUILTIN_FONT_URI, Hint, SimpleTextLayout, GlyphStore, calculatePixelXMin} from "./text";
|
import SSAAStrategy from './ssaa-strategy';
|
||||||
import {calculatePixelDescent, calculatePixelRectForGlyph, PathfinderFont} from "./text";
|
import {calculatePixelDescent, calculatePixelRectForGlyph, PathfinderFont} from "./text";
|
||||||
import {PathfinderError, UINT32_SIZE, assert, expectNotNull, scaleRect, panic} from './utils';
|
import {BUILTIN_FONT_URI, calculatePixelXMin, GlyphStore, Hint, SimpleTextLayout} from "./text";
|
||||||
|
import {assert, expectNotNull, panic, PathfinderError, scaleRect, UINT32_SIZE} from './utils';
|
||||||
import {unwrapNull} from './utils';
|
import {unwrapNull} from './utils';
|
||||||
import { MonochromePathfinderView, Timings, TIMINGS } from './view';
|
import { MonochromePathfinderView, Timings, TIMINGS } from './view';
|
||||||
import PathfinderBufferTexture from './buffer-texture';
|
|
||||||
import SSAAStrategy from './ssaa-strategy';
|
|
||||||
import { Metrics } from 'opentype.js';
|
|
||||||
|
|
||||||
const DEFAULT_TEXT: string =
|
const DEFAULT_TEXT: string =
|
||||||
`’Twas brillig, and the slithy toves
|
`’Twas brillig, and the slithy toves
|
||||||
|
@ -123,6 +123,24 @@ function rectsIntersect(a: glmatrix.vec4, b: glmatrix.vec4): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextDemoController extends DemoAppController<TextDemoView> {
|
class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
|
font: PathfinderFont;
|
||||||
|
layout: SimpleTextLayout;
|
||||||
|
glyphStore: GlyphStore;
|
||||||
|
atlasGlyphs: AtlasGlyph[];
|
||||||
|
|
||||||
|
private hintingSelect: HTMLSelectElement;
|
||||||
|
|
||||||
|
private editTextModal: HTMLElement;
|
||||||
|
private editTextArea: HTMLTextAreaElement;
|
||||||
|
|
||||||
|
private _atlas: Atlas;
|
||||||
|
|
||||||
|
private meshes: PathfinderMeshData;
|
||||||
|
|
||||||
|
private _fontSize: number;
|
||||||
|
|
||||||
|
private text: string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.text = DEFAULT_TEXT;
|
this.text = DEFAULT_TEXT;
|
||||||
|
@ -154,14 +172,8 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
window.jQuery(this.editTextModal).modal();
|
window.jQuery(this.editTextModal).modal();
|
||||||
}
|
}
|
||||||
|
|
||||||
private hintingChanged(): void {
|
createHint(): Hint {
|
||||||
this.view.then(view => view.updateHinting());
|
return new Hint(this.font, this.pixelsPerUnit, this.useHinting);
|
||||||
}
|
|
||||||
|
|
||||||
private updateText(): void {
|
|
||||||
this.text = this.editTextArea.value;
|
|
||||||
|
|
||||||
window.jQuery(this.editTextModal).modal('hide');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createView() {
|
protected createView() {
|
||||||
|
@ -175,6 +187,16 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
this.recreateLayout(font);
|
this.recreateLayout(font);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private hintingChanged(): void {
|
||||||
|
this.view.then(view => view.updateHinting());
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateText(): void {
|
||||||
|
this.text = this.editTextArea.value;
|
||||||
|
|
||||||
|
window.jQuery(this.editTextModal).modal('hide');
|
||||||
|
}
|
||||||
|
|
||||||
private recreateLayout(font: PathfinderFont) {
|
private recreateLayout(font: PathfinderFont) {
|
||||||
const newLayout = new SimpleTextLayout(font, this.text);
|
const newLayout = new SimpleTextLayout(font, this.text);
|
||||||
|
|
||||||
|
@ -220,10 +242,6 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
return this.hintingSelect.selectedIndex !== 0;
|
return this.hintingSelect.selectedIndex !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
createHint(): Hint {
|
|
||||||
return new Hint(this.font, this.pixelsPerUnit, this.useHinting);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get builtinFileURI(): string {
|
protected get builtinFileURI(): string {
|
||||||
return BUILTIN_FONT_URI;
|
return BUILTIN_FONT_URI;
|
||||||
}
|
}
|
||||||
|
@ -232,27 +250,25 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
return DEFAULT_FONT;
|
return DEFAULT_FONT;
|
||||||
}
|
}
|
||||||
|
|
||||||
font: PathfinderFont;
|
|
||||||
|
|
||||||
private hintingSelect: HTMLSelectElement;
|
|
||||||
|
|
||||||
private editTextModal: HTMLElement;
|
|
||||||
private editTextArea: HTMLTextAreaElement;
|
|
||||||
|
|
||||||
private _atlas: Atlas;
|
|
||||||
atlasGlyphs: AtlasGlyph[];
|
|
||||||
|
|
||||||
private meshes: PathfinderMeshData;
|
|
||||||
|
|
||||||
private _fontSize: number;
|
|
||||||
|
|
||||||
private text: string;
|
|
||||||
|
|
||||||
layout: SimpleTextLayout;
|
|
||||||
glyphStore: GlyphStore;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextDemoView extends MonochromePathfinderView {
|
class TextDemoView extends MonochromePathfinderView {
|
||||||
|
atlasFramebuffer: WebGLFramebuffer;
|
||||||
|
atlasDepthTexture: WebGLTexture;
|
||||||
|
|
||||||
|
glyphPositionsBuffer: WebGLBuffer;
|
||||||
|
glyphTexCoordsBuffer: WebGLBuffer;
|
||||||
|
glyphElementsBuffer: WebGLBuffer;
|
||||||
|
|
||||||
|
appController: TextDemoController;
|
||||||
|
|
||||||
|
camera: OrthographicCamera;
|
||||||
|
|
||||||
|
readonly bgColor: glmatrix.vec4 = glmatrix.vec4.fromValues(1.0, 1.0, 1.0, 0.0);
|
||||||
|
readonly fgColor: glmatrix.vec4 = glmatrix.vec4.fromValues(0.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
protected depthFunction: number = this.gl.GREATER;
|
||||||
|
|
||||||
constructor(appController: TextDemoController,
|
constructor(appController: TextDemoController,
|
||||||
commonShaderSource: string,
|
commonShaderSource: string,
|
||||||
shaderSources: ShaderMap<ShaderProgramSource>) {
|
shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||||
|
@ -261,13 +277,40 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
this.appController = appController;
|
this.appController = appController;
|
||||||
|
|
||||||
this.camera = new OrthographicCamera(this.canvas, {
|
this.camera = new OrthographicCamera(this.canvas, {
|
||||||
minScale: MIN_SCALE,
|
|
||||||
maxScale: MAX_SCALE,
|
maxScale: MAX_SCALE,
|
||||||
|
minScale: MIN_SCALE,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.canvas.addEventListener('dblclick', () => this.appController.showTextEditor(), false);
|
this.canvas.addEventListener('dblclick', () => this.appController.showTextEditor(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attachText() {
|
||||||
|
this.panZoomEventsEnabled = false;
|
||||||
|
|
||||||
|
if (this.atlasFramebuffer == null)
|
||||||
|
this.createAtlasFramebuffer();
|
||||||
|
|
||||||
|
this.layoutText();
|
||||||
|
this.camera.zoomToFit();
|
||||||
|
this.appController.fontSize = this.camera.scale *
|
||||||
|
this.appController.font.opentypeFont.unitsPerEm;
|
||||||
|
this.buildAtlasGlyphs();
|
||||||
|
this.setDirty();
|
||||||
|
|
||||||
|
this.panZoomEventsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
relayoutText() {
|
||||||
|
this.layoutText();
|
||||||
|
this.buildAtlasGlyphs();
|
||||||
|
this.setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHinting(): void {
|
||||||
|
this.buildAtlasGlyphs();
|
||||||
|
this.setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
protected initContext() {
|
protected initContext() {
|
||||||
super.initContext();
|
super.initContext();
|
||||||
}
|
}
|
||||||
|
@ -287,12 +330,113 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
return pathColors;
|
return pathColors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
||||||
|
const glyphCount = this.appController.glyphStore.glyphIDs.length;
|
||||||
|
const atlasGlyphs = this.appController.atlasGlyphs;
|
||||||
|
const pixelsPerUnit = this.appController.pixelsPerUnit;
|
||||||
|
|
||||||
|
const transforms = new Float32Array((glyphCount + 1) * 4);
|
||||||
|
|
||||||
|
for (const glyph of atlasGlyphs) {
|
||||||
|
const pathID = glyph.glyphStoreIndex + 1;
|
||||||
|
const atlasOrigin = glyph.calculatePixelOrigin(pixelsPerUnit);
|
||||||
|
|
||||||
|
transforms[pathID * 4 + 0] = pixelsPerUnit;
|
||||||
|
transforms[pathID * 4 + 1] = pixelsPerUnit;
|
||||||
|
transforms[pathID * 4 + 2] = atlasOrigin[0];
|
||||||
|
transforms[pathID * 4 + 3] = atlasOrigin[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return transforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onPan() {
|
||||||
|
this.buildAtlasGlyphs();
|
||||||
|
this.setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onZoom() {
|
||||||
|
this.appController.fontSize = this.camera.scale *
|
||||||
|
this.appController.font.opentypeFont.unitsPerEm;
|
||||||
|
this.buildAtlasGlyphs();
|
||||||
|
this.setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected compositeIfNecessary() {
|
||||||
|
// Set up composite state.
|
||||||
|
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
|
||||||
|
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
||||||
|
this.gl.disable(this.gl.DEPTH_TEST);
|
||||||
|
this.gl.disable(this.gl.SCISSOR_TEST);
|
||||||
|
this.gl.blendEquation(this.gl.FUNC_ADD);
|
||||||
|
this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA,
|
||||||
|
this.gl.ONE, this.gl.ONE);
|
||||||
|
this.gl.enable(this.gl.BLEND);
|
||||||
|
|
||||||
|
// Clear.
|
||||||
|
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
||||||
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
// Set up the composite VAO.
|
||||||
|
const blitProgram = this.shaderPrograms.blit;
|
||||||
|
const attributes = blitProgram.attributes;
|
||||||
|
this.gl.useProgram(blitProgram.program);
|
||||||
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
|
||||||
|
this.gl.vertexAttribPointer(attributes.aPosition, 2, this.gl.FLOAT, false, 0, 0);
|
||||||
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
|
||||||
|
this.gl.vertexAttribPointer(attributes.aTexCoord, 2, this.gl.FLOAT, false, 0, 0);
|
||||||
|
this.gl.enableVertexAttribArray(attributes.aPosition);
|
||||||
|
this.gl.enableVertexAttribArray(attributes.aTexCoord);
|
||||||
|
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.glyphElementsBuffer);
|
||||||
|
|
||||||
|
// Create the transform.
|
||||||
|
const transform = glmatrix.mat4.create();
|
||||||
|
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
|
||||||
|
glmatrix.mat4.scale(transform,
|
||||||
|
transform,
|
||||||
|
[2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]);
|
||||||
|
glmatrix.mat4.translate(transform,
|
||||||
|
transform,
|
||||||
|
[this.camera.translation[0],
|
||||||
|
this.camera.translation[1],
|
||||||
|
0.0]);
|
||||||
|
|
||||||
|
// Blit.
|
||||||
|
this.gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform);
|
||||||
|
this.gl.activeTexture(this.gl.TEXTURE0);
|
||||||
|
this.gl.bindTexture(this.gl.TEXTURE_2D, this.appController.atlas.ensureTexture(this.gl));
|
||||||
|
this.gl.uniform1i(blitProgram.uniforms.uSource, 0);
|
||||||
|
this.setIdentityTexScaleUniform(blitProgram.uniforms);
|
||||||
|
this.gl.drawElements(this.gl.TRIANGLES,
|
||||||
|
this.appController.layout.textFrame.totalGlyphCount * 6,
|
||||||
|
this.gl.UNSIGNED_INT,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected clearForDirectRendering(): void {
|
||||||
|
this.gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
||||||
|
this.gl.clearDepth(0.0);
|
||||||
|
this.gl.depthMask(true);
|
||||||
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
||||||
|
aaLevel: number,
|
||||||
|
subpixelAA: boolean):
|
||||||
|
AntialiasingStrategy {
|
||||||
|
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected newTimingsReceived() {
|
||||||
|
this.appController.newTimingsReceived(this.lastTimings);
|
||||||
|
}
|
||||||
|
|
||||||
/// Lays out glyphs on the canvas.
|
/// Lays out glyphs on the canvas.
|
||||||
private layoutText() {
|
private layoutText() {
|
||||||
const layout = this.appController.layout;
|
const layout = this.appController.layout;
|
||||||
layout.layoutRuns();
|
layout.layoutRuns();
|
||||||
|
|
||||||
let textBounds = layout.textFrame.bounds;
|
const textBounds = layout.textFrame.bounds;
|
||||||
this.camera.bounds = textBounds;
|
this.camera.bounds = textBounds;
|
||||||
|
|
||||||
const totalGlyphCount = layout.textFrame.totalGlyphCount;
|
const totalGlyphCount = layout.textFrame.totalGlyphCount;
|
||||||
|
@ -389,28 +533,6 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
this.setGlyphTexCoords();
|
this.setGlyphTexCoords();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
|
||||||
const glyphCount = this.appController.glyphStore.glyphIDs.length;
|
|
||||||
const atlasGlyphs = this.appController.atlasGlyphs;
|
|
||||||
const pixelsPerUnit = this.appController.pixelsPerUnit;
|
|
||||||
|
|
||||||
const transforms = new Float32Array((glyphCount + 1) * 4);
|
|
||||||
|
|
||||||
for (let glyphIndex = 0; glyphIndex < atlasGlyphs.length; glyphIndex++) {
|
|
||||||
const glyph = atlasGlyphs[glyphIndex];
|
|
||||||
|
|
||||||
const pathID = glyph.glyphStoreIndex + 1;
|
|
||||||
const atlasOrigin = glyph.calculatePixelOrigin(pixelsPerUnit);
|
|
||||||
|
|
||||||
transforms[pathID * 4 + 0] = pixelsPerUnit;
|
|
||||||
transforms[pathID * 4 + 1] = pixelsPerUnit;
|
|
||||||
transforms[pathID * 4 + 2] = atlasOrigin[0];
|
|
||||||
transforms[pathID * 4 + 3] = atlasOrigin[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return transforms;
|
|
||||||
}
|
|
||||||
|
|
||||||
private createAtlasFramebuffer() {
|
private createAtlasFramebuffer() {
|
||||||
const atlasColorTexture = this.appController.atlas.ensureTexture(this.gl);
|
const atlasColorTexture = this.appController.atlas.ensureTexture(this.gl);
|
||||||
this.atlasDepthTexture = createFramebufferDepthTexture(this.gl, ATLAS_SIZE);
|
this.atlasDepthTexture = createFramebufferDepthTexture(this.gl, ATLAS_SIZE);
|
||||||
|
@ -443,7 +565,7 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
glyphIndex++, globalGlyphIndex++) {
|
glyphIndex++, globalGlyphIndex++) {
|
||||||
const textGlyphID = run.glyphIDs[glyphIndex];
|
const textGlyphID = run.glyphIDs[glyphIndex];
|
||||||
|
|
||||||
let atlasGlyphIndex = _.sortedIndexOf(atlasGlyphIDs, textGlyphID);
|
const atlasGlyphIndex = _.sortedIndexOf(atlasGlyphIDs, textGlyphID);
|
||||||
if (atlasGlyphIndex < 0)
|
if (atlasGlyphIndex < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -477,45 +599,6 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
this.gl.bufferData(this.gl.ARRAY_BUFFER, glyphTexCoords, this.gl.STATIC_DRAW);
|
this.gl.bufferData(this.gl.ARRAY_BUFFER, glyphTexCoords, this.gl.STATIC_DRAW);
|
||||||
}
|
}
|
||||||
|
|
||||||
attachText() {
|
|
||||||
this.panZoomEventsEnabled = false;
|
|
||||||
|
|
||||||
if (this.atlasFramebuffer == null)
|
|
||||||
this.createAtlasFramebuffer();
|
|
||||||
|
|
||||||
this.layoutText();
|
|
||||||
this.camera.zoomToFit();
|
|
||||||
this.appController.fontSize = this.camera.scale *
|
|
||||||
this.appController.font.opentypeFont.unitsPerEm;
|
|
||||||
this.buildAtlasGlyphs();
|
|
||||||
this.setDirty();
|
|
||||||
|
|
||||||
this.panZoomEventsEnabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
relayoutText() {
|
|
||||||
this.layoutText();
|
|
||||||
this.buildAtlasGlyphs();
|
|
||||||
this.setDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onPan() {
|
|
||||||
this.buildAtlasGlyphs();
|
|
||||||
this.setDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onZoom() {
|
|
||||||
this.appController.fontSize = this.camera.scale *
|
|
||||||
this.appController.font.opentypeFont.unitsPerEm;
|
|
||||||
this.buildAtlasGlyphs();
|
|
||||||
this.setDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateHinting(): void {
|
|
||||||
this.buildAtlasGlyphs();
|
|
||||||
this.setDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private setIdentityTexScaleUniform(uniforms: UniformMap) {
|
private setIdentityTexScaleUniform(uniforms: UniformMap) {
|
||||||
this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
|
this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
|
||||||
}
|
}
|
||||||
|
@ -526,64 +609,6 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
return usedSize;
|
return usedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected compositeIfNecessary() {
|
|
||||||
// Set up composite state.
|
|
||||||
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
|
|
||||||
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
||||||
this.gl.disable(this.gl.DEPTH_TEST);
|
|
||||||
this.gl.disable(this.gl.SCISSOR_TEST);
|
|
||||||
this.gl.blendEquation(this.gl.FUNC_ADD);
|
|
||||||
this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA,
|
|
||||||
this.gl.ONE, this.gl.ONE);
|
|
||||||
this.gl.enable(this.gl.BLEND);
|
|
||||||
|
|
||||||
// Clear.
|
|
||||||
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
|
||||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
||||||
|
|
||||||
// Set up the composite VAO.
|
|
||||||
const blitProgram = this.shaderPrograms.blit;
|
|
||||||
const attributes = blitProgram.attributes;
|
|
||||||
this.gl.useProgram(blitProgram.program);
|
|
||||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
|
|
||||||
this.gl.vertexAttribPointer(attributes.aPosition, 2, this.gl.FLOAT, false, 0, 0);
|
|
||||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
|
|
||||||
this.gl.vertexAttribPointer(attributes.aTexCoord, 2, this.gl.FLOAT, false, 0, 0);
|
|
||||||
this.gl.enableVertexAttribArray(attributes.aPosition);
|
|
||||||
this.gl.enableVertexAttribArray(attributes.aTexCoord);
|
|
||||||
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.glyphElementsBuffer);
|
|
||||||
|
|
||||||
// Create the transform.
|
|
||||||
const transform = glmatrix.mat4.create();
|
|
||||||
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
|
|
||||||
glmatrix.mat4.scale(transform,
|
|
||||||
transform,
|
|
||||||
[2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]);
|
|
||||||
glmatrix.mat4.translate(transform,
|
|
||||||
transform,
|
|
||||||
[this.camera.translation[0],
|
|
||||||
this.camera.translation[1],
|
|
||||||
0.0]);
|
|
||||||
|
|
||||||
// Blit.
|
|
||||||
this.gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform);
|
|
||||||
this.gl.activeTexture(this.gl.TEXTURE0);
|
|
||||||
this.gl.bindTexture(this.gl.TEXTURE_2D, this.appController.atlas.ensureTexture(this.gl));
|
|
||||||
this.gl.uniform1i(blitProgram.uniforms.uSource, 0);
|
|
||||||
this.setIdentityTexScaleUniform(blitProgram.uniforms);
|
|
||||||
this.gl.drawElements(this.gl.TRIANGLES,
|
|
||||||
this.appController.layout.textFrame.totalGlyphCount * 6,
|
|
||||||
this.gl.UNSIGNED_INT,
|
|
||||||
0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected clearForDirectRendering(): void {
|
|
||||||
this.gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
|
||||||
this.gl.clearDepth(0.0);
|
|
||||||
this.gl.depthMask(true);
|
|
||||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private set panZoomEventsEnabled(flag: boolean) {
|
private set panZoomEventsEnabled(flag: boolean) {
|
||||||
if (flag) {
|
if (flag) {
|
||||||
this.camera.onPan = () => this.onPan();
|
this.camera.onPan = () => this.onPan();
|
||||||
|
@ -594,9 +619,6 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly bgColor: glmatrix.vec4 = glmatrix.vec4.fromValues(1.0, 1.0, 1.0, 0.0);
|
|
||||||
readonly fgColor: glmatrix.vec4 = glmatrix.vec4.fromValues(0.0, 0.0, 0.0, 1.0);
|
|
||||||
|
|
||||||
get destFramebuffer(): WebGLFramebuffer {
|
get destFramebuffer(): WebGLFramebuffer {
|
||||||
return this.atlasFramebuffer;
|
return this.atlasFramebuffer;
|
||||||
}
|
}
|
||||||
|
@ -609,17 +631,6 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
return this.appController.atlas.usedSize;
|
return this.appController.atlas.usedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
|
||||||
aaLevel: number,
|
|
||||||
subpixelAA: boolean):
|
|
||||||
AntialiasingStrategy {
|
|
||||||
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected newTimingsReceived() {
|
|
||||||
this.appController.newTimingsReceived(this.lastTimings);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get worldTransform(): glmatrix.mat4 {
|
protected get worldTransform(): glmatrix.mat4 {
|
||||||
const transform = glmatrix.mat4.create();
|
const transform = glmatrix.mat4.create();
|
||||||
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
|
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
|
||||||
|
@ -634,19 +645,6 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
protected get directInteriorProgramName(): keyof ShaderMap<void> {
|
protected get directInteriorProgramName(): keyof ShaderMap<void> {
|
||||||
return 'directInterior';
|
return 'directInterior';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected depthFunction: number = this.gl.GREATER;
|
|
||||||
|
|
||||||
atlasFramebuffer: WebGLFramebuffer;
|
|
||||||
atlasDepthTexture: WebGLTexture;
|
|
||||||
|
|
||||||
glyphPositionsBuffer: WebGLBuffer;
|
|
||||||
glyphTexCoordsBuffer: WebGLBuffer;
|
|
||||||
glyphElementsBuffer: WebGLBuffer;
|
|
||||||
|
|
||||||
appController: TextDemoController;
|
|
||||||
|
|
||||||
camera: OrthographicCamera;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AntialiasingStrategyTable {
|
interface AntialiasingStrategyTable {
|
||||||
|
@ -656,6 +654,9 @@ interface AntialiasingStrategyTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Atlas {
|
class Atlas {
|
||||||
|
private _texture: WebGLTexture | null;
|
||||||
|
private _usedSize: Size2D;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._texture = null;
|
this._texture = null;
|
||||||
this._usedSize = glmatrix.vec2.create();
|
this._usedSize = glmatrix.vec2.create();
|
||||||
|
@ -725,12 +726,13 @@ class Atlas {
|
||||||
get usedSize(): glmatrix.vec2 {
|
get usedSize(): glmatrix.vec2 {
|
||||||
return this._usedSize;
|
return this._usedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _texture: WebGLTexture | null;
|
|
||||||
private _usedSize: Size2D;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AtlasGlyph {
|
class AtlasGlyph {
|
||||||
|
readonly glyphStoreIndex: number;
|
||||||
|
readonly glyphID: number;
|
||||||
|
readonly origin: glmatrix.vec2;
|
||||||
|
|
||||||
constructor(glyphStoreIndex: number, glyphID: number) {
|
constructor(glyphStoreIndex: number, glyphID: number) {
|
||||||
this.glyphStoreIndex = glyphStoreIndex;
|
this.glyphStoreIndex = glyphStoreIndex;
|
||||||
this.glyphID = glyphID;
|
this.glyphID = glyphID;
|
||||||
|
@ -756,16 +758,12 @@ class AtlasGlyph {
|
||||||
private setPixelOrigin(pixelOrigin: glmatrix.vec2, pixelsPerUnit: number): void {
|
private setPixelOrigin(pixelOrigin: glmatrix.vec2, pixelsPerUnit: number): void {
|
||||||
glmatrix.vec2.scale(this.origin, pixelOrigin, 1.0 / pixelsPerUnit);
|
glmatrix.vec2.scale(this.origin, pixelOrigin, 1.0 / pixelsPerUnit);
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly glyphStoreIndex: number;
|
|
||||||
readonly glyphID: number;
|
|
||||||
readonly origin: glmatrix.vec2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
||||||
|
ecaa: ECAAMonochromeStrategy,
|
||||||
none: NoAAStrategy,
|
none: NoAAStrategy,
|
||||||
ssaa: SSAAStrategy,
|
ssaa: SSAAStrategy,
|
||||||
ecaa: ECAAMonochromeStrategy,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
|
|
|
@ -8,14 +8,14 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
import {Metrics} from 'opentype.js';
|
|
||||||
import * as base64js from 'base64-js';
|
import * as base64js from 'base64-js';
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import * as opentype from "opentype.js";
|
import * as opentype from "opentype.js";
|
||||||
|
import {Metrics} from 'opentype.js';
|
||||||
|
|
||||||
import {B_QUAD_SIZE, PathfinderMeshData} from "./meshes";
|
import {B_QUAD_SIZE, PathfinderMeshData} from "./meshes";
|
||||||
import { UINT32_SIZE, UINT32_MAX, assert, panic, unwrapNull } from "./utils";
|
import {assert, panic, UINT32_MAX, UINT32_SIZE, unwrapNull} from "./utils";
|
||||||
|
|
||||||
export const BUILTIN_FONT_URI: string = "/otf/demo";
|
export const BUILTIN_FONT_URI: string = "/otf/demo";
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@ export interface ExpandedMeshData {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PartitionResult {
|
export interface PartitionResult {
|
||||||
meshes: PathfinderMeshData,
|
meshes: PathfinderMeshData;
|
||||||
time: number,
|
time: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PixelMetrics {
|
export interface PixelMetrics {
|
||||||
|
@ -39,7 +39,7 @@ export interface PixelMetrics {
|
||||||
|
|
||||||
opentype.Font.prototype.isSupported = function() {
|
opentype.Font.prototype.isSupported = function() {
|
||||||
return (this as any).supported;
|
return (this as any).supported;
|
||||||
}
|
};
|
||||||
|
|
||||||
opentype.Font.prototype.lineHeight = function() {
|
opentype.Font.prototype.lineHeight = function() {
|
||||||
const os2Table = this.tables.os2;
|
const os2Table = this.tables.os2;
|
||||||
|
@ -47,6 +47,11 @@ opentype.Font.prototype.lineHeight = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class PathfinderFont {
|
export class PathfinderFont {
|
||||||
|
readonly opentypeFont: opentype.Font;
|
||||||
|
readonly data: ArrayBuffer;
|
||||||
|
|
||||||
|
private metricsCache: Metrics[];
|
||||||
|
|
||||||
constructor(data: ArrayBuffer) {
|
constructor(data: ArrayBuffer) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
|
||||||
|
@ -62,14 +67,15 @@ export class PathfinderFont {
|
||||||
this.metricsCache[glyphID] = this.opentypeFont.glyphs.get(glyphID).getMetrics();
|
this.metricsCache[glyphID] = this.opentypeFont.glyphs.get(glyphID).getMetrics();
|
||||||
return this.metricsCache[glyphID];
|
return this.metricsCache[glyphID];
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly opentypeFont: opentype.Font;
|
|
||||||
readonly data: ArrayBuffer;
|
|
||||||
|
|
||||||
private metricsCache: Metrics[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TextRun {
|
export class TextRun {
|
||||||
|
readonly glyphIDs: number[];
|
||||||
|
advances: number[];
|
||||||
|
readonly origin: number[];
|
||||||
|
|
||||||
|
private readonly font: PathfinderFont;
|
||||||
|
|
||||||
constructor(text: number[] | string, origin: number[], font: PathfinderFont) {
|
constructor(text: number[] | string, origin: number[], font: PathfinderFont) {
|
||||||
if (typeof(text) === 'string') {
|
if (typeof(text) === 'string') {
|
||||||
this.glyphIDs = font.opentypeFont
|
this.glyphIDs = font.opentypeFont
|
||||||
|
@ -93,12 +99,6 @@ export class TextRun {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private pixelMetricsForGlyphAt(index: number, pixelsPerUnit: number, hint: Hint):
|
|
||||||
PixelMetrics {
|
|
||||||
const metrics = unwrapNull(this.font.metricsForGlyph(index));
|
|
||||||
return calculatePixelMetricsForGlyph(metrics, pixelsPerUnit, hint);
|
|
||||||
}
|
|
||||||
|
|
||||||
calculatePixelOriginForGlyphAt(index: number, pixelsPerUnit: number, hint: Hint):
|
calculatePixelOriginForGlyphAt(index: number, pixelsPerUnit: number, hint: Hint):
|
||||||
glmatrix.vec2 {
|
glmatrix.vec2 {
|
||||||
const textGlyphOrigin = glmatrix.vec2.clone(this.origin);
|
const textGlyphOrigin = glmatrix.vec2.clone(this.origin);
|
||||||
|
@ -121,13 +121,19 @@ export class TextRun {
|
||||||
return lastAdvance + this.font.opentypeFont.glyphs.get(lastGlyphID).advanceWidth;
|
return lastAdvance + this.font.opentypeFont.glyphs.get(lastGlyphID).advanceWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly glyphIDs: number[];
|
private pixelMetricsForGlyphAt(index: number, pixelsPerUnit: number, hint: Hint):
|
||||||
advances: number[];
|
PixelMetrics {
|
||||||
readonly origin: number[];
|
const metrics = unwrapNull(this.font.metricsForGlyph(index));
|
||||||
private readonly font: PathfinderFont;
|
return calculatePixelMetricsForGlyph(metrics, pixelsPerUnit, hint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TextFrame {
|
export class TextFrame {
|
||||||
|
readonly runs: TextRun[];
|
||||||
|
readonly origin: glmatrix.vec3;
|
||||||
|
|
||||||
|
private readonly font: PathfinderFont;
|
||||||
|
|
||||||
constructor(runs: TextRun[], font: PathfinderFont) {
|
constructor(runs: TextRun[], font: PathfinderFont) {
|
||||||
this.runs = runs;
|
this.runs = runs;
|
||||||
this.origin = glmatrix.vec3.create();
|
this.origin = glmatrix.vec3.create();
|
||||||
|
@ -137,8 +143,7 @@ export class TextFrame {
|
||||||
expandMeshes(meshes: PathfinderMeshData, glyphIDs: number[]): ExpandedMeshData {
|
expandMeshes(meshes: PathfinderMeshData, glyphIDs: number[]): ExpandedMeshData {
|
||||||
const pathIDs = [];
|
const pathIDs = [];
|
||||||
for (const textRun of this.runs) {
|
for (const textRun of this.runs) {
|
||||||
for (let glyphIndex = 0; glyphIndex < textRun.glyphIDs.length; glyphIndex++) {
|
for (const glyphID of textRun.glyphIDs) {
|
||||||
const glyphID = textRun.glyphIDs[glyphIndex];
|
|
||||||
if (glyphID === 0)
|
if (glyphID === 0)
|
||||||
continue;
|
continue;
|
||||||
const pathID = _.sortedIndexOf(glyphIDs, glyphID);
|
const pathID = _.sortedIndexOf(glyphIDs, glyphID);
|
||||||
|
@ -180,15 +185,13 @@ export class TextFrame {
|
||||||
glyphIDs.push(...run.glyphIDs);
|
glyphIDs.push(...run.glyphIDs);
|
||||||
return glyphIDs;
|
return glyphIDs;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly runs: TextRun[];
|
|
||||||
readonly origin: glmatrix.vec3;
|
|
||||||
|
|
||||||
private readonly font: PathfinderFont;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores one copy of each glyph.
|
/// Stores one copy of each glyph.
|
||||||
export class GlyphStore {
|
export class GlyphStore {
|
||||||
|
readonly font: PathfinderFont;
|
||||||
|
readonly glyphIDs: number[];
|
||||||
|
|
||||||
constructor(font: PathfinderFont, glyphIDs: number[]) {
|
constructor(font: PathfinderFont, glyphIDs: number[]) {
|
||||||
this.font = font;
|
this.font = font;
|
||||||
this.glyphIDs = glyphIDs;
|
this.glyphIDs = glyphIDs;
|
||||||
|
@ -209,9 +212,9 @@ export class GlyphStore {
|
||||||
|
|
||||||
// Make the request.
|
// Make the request.
|
||||||
return window.fetch(PARTITION_FONT_ENDPOINT_URI, {
|
return window.fetch(PARTITION_FONT_ENDPOINT_URI, {
|
||||||
method: 'POST',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify(request),
|
body: JSON.stringify(request),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
method: 'POST',
|
||||||
}).then(response => response.text()).then(responseText => {
|
}).then(response => response.text()).then(responseText => {
|
||||||
const response = JSON.parse(responseText);
|
const response = JSON.parse(responseText);
|
||||||
if (!('Ok' in response))
|
if (!('Ok' in response))
|
||||||
|
@ -227,12 +230,11 @@ export class GlyphStore {
|
||||||
const index = _.sortedIndexOf(this.glyphIDs, glyphID);
|
const index = _.sortedIndexOf(this.glyphIDs, glyphID);
|
||||||
return index >= 0 ? index : null;
|
return index >= 0 ? index : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly font: PathfinderFont;
|
|
||||||
readonly glyphIDs: number[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SimpleTextLayout {
|
export class SimpleTextLayout {
|
||||||
|
readonly textFrame: TextFrame;
|
||||||
|
|
||||||
constructor(font: PathfinderFont, text: string) {
|
constructor(font: PathfinderFont, text: string) {
|
||||||
const lineHeight = font.opentypeFont.lineHeight();
|
const lineHeight = font.opentypeFont.lineHeight();
|
||||||
const textRuns: TextRun[] = text.split("\n").map((line, lineNumber) => {
|
const textRuns: TextRun[] = text.split("\n").map((line, lineNumber) => {
|
||||||
|
@ -244,11 +246,14 @@ export class SimpleTextLayout {
|
||||||
layoutRuns() {
|
layoutRuns() {
|
||||||
this.textFrame.runs.forEach(textRun => textRun.layout());
|
this.textFrame.runs.forEach(textRun => textRun.layout());
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly textFrame: TextFrame;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Hint {
|
export class Hint {
|
||||||
|
readonly xHeight: number;
|
||||||
|
readonly hintedXHeight: number;
|
||||||
|
|
||||||
|
private useHinting: boolean;
|
||||||
|
|
||||||
constructor(font: PathfinderFont, pixelsPerUnit: number, useHinting: boolean) {
|
constructor(font: PathfinderFont, pixelsPerUnit: number, useHinting: boolean) {
|
||||||
this.useHinting = useHinting;
|
this.useHinting = useHinting;
|
||||||
|
|
||||||
|
@ -278,10 +283,6 @@ export class Hint {
|
||||||
return glmatrix.vec2.fromValues(position[0],
|
return glmatrix.vec2.fromValues(position[0],
|
||||||
position[1] / this.xHeight * this.hintedXHeight);
|
position[1] / this.xHeight * this.hintedXHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly xHeight: number;
|
|
||||||
readonly hintedXHeight: number;
|
|
||||||
private useHinting: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculatePixelXMin(metrics: Metrics, pixelsPerUnit: number): number {
|
export function calculatePixelXMin(metrics: Metrics, pixelsPerUnit: number): number {
|
||||||
|
@ -296,10 +297,10 @@ function calculatePixelMetricsForGlyph(metrics: Metrics, pixelsPerUnit: number,
|
||||||
PixelMetrics {
|
PixelMetrics {
|
||||||
const top = hint.hintPosition(glmatrix.vec2.fromValues(0, metrics.yMax))[1];
|
const top = hint.hintPosition(glmatrix.vec2.fromValues(0, metrics.yMax))[1];
|
||||||
return {
|
return {
|
||||||
left: calculatePixelXMin(metrics, pixelsPerUnit),
|
|
||||||
right: Math.ceil(metrics.xMax * pixelsPerUnit),
|
|
||||||
ascent: Math.ceil(top * pixelsPerUnit),
|
ascent: Math.ceil(top * pixelsPerUnit),
|
||||||
descent: calculatePixelDescent(metrics, pixelsPerUnit),
|
descent: calculatePixelDescent(metrics, pixelsPerUnit),
|
||||||
|
left: calculatePixelXMin(metrics, pixelsPerUnit),
|
||||||
|
right: Math.ceil(metrics.xMax * pixelsPerUnit),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,13 +11,13 @@
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
|
|
||||||
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
||||||
|
import PathfinderBufferTexture from './buffer-texture';
|
||||||
import {Camera} from "./camera";
|
import {Camera} from "./camera";
|
||||||
import {QUAD_ELEMENTS, UniformMap} from './gl-utils';
|
import {QUAD_ELEMENTS, UniformMap} from './gl-utils';
|
||||||
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
||||||
import {PathfinderShaderProgram, SHADER_NAMES, ShaderMap} from './shader-loader';
|
import {PathfinderShaderProgram, SHADER_NAMES, ShaderMap} from './shader-loader';
|
||||||
import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader';
|
import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader';
|
||||||
import {PathfinderError, UINT32_SIZE, expectNotNull, unwrapNull} from './utils';
|
import {expectNotNull, PathfinderError, UINT32_SIZE, unwrapNull} from './utils';
|
||||||
import PathfinderBufferTexture from './buffer-texture';
|
|
||||||
|
|
||||||
const TIME_INTERVAL_DELAY: number = 32;
|
const TIME_INTERVAL_DELAY: number = 32;
|
||||||
|
|
||||||
|
@ -40,18 +40,24 @@ const QUAD_TEX_COORDS: Float32Array = new Float32Array([
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const TIMINGS: {[name: string]: string} = {
|
export const TIMINGS: {[name: string]: string} = {
|
||||||
rendering: "Rendering",
|
|
||||||
compositing: "Compositing",
|
compositing: "Compositing",
|
||||||
}
|
rendering: "Rendering",
|
||||||
|
};
|
||||||
|
|
||||||
export interface Timings {
|
export interface Timings {
|
||||||
rendering: number;
|
|
||||||
compositing: number;
|
compositing: number;
|
||||||
|
rendering: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare class WebGLQuery {}
|
declare class WebGLQuery {}
|
||||||
|
|
||||||
export abstract class PathfinderView {
|
export abstract class PathfinderView {
|
||||||
|
protected canvas: HTMLCanvasElement;
|
||||||
|
|
||||||
|
protected camera: Camera;
|
||||||
|
|
||||||
|
private dirty: boolean;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
|
|
||||||
|
@ -61,6 +67,29 @@ export abstract class PathfinderView {
|
||||||
this.resizeToFit(true);
|
this.resizeToFit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zoomIn(): void {
|
||||||
|
this.camera.zoomIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomOut(): void {
|
||||||
|
this.camera.zoomOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected resized(): void {
|
||||||
|
this.setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setDirty() {
|
||||||
|
if (this.dirty)
|
||||||
|
return;
|
||||||
|
this.dirty = true;
|
||||||
|
window.requestAnimationFrame(() => this.redraw());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected redraw() {
|
||||||
|
this.dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
private resizeToFit(initialSize: boolean) {
|
private resizeToFit(initialSize: boolean) {
|
||||||
const width = window.innerWidth;
|
const width = window.innerWidth;
|
||||||
|
|
||||||
|
@ -84,38 +113,42 @@ export abstract class PathfinderView {
|
||||||
|
|
||||||
this.resized();
|
this.resized();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected resized(): void {
|
|
||||||
this.setDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected setDirty() {
|
|
||||||
if (this.dirty)
|
|
||||||
return;
|
|
||||||
this.dirty = true;
|
|
||||||
window.requestAnimationFrame(() => this.redraw());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected redraw() {
|
|
||||||
this.dirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
zoomIn(): void {
|
|
||||||
this.camera.zoomIn();
|
|
||||||
}
|
|
||||||
|
|
||||||
zoomOut(): void {
|
|
||||||
this.camera.zoomOut();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected canvas: HTMLCanvasElement;
|
|
||||||
|
|
||||||
protected camera: Camera;
|
|
||||||
|
|
||||||
private dirty: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class PathfinderDemoView extends PathfinderView {
|
export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
|
gl: WebGLRenderingContext;
|
||||||
|
|
||||||
|
shaderPrograms: ShaderMap<PathfinderShaderProgram>;
|
||||||
|
|
||||||
|
drawBuffersExt: any;
|
||||||
|
instancedArraysExt: any;
|
||||||
|
textureHalfFloatExt: any;
|
||||||
|
vertexArrayObjectExt: any;
|
||||||
|
|
||||||
|
quadPositionsBuffer: WebGLBuffer;
|
||||||
|
quadTexCoordsBuffer: WebGLBuffer;
|
||||||
|
quadElementsBuffer: WebGLBuffer;
|
||||||
|
|
||||||
|
meshes: PathfinderMeshBuffers[];
|
||||||
|
meshData: PathfinderMeshData[];
|
||||||
|
|
||||||
|
pathTransformBufferTextures: PathfinderBufferTexture[];
|
||||||
|
pathHintsBufferTexture: PathfinderBufferTexture | null;
|
||||||
|
|
||||||
|
protected timerQueryExt: any;
|
||||||
|
|
||||||
|
protected antialiasingStrategy: AntialiasingStrategy | null;
|
||||||
|
protected colorBufferHalfFloatExt: any;
|
||||||
|
protected pathColorsBufferTextures: PathfinderBufferTexture[];
|
||||||
|
|
||||||
|
protected lastTimings: Timings;
|
||||||
|
|
||||||
|
private atlasRenderingTimerQuery: WebGLQuery;
|
||||||
|
private compositingTimerQuery: WebGLQuery;
|
||||||
|
private timerQueryPollInterval: number | null;
|
||||||
|
|
||||||
|
private wantsScreenshot: boolean;
|
||||||
|
|
||||||
constructor(commonShaderSource: string, shaderSources: ShaderMap<ShaderProgramSource>) {
|
constructor(commonShaderSource: string, shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -140,7 +173,7 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
subpixelAA: boolean) {
|
subpixelAA: boolean) {
|
||||||
this.antialiasingStrategy = this.createAAStrategy(aaType, aaLevel, subpixelAA);
|
this.antialiasingStrategy = this.createAAStrategy(aaType, aaLevel, subpixelAA);
|
||||||
|
|
||||||
let canvas = this.canvas;
|
const canvas = this.canvas;
|
||||||
this.antialiasingStrategy.init(this);
|
this.antialiasingStrategy.init(this);
|
||||||
if (this.meshData != null)
|
if (this.meshData != null)
|
||||||
this.antialiasingStrategy.attachMeshes(this);
|
this.antialiasingStrategy.attachMeshes(this);
|
||||||
|
@ -156,6 +189,82 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
this.setDirty();
|
this.setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initQuadVAO(attributes: any) {
|
||||||
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadPositionsBuffer);
|
||||||
|
this.gl.vertexAttribPointer(attributes.aPosition, 2, this.gl.FLOAT, false, 0, 0);
|
||||||
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadTexCoordsBuffer);
|
||||||
|
this.gl.vertexAttribPointer(attributes.aTexCoord, 2, this.gl.FLOAT, false, 0, 0);
|
||||||
|
this.gl.enableVertexAttribArray(attributes.aPosition);
|
||||||
|
this.gl.enableVertexAttribArray(attributes.aTexCoord);
|
||||||
|
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.quadElementsBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFramebufferSizeUniform(uniforms: UniformMap) {
|
||||||
|
const currentViewport = this.gl.getParameter(this.gl.VIEWPORT);
|
||||||
|
this.gl.uniform2i(uniforms.uFramebufferSize, currentViewport[2], currentViewport[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTransformSTUniform(uniforms: UniformMap, objectIndex: number) {
|
||||||
|
// FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an ST matrix is ugly and fragile.
|
||||||
|
// Refactor.
|
||||||
|
const transform = glmatrix.mat4.clone(this.worldTransform);
|
||||||
|
glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex));
|
||||||
|
|
||||||
|
const translation = glmatrix.vec4.clone([transform[12], transform[13], 0.0, 1.0]);
|
||||||
|
|
||||||
|
this.gl.uniform4f(uniforms.uTransformST,
|
||||||
|
transform[0],
|
||||||
|
transform[5],
|
||||||
|
transform[12],
|
||||||
|
transform[13]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap) {
|
||||||
|
const usedSize = this.usedSizeFactor;
|
||||||
|
this.gl.uniform4f(uniforms.uTransformST, 2.0 * usedSize[0], 2.0 * usedSize[1], -1.0, -1.0);
|
||||||
|
this.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTransformAndTexScaleUniformsForDest(uniforms: UniformMap) {
|
||||||
|
const usedSize = this.usedSizeFactor;
|
||||||
|
|
||||||
|
const transform = glmatrix.mat4.create();
|
||||||
|
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
|
||||||
|
glmatrix.mat4.scale(transform, transform, [2.0 * usedSize[0], 2.0 * usedSize[1], 1.0]);
|
||||||
|
this.gl.uniformMatrix4fv(uniforms.uTransform, false, transform);
|
||||||
|
|
||||||
|
this.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
queueScreenshot() {
|
||||||
|
this.wantsScreenshot = true;
|
||||||
|
this.setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadPathColors(objectCount: number) {
|
||||||
|
this.pathColorsBufferTextures = [];
|
||||||
|
|
||||||
|
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
|
||||||
|
const pathColorsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathColors');
|
||||||
|
const pathColors = this.pathColorsForObject(objectIndex);
|
||||||
|
pathColorsBufferTexture.upload(this.gl, pathColors);
|
||||||
|
this.pathColorsBufferTextures.push(pathColorsBufferTexture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadPathTransforms(objectCount: number) {
|
||||||
|
this.pathTransformBufferTextures = [];
|
||||||
|
|
||||||
|
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
|
||||||
|
const pathTransformBufferTexture = new PathfinderBufferTexture(this.gl,
|
||||||
|
'uPathTransform');
|
||||||
|
|
||||||
|
const pathTransforms = this.pathTransformsForObject(objectIndex);
|
||||||
|
pathTransformBufferTexture.upload(this.gl, pathTransforms);
|
||||||
|
this.pathTransformBufferTextures.push(pathTransformBufferTexture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected resized(): void {
|
protected resized(): void {
|
||||||
super.resized();
|
super.resized();
|
||||||
|
|
||||||
|
@ -194,65 +303,6 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
this.compositingTimerQuery = this.timerQueryExt.createQueryEXT();
|
this.compositingTimerQuery = this.timerQueryExt.createQueryEXT();
|
||||||
}
|
}
|
||||||
|
|
||||||
private compileShaders(commonSource: string, shaderSources: ShaderMap<ShaderProgramSource>):
|
|
||||||
ShaderMap<UnlinkedShaderProgram> {
|
|
||||||
let shaders: Partial<ShaderMap<Partial<UnlinkedShaderProgram>>> = {};
|
|
||||||
|
|
||||||
for (const shaderKey of SHADER_NAMES) {
|
|
||||||
for (const typeName of ['vertex', 'fragment'] as Array<'vertex' | 'fragment'>) {
|
|
||||||
const type = {
|
|
||||||
vertex: this.gl.VERTEX_SHADER,
|
|
||||||
fragment: this.gl.FRAGMENT_SHADER,
|
|
||||||
}[typeName];
|
|
||||||
|
|
||||||
const source = shaderSources[shaderKey][typeName];
|
|
||||||
const shader = this.gl.createShader(type);
|
|
||||||
if (shader == null)
|
|
||||||
throw new PathfinderError("Failed to create shader!");
|
|
||||||
|
|
||||||
this.gl.shaderSource(shader, commonSource + "\n#line 1\n" + source);
|
|
||||||
this.gl.compileShader(shader);
|
|
||||||
if (this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS) == 0) {
|
|
||||||
const infoLog = this.gl.getShaderInfoLog(shader);
|
|
||||||
throw new PathfinderError(`Failed to compile ${typeName} shader ` +
|
|
||||||
`"${shaderKey}":\n${infoLog}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shaders[shaderKey] == null)
|
|
||||||
shaders[shaderKey] = {};
|
|
||||||
shaders[shaderKey]![typeName] = shader;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return shaders as ShaderMap<UnlinkedShaderProgram>;
|
|
||||||
}
|
|
||||||
|
|
||||||
private linkShaders(shaders: ShaderMap<UnlinkedShaderProgram>):
|
|
||||||
ShaderMap<PathfinderShaderProgram> {
|
|
||||||
let shaderProgramMap: Partial<ShaderMap<PathfinderShaderProgram>> = {};
|
|
||||||
for (const shaderName of Object.keys(shaders) as Array<keyof ShaderMap<string>>) {
|
|
||||||
shaderProgramMap[shaderName] = new PathfinderShaderProgram(this.gl,
|
|
||||||
shaderName,
|
|
||||||
shaders[shaderName]);
|
|
||||||
}
|
|
||||||
return shaderProgramMap as ShaderMap<PathfinderShaderProgram>;
|
|
||||||
}
|
|
||||||
|
|
||||||
initQuadVAO(attributes: any) {
|
|
||||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadPositionsBuffer);
|
|
||||||
this.gl.vertexAttribPointer(attributes.aPosition, 2, this.gl.FLOAT, false, 0, 0);
|
|
||||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadTexCoordsBuffer);
|
|
||||||
this.gl.vertexAttribPointer(attributes.aTexCoord, 2, this.gl.FLOAT, false, 0, 0);
|
|
||||||
this.gl.enableVertexAttribArray(attributes.aPosition);
|
|
||||||
this.gl.enableVertexAttribArray(attributes.aTexCoord);
|
|
||||||
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.quadElementsBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
setFramebufferSizeUniform(uniforms: UniformMap) {
|
|
||||||
const currentViewport = this.gl.getParameter(this.gl.VIEWPORT);
|
|
||||||
this.gl.uniform2i(uniforms.uFramebufferSize, currentViewport[2], currentViewport[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected redraw() {
|
protected redraw() {
|
||||||
super.redraw();
|
super.redraw();
|
||||||
|
|
||||||
|
@ -309,19 +359,79 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
|
|
||||||
protected renderingFinished(): void {}
|
protected renderingFinished(): void {}
|
||||||
|
|
||||||
setTransformSTUniform(uniforms: UniformMap, objectIndex: number) {
|
protected getModelviewTransform(pathIndex: number): glmatrix.mat4 {
|
||||||
// FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an ST matrix is ugly and fragile.
|
return glmatrix.mat4.create();
|
||||||
// Refactor.
|
}
|
||||||
const transform = glmatrix.mat4.clone(this.worldTransform);
|
|
||||||
glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex));
|
|
||||||
|
|
||||||
const translation = glmatrix.vec4.clone([transform[12], transform[13], 0.0, 1.0]);
|
protected drawSceneryIfNecessary(): void {}
|
||||||
|
|
||||||
this.gl.uniform4f(uniforms.uTransformST,
|
protected clearForDirectRendering(): void {
|
||||||
transform[0],
|
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
||||||
transform[5],
|
this.gl.clearDepth(0.0);
|
||||||
transform[12],
|
this.gl.depthMask(true);
|
||||||
transform[13]);
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected shouldRenderObject(objectIndex: number): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected newTimingsReceived() {}
|
||||||
|
|
||||||
|
protected abstract pathColorsForObject(objectIndex: number): Uint8Array;
|
||||||
|
protected abstract pathTransformsForObject(objectIndex: number): Float32Array;
|
||||||
|
|
||||||
|
protected abstract get depthFunction(): number;
|
||||||
|
|
||||||
|
protected abstract createAAStrategy(aaType: AntialiasingStrategyName,
|
||||||
|
aaLevel: number,
|
||||||
|
subpixelAA: boolean):
|
||||||
|
AntialiasingStrategy;
|
||||||
|
|
||||||
|
protected abstract compositeIfNecessary(): void;
|
||||||
|
|
||||||
|
private compileShaders(commonSource: string, shaderSources: ShaderMap<ShaderProgramSource>):
|
||||||
|
ShaderMap<UnlinkedShaderProgram> {
|
||||||
|
const shaders: Partial<ShaderMap<Partial<UnlinkedShaderProgram>>> = {};
|
||||||
|
|
||||||
|
for (const shaderKey of SHADER_NAMES) {
|
||||||
|
for (const typeName of ['vertex', 'fragment'] as Array<'vertex' | 'fragment'>) {
|
||||||
|
const type = {
|
||||||
|
fragment: this.gl.FRAGMENT_SHADER,
|
||||||
|
vertex: this.gl.VERTEX_SHADER,
|
||||||
|
}[typeName];
|
||||||
|
|
||||||
|
const source = shaderSources[shaderKey][typeName];
|
||||||
|
const shader = this.gl.createShader(type);
|
||||||
|
if (shader == null)
|
||||||
|
throw new PathfinderError("Failed to create shader!");
|
||||||
|
|
||||||
|
this.gl.shaderSource(shader, commonSource + "\n#line 1\n" + source);
|
||||||
|
this.gl.compileShader(shader);
|
||||||
|
if (this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS) === 0) {
|
||||||
|
const infoLog = this.gl.getShaderInfoLog(shader);
|
||||||
|
throw new PathfinderError(`Failed to compile ${typeName} shader ` +
|
||||||
|
`"${shaderKey}":\n${infoLog}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shaders[shaderKey] == null)
|
||||||
|
shaders[shaderKey] = {};
|
||||||
|
shaders[shaderKey]![typeName] = shader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return shaders as ShaderMap<UnlinkedShaderProgram>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private linkShaders(shaders: ShaderMap<UnlinkedShaderProgram>):
|
||||||
|
ShaderMap<PathfinderShaderProgram> {
|
||||||
|
const shaderProgramMap: Partial<ShaderMap<PathfinderShaderProgram>> = {};
|
||||||
|
for (const shaderName of Object.keys(shaders) as Array<keyof ShaderMap<string>>) {
|
||||||
|
shaderProgramMap[shaderName] = new PathfinderShaderProgram(this.gl,
|
||||||
|
shaderName,
|
||||||
|
shaders[shaderName]);
|
||||||
|
}
|
||||||
|
return shaderProgramMap as ShaderMap<PathfinderShaderProgram>;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setTransformUniform(uniforms: UniformMap, objectIndex: number) {
|
private setTransformUniform(uniforms: UniformMap, objectIndex: number) {
|
||||||
|
@ -450,7 +560,7 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
Array<'atlasRenderingTimerQuery' | 'compositingTimerQuery'>) {
|
Array<'atlasRenderingTimerQuery' | 'compositingTimerQuery'>) {
|
||||||
if (this.timerQueryExt.getQueryObjectEXT(this[queryName],
|
if (this.timerQueryExt.getQueryObjectEXT(this[queryName],
|
||||||
this.timerQueryExt
|
this.timerQueryExt
|
||||||
.QUERY_RESULT_AVAILABLE_EXT) == 0) {
|
.QUERY_RESULT_AVAILABLE_EXT) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -462,8 +572,8 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
this.timerQueryExt.getQueryObjectEXT(this.compositingTimerQuery,
|
this.timerQueryExt.getQueryObjectEXT(this.compositingTimerQuery,
|
||||||
this.timerQueryExt.QUERY_RESULT_EXT);
|
this.timerQueryExt.QUERY_RESULT_EXT);
|
||||||
this.lastTimings = {
|
this.lastTimings = {
|
||||||
rendering: atlasRenderingTime / 1000000.0,
|
|
||||||
compositing: compositingTime / 1000000.0,
|
compositing: compositingTime / 1000000.0,
|
||||||
|
rendering: atlasRenderingTime / 1000000.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.newTimingsReceived();
|
this.newTimingsReceived();
|
||||||
|
@ -473,28 +583,6 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
}, TIME_INTERVAL_DELAY);
|
}, TIME_INTERVAL_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap) {
|
|
||||||
const usedSize = this.usedSizeFactor;
|
|
||||||
this.gl.uniform4f(uniforms.uTransformST, 2.0 * usedSize[0], 2.0 * usedSize[1], -1.0, -1.0);
|
|
||||||
this.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTransformAndTexScaleUniformsForDest(uniforms: UniformMap) {
|
|
||||||
const usedSize = this.usedSizeFactor;
|
|
||||||
|
|
||||||
const transform = glmatrix.mat4.create();
|
|
||||||
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
|
|
||||||
glmatrix.mat4.scale(transform, transform, [2.0 * usedSize[0], 2.0 * usedSize[1], 1.0]);
|
|
||||||
this.gl.uniformMatrix4fv(uniforms.uTransform, false, transform);
|
|
||||||
|
|
||||||
this.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
queueScreenshot() {
|
|
||||||
this.wantsScreenshot = true;
|
|
||||||
this.setDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private takeScreenshot() {
|
private takeScreenshot() {
|
||||||
const width = this.canvas.width, height = this.canvas.height;
|
const width = this.canvas.width, height = this.canvas.height;
|
||||||
const scratchCanvas = document.createElement('canvas');
|
const scratchCanvas = document.createElement('canvas');
|
||||||
|
@ -512,61 +600,6 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
document.body.removeChild(scratchLink);
|
document.body.removeChild(scratchLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getModelviewTransform(pathIndex: number): glmatrix.mat4 {
|
|
||||||
return glmatrix.mat4.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected drawSceneryIfNecessary(): void {}
|
|
||||||
|
|
||||||
protected clearForDirectRendering(): void {
|
|
||||||
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
|
||||||
this.gl.clearDepth(0.0);
|
|
||||||
this.gl.depthMask(true);
|
|
||||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected shouldRenderObject(objectIndex: number): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadPathColors(objectCount: number) {
|
|
||||||
this.pathColorsBufferTextures = [];
|
|
||||||
|
|
||||||
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
|
|
||||||
const pathColorsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathColors');
|
|
||||||
const pathColors = this.pathColorsForObject(objectIndex);
|
|
||||||
pathColorsBufferTexture.upload(this.gl, pathColors);
|
|
||||||
this.pathColorsBufferTextures.push(pathColorsBufferTexture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadPathTransforms(objectCount: number) {
|
|
||||||
this.pathTransformBufferTextures = [];
|
|
||||||
|
|
||||||
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
|
|
||||||
const pathTransformBufferTexture = new PathfinderBufferTexture(this.gl,
|
|
||||||
'uPathTransform');
|
|
||||||
|
|
||||||
const pathTransforms = this.pathTransformsForObject(objectIndex);
|
|
||||||
pathTransformBufferTexture.upload(this.gl, pathTransforms);
|
|
||||||
this.pathTransformBufferTextures.push(pathTransformBufferTexture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected newTimingsReceived() {}
|
|
||||||
|
|
||||||
protected abstract pathColorsForObject(objectIndex: number): Uint8Array;
|
|
||||||
protected abstract pathTransformsForObject(objectIndex: number): Float32Array;
|
|
||||||
|
|
||||||
protected abstract get depthFunction(): number;
|
|
||||||
|
|
||||||
protected abstract createAAStrategy(aaType: AntialiasingStrategyName,
|
|
||||||
aaLevel: number,
|
|
||||||
subpixelAA: boolean):
|
|
||||||
AntialiasingStrategy;
|
|
||||||
|
|
||||||
protected abstract compositeIfNecessary(): void;
|
|
||||||
|
|
||||||
abstract get destFramebuffer(): WebGLFramebuffer | null;
|
abstract get destFramebuffer(): WebGLFramebuffer | null;
|
||||||
|
|
||||||
abstract get destAllocatedSize(): glmatrix.vec2;
|
abstract get destAllocatedSize(): glmatrix.vec2;
|
||||||
|
@ -578,38 +611,6 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
|
|
||||||
protected abstract get directCurveProgramName(): keyof ShaderMap<void>;
|
protected abstract get directCurveProgramName(): keyof ShaderMap<void>;
|
||||||
protected abstract get directInteriorProgramName(): keyof ShaderMap<void>;
|
protected abstract get directInteriorProgramName(): keyof ShaderMap<void>;
|
||||||
|
|
||||||
protected antialiasingStrategy: AntialiasingStrategy | null;
|
|
||||||
|
|
||||||
gl: WebGLRenderingContext;
|
|
||||||
|
|
||||||
shaderPrograms: ShaderMap<PathfinderShaderProgram>;
|
|
||||||
|
|
||||||
protected colorBufferHalfFloatExt: any;
|
|
||||||
drawBuffersExt: any;
|
|
||||||
instancedArraysExt: any;
|
|
||||||
textureHalfFloatExt: any;
|
|
||||||
protected timerQueryExt: any;
|
|
||||||
vertexArrayObjectExt: any;
|
|
||||||
|
|
||||||
quadPositionsBuffer: WebGLBuffer;
|
|
||||||
quadTexCoordsBuffer: WebGLBuffer;
|
|
||||||
quadElementsBuffer: WebGLBuffer;
|
|
||||||
|
|
||||||
meshes: PathfinderMeshBuffers[];
|
|
||||||
meshData: PathfinderMeshData[];
|
|
||||||
|
|
||||||
pathTransformBufferTextures: PathfinderBufferTexture[];
|
|
||||||
pathHintsBufferTexture: PathfinderBufferTexture | null;
|
|
||||||
protected pathColorsBufferTextures: PathfinderBufferTexture[];
|
|
||||||
|
|
||||||
private atlasRenderingTimerQuery: WebGLQuery;
|
|
||||||
private compositingTimerQuery: WebGLQuery;
|
|
||||||
private timerQueryPollInterval: number | null;
|
|
||||||
|
|
||||||
protected lastTimings: Timings;
|
|
||||||
|
|
||||||
private wantsScreenshot: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class MonochromePathfinderView extends PathfinderDemoView {
|
export abstract class MonochromePathfinderView extends PathfinderDemoView {
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"defaultSeverity": "error",
|
||||||
|
"extends": [
|
||||||
|
"tslint:recommended"
|
||||||
|
],
|
||||||
|
"jsRules": {
|
||||||
|
"quotemark": false
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"quotemark": false,
|
||||||
|
"interface-name": false,
|
||||||
|
"curly": false,
|
||||||
|
"member-access": false,
|
||||||
|
"max-classes-per-file": false,
|
||||||
|
"arrow-parens": false,
|
||||||
|
"one-variable-per-declaration": false,
|
||||||
|
"object-literal-shorthand": false,
|
||||||
|
"no-empty": false,
|
||||||
|
"variable-name": false,
|
||||||
|
"no-bitwise": false,
|
||||||
|
"new-parens": false,
|
||||||
|
"no-conditional-assignment": false,
|
||||||
|
"no-shadowed-variable": false,
|
||||||
|
"no-var-requires": false
|
||||||
|
},
|
||||||
|
"rulesDirectory": []
|
||||||
|
}
|
|
@ -13,6 +13,15 @@ module.exports = {
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
|
{
|
||||||
|
test: /src\/[a-zA-Z0-9_-]+\.tsx?$/,
|
||||||
|
enforce: 'pre',
|
||||||
|
loader: 'tslint-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
options: {
|
||||||
|
configFile: "tslint.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /src\/[a-zA-Z0-9_-]+\.tsx?$/,
|
test: /src\/[a-zA-Z0-9_-]+\.tsx?$/,
|
||||||
use: 'ts-loader',
|
use: 'ts-loader',
|
||||||
|
|
Loading…
Reference in New Issue