diff --git a/demo/client/package.json b/demo/client/package.json index 3bb0018a..9ed81cbc 100644 --- a/demo/client/package.json +++ b/demo/client/package.json @@ -31,5 +31,9 @@ "ts-loader": "^2.3.7", "typescript": "^2.5.2", "webpack": "^3.5.6" + }, + "devDependencies": { + "tslint": "^5.7.0", + "tslint-loader": "^3.5.3" } } diff --git a/demo/client/src/3d-demo.ts b/demo/client/src/3d-demo.ts index ada02054..07b0366b 100644 --- a/demo/client/src/3d-demo.ts +++ b/demo/client/src/3d-demo.ts @@ -9,21 +9,21 @@ // except according to those terms. import * as glmatrix from 'gl-matrix'; +import * as _ from "lodash"; import * as opentype from "opentype.js"; +import {mat4, vec2} from "gl-matrix"; import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; import {DemoAppController} from "./app-controller"; +import PathfinderBufferTexture from "./buffer-texture"; import {PerspectiveCamera} from "./camera"; -import {mat4, vec2} from "gl-matrix"; import {PathfinderMeshData} from "./meshes"; 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 * as _ from "lodash"; -import PathfinderBufferTexture from "./buffer-texture"; +import {BUILTIN_FONT_URI, ExpandedMeshData} from "./text"; +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_PADDING: number = 2000; @@ -94,6 +94,14 @@ interface MonumentSide { } class ThreeDController extends DemoAppController { + textFrames: TextFrame[]; + glyphStore: GlyphStore; + + private baseMeshes: PathfinderMeshData; + private expandedMeshes: ExpandedMeshData[]; + + private monumentPromise: Promise; + start() { super.start(); @@ -104,15 +112,34 @@ class ThreeDController extends DemoAppController { 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[] { const sides = []; for (let sideIndex = 0; sideIndex < 4; sideIndex++) sides[sideIndex] = { upper: { lines: [] }, lower: { lines: [] } }; for (const nameData of textData.monument) { - const side = parseInt(nameData.side) - 1; - const row = parseInt(nameData.row) - 1; - const number = parseInt(nameData.number) - 1; + const side = parseInt(nameData.side, 10) - 1; + const row = parseInt(nameData.row, 10) - 1; + const index = parseInt(nameData.number, 10) - 1; if (sides[side] == null) continue; @@ -121,29 +148,24 @@ class ThreeDController extends DemoAppController { if (lines[row] == null) 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) })); } - 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[]) { this.textFrames = []; let glyphsNeeded: number[] = []; for (const monumentSide of monument) { - let textRuns = []; + const textRuns = []; for (let lineNumber = 0; lineNumber < monumentSide.lines.length; lineNumber++) { const line = monumentSide.lines[lineNumber]; const lineY = -lineNumber * font.opentypeFont.lineHeight(); - const lineGlyphs = line.names.map(string => { - const glyphs = font.opentypeFont.stringToGlyphs(string); + const lineGlyphs = line.names.map(name => { + const glyphs = font.opentypeFont.stringToGlyphs(name); const glyphIDs = glyphs.map(glyph => (glyph as any).index); const width = _.sumBy(glyphs, glyph => glyph.advanceWidth); return { glyphs: glyphIDs, width: width }; @@ -156,7 +178,7 @@ class ThreeDController extends DemoAppController { let currentX = 0.0; for (const glyphInfo of lineGlyphs) { const textRunOrigin = [currentX, lineY]; - const textRun = new TextRun(glyphInfo.glyphs, textRunOrigin, font); + const textRun = new TextRun(glyphInfo.glyphs, textRunOrigin, font); textRun.layout(); textRuns.push(textRun); currentX += glyphInfo.width + spacing; @@ -184,31 +206,27 @@ class ThreeDController extends DemoAppController { }); }); } - - 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; } 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 = 'direct3DCurve'; + protected directInteriorProgramName: keyof ShaderMap = 'direct3DInterior'; + + protected depthFunction: number = this.gl.LESS; + + private _scale: number; + + private appController: ThreeDController; + + private cubeVertexPositionBuffer: WebGLBuffer; + private cubeIndexBuffer: WebGLBuffer; + constructor(appController: ThreeDController, commonShaderSource: string, shaderSources: ShaderMap) { @@ -233,7 +251,7 @@ class ThreeDView extends PathfinderDemoView { protected pathColorsForObject(textFrameIndex: number): Uint8Array { const textFrame = this.appController.textFrames[textFrameIndex]; const pathCount = textFrame.totalGlyphCount; - + const pathColors = new Uint8Array(4 * (pathCount + 1)); for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) pathColors.set(TEXT_COLOR, (pathIndex + 1) * 4); @@ -244,7 +262,7 @@ class ThreeDView extends PathfinderDemoView { protected pathTransformsForObject(textFrameIndex: number): Float32Array { const textFrame = this.appController.textFrames[textFrameIndex]; const pathCount = textFrame.totalGlyphCount; - + const hint = new Hint(this.appController.glyphStore.font, PIXELS_PER_UNIT, false); const pathTransforms = new Float32Array(4 * (pathCount + 1)); @@ -316,18 +334,40 @@ class ThreeDView extends PathfinderDemoView { 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 { return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height); } - destFramebuffer: WebGLFramebuffer | null = null; - get destUsedSize(): glmatrix.vec2 { return this.destAllocatedSize; } - protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.clone([1.0, 1.0]); - private calculateWorldTransform(modelviewTranslation: glmatrix.vec3, modelviewScale: glmatrix.vec3): glmatrix.mat4 { @@ -349,49 +389,9 @@ class ThreeDView extends PathfinderDemoView { 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() { 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 = 'direct3DCurve'; - protected directInteriorProgramName: keyof ShaderMap = 'direct3DInterior'; - - protected depthFunction: number = this.gl.LESS; - - private _scale: number; - - private appController: ThreeDController; - - private cubeVertexPositionBuffer: WebGLBuffer; - private cubeIndexBuffer: WebGLBuffer; - - camera: PerspectiveCamera; } function main() { diff --git a/demo/client/src/aa-strategy.ts b/demo/client/src/aa-strategy.ts index 1b74a15d..bc2e9667 100644 --- a/demo/client/src/aa-strategy.ts +++ b/demo/client/src/aa-strategy.ts @@ -15,6 +15,9 @@ import {PathfinderDemoView} from './view'; export type AntialiasingStrategyName = 'none' | 'ssaa' | 'ecaa'; 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. init(view: PathfinderDemoView): void { this.setFramebufferSize(view); @@ -43,12 +46,11 @@ export abstract class AntialiasingStrategy { // // This usually blits to the real framebuffer. abstract resolve(view: PathfinderDemoView): void; - - // True if direct rendering should occur. - shouldRenderDirect: boolean; } export class NoAAStrategy extends AntialiasingStrategy { + framebufferSize: glmatrix.vec2; + constructor(level: number, subpixelAA: boolean) { super(); this.framebufferSize = glmatrix.vec2.create(); @@ -77,6 +79,4 @@ export class NoAAStrategy extends AntialiasingStrategy { get shouldRenderDirect() { return true; } - - framebufferSize: glmatrix.vec2; } diff --git a/demo/client/src/app-controller.ts b/demo/client/src/app-controller.ts index e838d097..d7c18fe4 100644 --- a/demo/client/src/app-controller.ts +++ b/demo/client/src/app-controller.ts @@ -9,12 +9,16 @@ // except according to those terms. import {AntialiasingStrategyName} from "./aa-strategy"; +import {FilePickerView} from "./file-picker"; 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 { FilePickerView } from "./file-picker"; export abstract class AppController { + protected canvas: HTMLCanvasElement; + + protected screenshotButton: HTMLButtonElement | null; + start() { const canvas = document.getElementById('pf-canvas') as HTMLCanvasElement; } @@ -36,16 +40,25 @@ export abstract class AppController { .then(data => this.fileLoaded(data)); } - protected canvas: HTMLCanvasElement; - - protected screenshotButton: HTMLButtonElement | null; - protected abstract fileLoaded(data: ArrayBuffer): void; protected abstract get defaultFile(): string; } export abstract class DemoAppController extends AppController { + view: Promise; + + protected abstract readonly builtinFileURI: string; + + protected filePickerView: FilePickerView | null; + + protected commonShaderSource: string | null; + protected shaderSources: ShaderMap | null; + + private aaLevelSelect: HTMLSelectElement | null; + private subpixelAASwitch: HTMLInputElement | null; + private fpsLabel: HTMLElement | null; + constructor() { super(); } @@ -164,13 +177,15 @@ export abstract class DemoAppController extends this.fpsLabel.classList.remove('invisible'); } + protected abstract createView(): View; + private updateAALevel() { let aaType: AntialiasingStrategyName, aaLevel: number; if (this.aaLevelSelect != null) { const selectedOption = this.aaLevelSelect.selectedOptions[0]; const aaValues = unwrapNull(/^([a-z-]+)(?:-([0-9]+))?$/.exec(selectedOption.value)); aaType = aaValues[1] as AntialiasingStrategyName; - aaLevel = aaValues[2] === "" ? 1 : parseInt(aaValues[2]); + aaLevel = aaValues[2] === "" ? 1 : parseInt(aaValues[2], 10); } else { aaType = 'none'; aaLevel = 0; @@ -204,19 +219,4 @@ export abstract class DemoAppController extends // Fetch the file. this.fetchFile(selectedOption.value, this.builtinFileURI); } - - protected abstract createView(): View; - - protected abstract readonly builtinFileURI: string; - - view: Promise; - - protected filePickerView: FilePickerView | null; - - protected commonShaderSource: string | null; - protected shaderSources: ShaderMap | null; - - private aaLevelSelect: HTMLSelectElement | null; - private subpixelAASwitch: HTMLInputElement | null; - private fpsLabel: HTMLElement | null; } diff --git a/demo/client/src/benchmark.ts b/demo/client/src/benchmark.ts index e94ab442..6eb16aa4 100644 --- a/demo/client/src/benchmark.ts +++ b/demo/client/src/benchmark.ts @@ -11,17 +11,18 @@ import * as glmatrix from 'gl-matrix'; import * as opentype from "opentype.js"; -import { AppController, DemoAppController } from "./app-controller"; -import {PathfinderMeshData} from "./meshes"; -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 {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; +import {AppController, DemoAppController} from "./app-controller"; 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"; @@ -33,9 +34,9 @@ const MIN_FONT_SIZE: number = 6; const MAX_FONT_SIZE: number = 200; const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { + ecaa: ECAAMonochromeStrategy, none: NoAAStrategy, ssaa: SSAAStrategy, - ecaa: ECAAMonochromeStrategy, }; interface ElapsedTime { @@ -50,6 +51,24 @@ interface AntialiasingStrategyTable { } class BenchmarkAppController extends DemoAppController { + 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() { super.start(); @@ -104,8 +123,8 @@ class BenchmarkAppController extends DemoAppController { view.uploadPathTransforms(1); view.uploadHints(); view.attachMeshes([expandedMeshes.meshes]); - }) - }) + }); + }); } protected createView(): BenchmarkTestView { @@ -128,7 +147,7 @@ class BenchmarkAppController extends DemoAppController { renderedPromise.then(elapsedTime => { this.elapsedTimes.push({ size: this.pixelsPerEm, time: elapsedTime }); - if (this.pixelsPerEm == MAX_FONT_SIZE) { + if (this.pixelsPerEm === MAX_FONT_SIZE) { this.showResults(); return; } @@ -156,27 +175,29 @@ class BenchmarkAppController extends DemoAppController { 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 { + 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 = 'directCurve'; + protected directInteriorProgramName: keyof ShaderMap = 'directInterior'; + + protected depthFunction: number = this.gl.GREATER; + + protected camera: OrthographicCamera; + + private _pixelsPerEm: number = 32.0; + + private readonly appController: BenchmarkAppController; + constructor(appController: BenchmarkAppController, commonShaderSource: string, shaderSources: ShaderMap) { @@ -189,6 +210,15 @@ class BenchmarkTestView extends MonochromePathfinderView { 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, aaLevel: number, 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 { return glmatrix.vec2.clone([this.canvas.width, this.canvas.height]); } @@ -260,10 +279,6 @@ class BenchmarkTestView extends MonochromePathfinderView { return this.destAllocatedSize; } - private readonly appController: BenchmarkAppController; - - protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.clone([1.0, 1.0]); - protected get worldTransform() { const transform = glmatrix.mat4.create(); const translation = this.camera.translation; @@ -293,20 +308,6 @@ class BenchmarkTestView extends MonochromePathfinderView { this.uploadPathTransforms(1); 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 = 'directCurve'; - protected directInteriorProgramName: keyof ShaderMap = 'directInterior'; - - protected depthFunction: number = this.gl.GREATER; - - protected camera: OrthographicCamera; } function main() { diff --git a/demo/client/src/buffer-texture.ts b/demo/client/src/buffer-texture.ts index 281bc39a..28e3cd30 100644 --- a/demo/client/src/buffer-texture.ts +++ b/demo/client/src/buffer-texture.ts @@ -14,6 +14,13 @@ import {setTextureParameters, UniformMap} from './gl-utils'; import {expectNotNull} from './utils'; 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) { this.texture = expectNotNull(gl.createTexture(), "Failed to create buffer texture!"); this.size = glmatrix.vec2.create(); @@ -28,7 +35,7 @@ export default class PathfinderBufferTexture { const glType = data instanceof Float32Array ? gl.FLOAT : gl.UNSIGNED_BYTE; 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 height = Math.ceil(area / width); 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. let remainderLength = data.length - splitIndex; let remainder: Float32Array | Uint8Array; - if (remainderLength % 4 == 0) { + if (remainderLength % 4 === 0) { remainder = data.slice(splitIndex); } else { remainderLength += 4 - remainderLength % 4; @@ -101,11 +108,4 @@ export default class PathfinderBufferTexture { private get capacityArea() { return this.capacity[0] * this.capacity[1]; } - - readonly texture: WebGLTexture; - readonly uniformName: string; - private size: glmatrix.vec2; - private capacity: glmatrix.vec2; - private glType: number; } - diff --git a/demo/client/src/camera.ts b/demo/client/src/camera.ts index 8ad87edb..917ceaaf 100644 --- a/demo/client/src/camera.ts +++ b/demo/client/src/camera.ts @@ -64,17 +64,30 @@ interface PerspectiveMovementKeys { } export abstract class Camera { + protected canvas: HTMLCanvasElement; + constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; } abstract zoomIn(): void; abstract zoomOut(): void; - - protected canvas: HTMLCanvasElement; } 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) { super(canvas); @@ -122,6 +135,31 @@ export class OrthographicCamera extends Camera { 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 { 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 { const absoluteTranslation = glmatrix.vec2.create(); glmatrix.vec2.sub(absoluteTranslation, this.translation, point); @@ -227,22 +240,23 @@ export class OrthographicCamera extends Camera { set bounds(newBounds: glmatrix.vec4) { 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 { + 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) { super(canvas); @@ -272,6 +286,14 @@ export class PerspectiveCamera extends Camera { ]); } + zoomIn(): void { + // TODO(pcwalton) + } + + zoomOut(): void { + // TODO(pcwalton) + } + private onMouseDown(event: MouseEvent): void { if (document.pointerLockElement !== this.canvas) { this.canvas.requestPointerLock(); @@ -391,26 +413,4 @@ export class PerspectiveCamera extends Camera { glmatrix.mat4.rotateY(matrix, matrix, this.rotation[0]); 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; } diff --git a/demo/client/src/ecaa-strategy.ts b/demo/client/src/ecaa-strategy.ts index 1cabde39..9da89593 100644 --- a/demo/client/src/ecaa-strategy.ts +++ b/demo/client/src/ecaa-strategy.ts @@ -11,6 +11,7 @@ import * as glmatrix from 'gl-matrix'; import {AntialiasingStrategy} from './aa-strategy'; +import PathfinderBufferTexture from './buffer-texture'; import {createFramebuffer, createFramebufferColorTexture} from './gl-utils'; import {createFramebufferDepthTexture, setTextureParameters, UniformMap} 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 {UINT32_SIZE, unwrapNull} from './utils'; import {MonochromePathfinderView} from './view'; -import PathfinderBufferTexture from './buffer-texture'; interface UpperAndLower { upper: T; @@ -26,6 +26,27 @@ interface UpperAndLower { } 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; + private curveVAOs: UpperAndLower; + private resolveVAO: WebGLVertexArrayObject; + constructor(level: number, subpixelAA: boolean) { super(); @@ -67,6 +88,58 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { 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 { return glmatrix.mat4.create(); } @@ -82,6 +155,30 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { 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) { this.aaAlphaTexture = unwrapNull(view.gl.createTexture()); view.gl.activeTexture(view.gl.TEXTURE0); @@ -148,8 +245,8 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { view.vertexArrayObjectExt.bindVertexArrayOES(vaos[direction]); const lineIndexBuffer = { - upper: view.meshes[0].edgeUpperLineIndices, lower: view.meshes[0].edgeLowerLineIndices, + upper: view.meshes[0].edgeUpperLineIndices, }[direction]; view.gl.useProgram(lineProgram.program); @@ -183,8 +280,8 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { view.vertexArrayObjectExt.bindVertexArrayOES(vaos[direction]); const curveIndexBuffer = { - upper: view.meshes[0].edgeUpperCurveIndices, lower: view.meshes[0].edgeLowerCurveIndices, + upper: view.meshes[0].edgeUpperCurveIndices, }[direction]; view.gl.useProgram(curveProgram.program); @@ -227,58 +324,6 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { 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) { // Set state for conservative coverage. const coverProgram = view.shaderPrograms.ecaaCover; @@ -350,8 +395,8 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { view.vertexArrayObjectExt.bindVertexArrayOES(this.lineVAOs[direction]); view.gl.uniform1i(uniforms.uLowerPart, direction === 'lower' ? 1 : 0); const count = { - upper: view.meshData[0].edgeUpperLineIndexCount, lower: view.meshData[0].edgeLowerLineIndexCount, + upper: view.meshData[0].edgeUpperLineIndexCount, }[direction]; view.instancedArraysExt.drawElementsInstancedANGLE(view.gl.TRIANGLES, 6, @@ -375,8 +420,8 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { view.vertexArrayObjectExt.bindVertexArrayOES(this.curveVAOs[direction]); view.gl.uniform1i(uniforms.uLowerPart, direction === 'lower' ? 1 : 0); const count = { - upper: view.meshData[0].edgeUpperCurveIndexCount, lower: view.meshData[0].edgeLowerCurveIndexCount, + upper: view.meshData[0].edgeUpperCurveIndexCount, }[direction]; view.instancedArraysExt.drawElementsInstancedANGLE(view.gl.TRIANGLES, 6, @@ -422,30 +467,6 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { 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 { return null; } @@ -453,27 +474,6 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { protected get supersampleScale(): glmatrix.vec2 { 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; - private curveVAOs: UpperAndLower; - 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 { @@ -515,6 +515,13 @@ export class ECAAMonochromeStrategy 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 { return view.shaderPrograms.ecaaMultiResolve; } @@ -627,11 +634,4 @@ export class ECAAMulticolorStrategy extends ECAAStrategy { protected get directDepthTexture(): WebGLTexture { return this._directDepthTexture; } - - private _directDepthTexture: WebGLTexture; - - private edgeDetectFramebuffer: WebGLFramebuffer; - private edgeDetectVAO: WebGLVertexArrayObject; - private bgColorTexture: WebGLTexture; - private fgColorTexture: WebGLTexture; } diff --git a/demo/client/src/file-picker.ts b/demo/client/src/file-picker.ts index 9def298b..cd0d3b07 100644 --- a/demo/client/src/file-picker.ts +++ b/demo/client/src/file-picker.ts @@ -11,17 +11,21 @@ import {expectNotNull} from "./utils"; 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) { this.element = element; this.onFileLoaded = null; 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() { this.element.click(); } @@ -36,8 +40,4 @@ export class FilePickerView { }, false); reader.readAsArrayBuffer(file); } - - onFileLoaded: ((fileData: ArrayBuffer) => void) | null; - - private readonly element: HTMLInputElement; } diff --git a/demo/client/src/gl-utils.ts b/demo/client/src/gl-utils.ts index 4eb6e945..25c47c0d 100644 --- a/demo/client/src/gl-utils.ts +++ b/demo/client/src/gl-utils.ts @@ -10,7 +10,7 @@ import * as glmatrix from 'gl-matrix'; -import {UINT32_SIZE, assert, unwrapNull} from './utils'; +import {assert, UINT32_SIZE, unwrapNull} from './utils'; export type WebGLVertexArrayObject = any; @@ -98,7 +98,7 @@ export function createFramebuffer(gl: WebGLRenderingContext, 0); } - assert(gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE, + assert(gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE, "Framebuffer was incomplete!"); return framebuffer; } diff --git a/demo/client/src/mesh-debugger.ts b/demo/client/src/mesh-debugger.ts index 10e50760..f7eabdbe 100644 --- a/demo/client/src/mesh-debugger.ts +++ b/demo/client/src/mesh-debugger.ts @@ -11,20 +11,20 @@ import * as glmatrix from 'gl-matrix'; import * as opentype from "opentype.js"; +import {Font} from 'opentype.js'; import {AppController} from "./app-controller"; import {OrthographicCamera} from "./camera"; 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_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_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 { GlyphStore, TextFrame, PathfinderFont } from "./text"; -import {unwrapNull, UINT32_SIZE, UINT32_MAX, assert} from "./utils"; +import {GlyphStore, PathfinderFont, TextFrame} from "./text"; +import {assert, UINT32_MAX, UINT32_SIZE, unwrapNull} from "./utils"; import {PathfinderView} from "./view"; -import {Font} from 'opentype.js'; const CHARACTER: string = 'A'; @@ -49,6 +49,22 @@ const SVG_SCALE: number = 1.0; type FileType = 'font' | 'svg'; 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() { super.start(); @@ -78,6 +94,43 @@ class MeshDebuggerAppController extends AppController { 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; + + 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 { window.jQuery(this.openModal).modal(); } @@ -98,18 +151,6 @@ class MeshDebuggerAppController extends AppController { 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 { this.file = new PathfinderFont(fileData); this.fileData = fileData; @@ -141,50 +182,13 @@ class MeshDebuggerAppController extends AppController { this.fontPathSelect.appendChild(newOption); } } - - protected loadPath(opentypeGlyph?: opentype.Glyph | null) { - window.jQuery(this.openModal).modal('hide'); - - let promise: Promise; - - 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 { + camera: OrthographicCamera; + + private appController: MeshDebuggerAppController; + constructor(appController: MeshDebuggerAppController) { super(); @@ -310,14 +314,10 @@ class MeshDebuggerView extends PathfinderView { context.restore(); } - - private appController: MeshDebuggerAppController; - - camera: OrthographicCamera; } function getPosition(positions: Float32Array, vertexIndex: number): Float32Array | null { - if (vertexIndex == UINT32_MAX) + if (vertexIndex === UINT32_MAX) return null; return new Float32Array([positions[vertexIndex * 2 + 0], -positions[vertexIndex * 2 + 1]]); } diff --git a/demo/client/src/meshes.ts b/demo/client/src/meshes.ts index a8c440a8..8366d73f 100644 --- a/demo/client/src/meshes.ts +++ b/demo/client/src/meshes.ts @@ -10,20 +10,20 @@ import * as base64js from 'base64-js'; -import { PathfinderError, expectNotNull, panic, UINT32_SIZE, UINT32_MAX } from './utils'; import * as _ from 'lodash'; +import { expectNotNull, panic, PathfinderError, UINT32_MAX, UINT32_SIZE } from './utils'; const BUFFER_TYPES: Meshes = { bQuads: 'ARRAY_BUFFER', - bVertexPositions: 'ARRAY_BUFFER', - bVertexPathIDs: 'ARRAY_BUFFER', bVertexLoopBlinnData: 'ARRAY_BUFFER', - coverInteriorIndices: 'ELEMENT_ARRAY_BUFFER', + bVertexPathIDs: 'ARRAY_BUFFER', + bVertexPositions: 'ARRAY_BUFFER', coverCurveIndices: 'ELEMENT_ARRAY_BUFFER', - edgeUpperLineIndices: 'ARRAY_BUFFER', + coverInteriorIndices: 'ELEMENT_ARRAY_BUFFER', + edgeLowerCurveIndices: 'ARRAY_BUFFER', edgeLowerLineIndices: 'ARRAY_BUFFER', edgeUpperCurveIndices: 'ARRAY_BUFFER', - edgeLowerCurveIndices: 'ARRAY_BUFFER', + edgeUpperLineIndices: 'ARRAY_BUFFER', }; export const B_QUAD_SIZE: number = 4 * 8; @@ -54,6 +54,23 @@ export interface Meshes { } export class PathfinderMeshData implements Meshes { + 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) { for (const bufferName of Object.keys(BUFFER_TYPES) as Array>) { const meshBuffer = meshes[bufferName]; @@ -177,53 +194,26 @@ export class PathfinderMeshData implements Meshes { return new PathfinderMeshData({ 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 ArrayBuffer, - coverInteriorIndices: new Uint32Array(expandedCoverInteriorIndices).buffer as - ArrayBuffer, + bVertexPathIDs: new Uint16Array(expandedBVertexPathIDs).buffer as ArrayBuffer, + bVertexPositions: new Float32Array(expandedBVertexPositions).buffer as ArrayBuffer, coverCurveIndices: new Uint32Array(expandedCoverCurveIndices).buffer as ArrayBuffer, - edgeUpperCurveIndices: new Uint32Array(expandedEdgeUpperCurveIndices).buffer as - ArrayBuffer, - edgeUpperLineIndices: new Uint32Array(expandedEdgeUpperLineIndices).buffer as + coverInteriorIndices: new Uint32Array(expandedCoverInteriorIndices).buffer as ArrayBuffer, edgeLowerCurveIndices: new Uint32Array(expandedEdgeLowerCurveIndices).buffer as ArrayBuffer, edgeLowerLineIndices: new Uint32Array(expandedEdgeLowerLineIndices).buffer as 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 { - constructor(gl: WebGLRenderingContext, meshData: PathfinderMeshData) { - for (const bufferName of Object.keys(BUFFER_TYPES) as Array) { - 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 bVertexPositions: WebGLBuffer; readonly bVertexPathIDs: WebGLBuffer; @@ -234,6 +224,16 @@ export class PathfinderMeshBuffers implements Meshes { readonly edgeUpperCurveIndices: WebGLBuffer; readonly edgeLowerLineIndices: WebGLBuffer; readonly edgeLowerCurveIndices: WebGLBuffer; + + constructor(gl: WebGLRenderingContext, meshData: PathfinderMeshData) { + for (const bufferName of Object.keys(BUFFER_TYPES) as Array) { + 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[], @@ -271,7 +271,7 @@ function findFirstBQuadIndex(bQuads: Uint32Array, queryPathID: number): number | const thisPathID = bQuads[mid * B_QUAD_FIELD_COUNT]; if (queryPathID <= thisPathID) high = mid; - else + else low = mid + 1; } return bQuads[low * B_QUAD_FIELD_COUNT] === queryPathID ? low : null; diff --git a/demo/client/src/shader-loader.ts b/demo/client/src/shader-loader.ts index cd78adb8..5183864e 100644 --- a/demo/client/src/shader-loader.ts +++ b/demo/client/src/shader-loader.ts @@ -9,7 +9,7 @@ // except according to those terms. import {AttributeMap, UniformMap} from './gl-utils'; -import {PathfinderError, expectNotNull, unwrapNull} from './utils'; +import {expectNotNull, PathfinderError, unwrapNull} from './utils'; export interface UnlinkedShaderProgram { vertex: WebGLShader; @@ -37,60 +37,60 @@ export const SHADER_NAMES: Array> = [ const SHADER_URLS: ShaderMap = { blit: { - vertex: "/glsl/gles2/blit.vs.glsl", fragment: "/glsl/gles2/blit.fs.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", + vertex: "/glsl/gles2/blit.vs.glsl", }, demo3DMonument: { - vertex: "/glsl/gles2/demo-3d-monument.vs.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 { + common: Promise; + shaders: Promise>; + load() { this.common = window.fetch(COMMON_SHADER_URL).then(response => response.text()); const shaderKeys = Object.keys(SHADER_URLS) as Array>; - let promises = []; + const promises = []; for (const shaderKey of shaderKeys) { promises.push(Promise.all([ window.fetch(SHADER_URLS[shaderKey].vertex).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 => { - let shaderMap: Partial> = {}; + const shaderMap: Partial> = {}; for (let keyIndex = 0; keyIndex < shaderKeys.length; keyIndex++) shaderMap[shaderKeys[keyIndex]] = promises[keyIndex]; return shaderMap as ShaderMap; }); } - - common: Promise; - shaders: Promise>; } export class PathfinderShaderProgram { + readonly uniforms: UniformMap; + readonly attributes: AttributeMap; + readonly program: WebGLProgram; + constructor(gl: WebGLRenderingContext, programName: string, unlinkedShaderProgram: UnlinkedShaderProgram) { @@ -155,7 +159,7 @@ export class PathfinderShaderProgram { gl.attachShader(this.program, compiledShader); 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); 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 attributeCount = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES); - let uniforms: UniformMap = {}; - let attributes: AttributeMap = {}; + const uniforms: UniformMap = {}; + const attributes: AttributeMap = {}; for (let uniformIndex = 0; uniformIndex < uniformCount; uniformIndex++) { const uniformName = unwrapNull(gl.getActiveUniform(this.program, uniformIndex)).name; @@ -179,8 +183,4 @@ export class PathfinderShaderProgram { this.uniforms = uniforms; this.attributes = attributes; } - - readonly uniforms: UniformMap; - readonly attributes: AttributeMap; - readonly program: WebGLProgram; } diff --git a/demo/client/src/ssaa-strategy.ts b/demo/client/src/ssaa-strategy.ts index 286b79bb..89a58c2c 100644 --- a/demo/client/src/ssaa-strategy.ts +++ b/demo/client/src/ssaa-strategy.ts @@ -11,11 +11,20 @@ import * as glmatrix from 'gl-matrix'; import {AntialiasingStrategy} from './aa-strategy'; -import {createFramebufferDepthTexture, createFramebuffer, setTextureParameters} from './gl-utils'; +import {createFramebuffer, createFramebufferDepthTexture, setTextureParameters} from './gl-utils'; import {unwrapNull} from './utils'; import {PathfinderDemoView} from './view'; 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) { super(); this.level = level; @@ -111,7 +120,7 @@ export default class SSAAStrategy extends AntialiasingStrategy { } 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 { @@ -119,14 +128,4 @@ export default class SSAAStrategy extends AntialiasingStrategy { glmatrix.vec2.mul(result, view.destUsedSize, this.supersampleScale); 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; } - diff --git a/demo/client/src/svg-demo.ts b/demo/client/src/svg-demo.ts index a78a8470..c2147a48 100644 --- a/demo/client/src/svg-demo.ts +++ b/demo/client/src/svg-demo.ts @@ -11,17 +11,17 @@ import * as glmatrix from 'gl-matrix'; import * as _ from 'lodash'; -import {DemoAppController} from './app-controller'; import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; +import {DemoAppController} from './app-controller'; +import PathfinderBufferTexture from "./buffer-texture"; import {OrthographicCamera} from "./camera"; -import {ECAAStrategy, ECAAMulticolorStrategy} from "./ecaa-strategy"; +import {ECAAMulticolorStrategy, ECAAStrategy} from "./ecaa-strategy"; import {PathfinderMeshData} from "./meshes"; 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 {PathfinderDemoView, Timings} from './view'; -import SSAAStrategy from "./ssaa-strategy"; -import PathfinderBufferTexture from "./buffer-texture"; 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 ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { + ecaa: ECAAMulticolorStrategy, none: NoAAStrategy, ssaa: SSAAStrategy, - ecaa: ECAAMulticolorStrategy, }; interface AntialiasingStrategyTable { @@ -42,6 +42,12 @@ interface AntialiasingStrategyTable { } class SVGDemoController extends DemoAppController { + loader: SVGLoader; + + protected readonly builtinFileURI: string = BUILTIN_SVG_URI; + + private meshes: PathfinderMeshData; + start() { super.start(); @@ -55,7 +61,7 @@ class SVGDemoController extends DemoAppController { this.loader.partition().then(meshes => { this.meshes = meshes; this.meshesReceived(); - }) + }); } protected createView() { @@ -64,8 +70,6 @@ class SVGDemoController extends DemoAppController { unwrapNull(this.shaderSources)); } - protected readonly builtinFileURI: string = BUILTIN_SVG_URI; - protected get defaultFile(): string { return DEFAULT_FILE; } @@ -78,15 +82,19 @@ class SVGDemoController extends DemoAppController { view.camera.bounds = this.loader.bounds; view.camera.zoomToFit(); - }) + }); } - - loader: SVGLoader; - - private meshes: PathfinderMeshData; } 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, commonShaderSource: string, shaderSources: ShaderMap) { @@ -156,8 +164,6 @@ class SVGDemoView extends PathfinderDemoView { this.appController.newTimingsReceived(_.pick(this.lastTimings, ['rendering'])); } - protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.fromValues(1.0, 1.0); - protected get worldTransform() { const transform = glmatrix.mat4.create(); const translation = this.camera.translation; @@ -177,12 +183,6 @@ class SVGDemoView extends PathfinderDemoView { protected get directInteriorProgramName(): keyof ShaderMap { return 'directInterior'; } - - protected depthFunction: number = this.gl.GREATER; - - private appController: SVGDemoController; - - camera: OrthographicCamera; } function main() { diff --git a/demo/client/src/svg-loader.ts b/demo/client/src/svg-loader.ts index 7cfefd70..32b1f42a 100644 --- a/demo/client/src/svg-loader.ts +++ b/demo/client/src/svg-loader.ts @@ -12,8 +12,8 @@ import * as glmatrix from 'gl-matrix'; import * as _ from 'lodash'; import 'path-data-polyfill.js'; -import {panic, unwrapNull} from "./utils"; import {PathfinderMeshData} from "./meshes"; +import {panic, unwrapNull} from "./utils"; export const BUILTIN_SVG_URI: string = "/svg/demo"; @@ -39,6 +39,15 @@ export interface PathInstance { } export class SVGLoader { + pathInstances: PathInstance[]; + scale: number; + bounds: glmatrix.vec4; + + private svg: SVGSVGElement; + private fileData: ArrayBuffer; + + private paths: any[]; + constructor() { this.scale = 1.0; this.svg = unwrapNull(document.getElementById('pf-svg')) as Element as SVGSVGElement; @@ -57,6 +66,22 @@ export class SVGLoader { this.attachSVG(svgElement); } + partition(pathIndex?: number | undefined): Promise { + // 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) { // Clear out the current document. let kid; @@ -69,7 +94,7 @@ export class SVGLoader { // Scan for geometry elements. this.pathInstances.length = 0; - const queue: Array = [this.svg]; + const queue: Element[] = [this.svg]; let element; while ((element = queue.pop()) != null) { let kid = element.lastChild; @@ -86,7 +111,7 @@ export class SVGLoader { if (style.stroke !== 'none') { this.pathInstances.push({ 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]); } - - partition(pathIndex?: number | undefined): Promise { - // 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; } diff --git a/demo/client/src/text-demo.ts b/demo/client/src/text-demo.ts index bb528450..6601421b 100644 --- a/demo/client/src/text-demo.ts +++ b/demo/client/src/text-demo.ts @@ -8,13 +8,15 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -import * as _ from 'lodash'; import * as base64js from 'base64-js'; import * as glmatrix from 'gl-matrix'; +import * as _ from 'lodash'; import * as opentype from 'opentype.js'; +import {Metrics} from 'opentype.js'; import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy'; import {DemoAppController} from './app-controller'; +import PathfinderBufferTexture from './buffer-texture'; import {OrthographicCamera} from "./camera"; import {ECAAMonochromeStrategy, ECAAStrategy} from './ecaa-strategy'; import {createFramebuffer, createFramebufferColorTexture} from './gl-utils'; @@ -22,14 +24,12 @@ import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from import {UniformMap} from './gl-utils'; import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes'; 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 {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 { MonochromePathfinderView, Timings, TIMINGS } from './view'; -import PathfinderBufferTexture from './buffer-texture'; -import SSAAStrategy from './ssaa-strategy'; -import { Metrics } from 'opentype.js'; const DEFAULT_TEXT: string = `’Twas brillig, and the slithy toves @@ -123,6 +123,24 @@ function rectsIntersect(a: glmatrix.vec4, b: glmatrix.vec4): boolean { } class TextDemoController extends DemoAppController { + 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() { super(); this.text = DEFAULT_TEXT; @@ -154,14 +172,8 @@ class TextDemoController extends DemoAppController { window.jQuery(this.editTextModal).modal(); } - private hintingChanged(): void { - this.view.then(view => view.updateHinting()); - } - - private updateText(): void { - this.text = this.editTextArea.value; - - window.jQuery(this.editTextModal).modal('hide'); + createHint(): Hint { + return new Hint(this.font, this.pixelsPerUnit, this.useHinting); } protected createView() { @@ -175,6 +187,16 @@ class TextDemoController extends DemoAppController { 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) { const newLayout = new SimpleTextLayout(font, this.text); @@ -220,10 +242,6 @@ class TextDemoController extends DemoAppController { return this.hintingSelect.selectedIndex !== 0; } - createHint(): Hint { - return new Hint(this.font, this.pixelsPerUnit, this.useHinting); - } - protected get builtinFileURI(): string { return BUILTIN_FONT_URI; } @@ -232,27 +250,25 @@ class TextDemoController extends DemoAppController { 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 { + 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, commonShaderSource: string, shaderSources: ShaderMap) { @@ -261,13 +277,40 @@ class TextDemoView extends MonochromePathfinderView { this.appController = appController; this.camera = new OrthographicCamera(this.canvas, { - minScale: MIN_SCALE, maxScale: MAX_SCALE, + minScale: MIN_SCALE, }); 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() { super.initContext(); } @@ -287,12 +330,113 @@ class TextDemoView extends MonochromePathfinderView { 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. private layoutText() { const layout = this.appController.layout; layout.layoutRuns(); - let textBounds = layout.textFrame.bounds; + const textBounds = layout.textFrame.bounds; this.camera.bounds = textBounds; const totalGlyphCount = layout.textFrame.totalGlyphCount; @@ -389,28 +533,6 @@ class TextDemoView extends MonochromePathfinderView { 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() { const atlasColorTexture = this.appController.atlas.ensureTexture(this.gl); this.atlasDepthTexture = createFramebufferDepthTexture(this.gl, ATLAS_SIZE); @@ -443,7 +565,7 @@ class TextDemoView extends MonochromePathfinderView { glyphIndex++, globalGlyphIndex++) { const textGlyphID = run.glyphIDs[glyphIndex]; - let atlasGlyphIndex = _.sortedIndexOf(atlasGlyphIDs, textGlyphID); + const atlasGlyphIndex = _.sortedIndexOf(atlasGlyphIDs, textGlyphID); if (atlasGlyphIndex < 0) continue; @@ -477,45 +599,6 @@ class TextDemoView extends MonochromePathfinderView { 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) { this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0); } @@ -526,64 +609,6 @@ class TextDemoView extends MonochromePathfinderView { 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) { if (flag) { 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 { return this.atlasFramebuffer; } @@ -609,17 +631,6 @@ class TextDemoView extends MonochromePathfinderView { 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 { const transform = glmatrix.mat4.create(); glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]); @@ -634,19 +645,6 @@ class TextDemoView extends MonochromePathfinderView { protected get directInteriorProgramName(): keyof ShaderMap { return 'directInterior'; } - - protected depthFunction: number = this.gl.GREATER; - - atlasFramebuffer: WebGLFramebuffer; - atlasDepthTexture: WebGLTexture; - - glyphPositionsBuffer: WebGLBuffer; - glyphTexCoordsBuffer: WebGLBuffer; - glyphElementsBuffer: WebGLBuffer; - - appController: TextDemoController; - - camera: OrthographicCamera; } interface AntialiasingStrategyTable { @@ -656,6 +654,9 @@ interface AntialiasingStrategyTable { } class Atlas { + private _texture: WebGLTexture | null; + private _usedSize: Size2D; + constructor() { this._texture = null; this._usedSize = glmatrix.vec2.create(); @@ -725,12 +726,13 @@ class Atlas { get usedSize(): glmatrix.vec2 { return this._usedSize; } - - private _texture: WebGLTexture | null; - private _usedSize: Size2D; } class AtlasGlyph { + readonly glyphStoreIndex: number; + readonly glyphID: number; + readonly origin: glmatrix.vec2; + constructor(glyphStoreIndex: number, glyphID: number) { this.glyphStoreIndex = glyphStoreIndex; this.glyphID = glyphID; @@ -756,16 +758,12 @@ class AtlasGlyph { private setPixelOrigin(pixelOrigin: glmatrix.vec2, pixelsPerUnit: number): void { glmatrix.vec2.scale(this.origin, pixelOrigin, 1.0 / pixelsPerUnit); } - - readonly glyphStoreIndex: number; - readonly glyphID: number; - readonly origin: glmatrix.vec2; } const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { + ecaa: ECAAMonochromeStrategy, none: NoAAStrategy, ssaa: SSAAStrategy, - ecaa: ECAAMonochromeStrategy, }; function main() { diff --git a/demo/client/src/text.ts b/demo/client/src/text.ts index e346d028..78cac0a1 100644 --- a/demo/client/src/text.ts +++ b/demo/client/src/text.ts @@ -8,14 +8,14 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -import {Metrics} from 'opentype.js'; import * as base64js from 'base64-js'; import * as glmatrix from 'gl-matrix'; import * as _ from 'lodash'; import * as opentype from "opentype.js"; +import {Metrics} from 'opentype.js'; 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"; @@ -26,8 +26,8 @@ export interface ExpandedMeshData { } export interface PartitionResult { - meshes: PathfinderMeshData, - time: number, + meshes: PathfinderMeshData; + time: number; } export interface PixelMetrics { @@ -39,7 +39,7 @@ export interface PixelMetrics { opentype.Font.prototype.isSupported = function() { return (this as any).supported; -} +}; opentype.Font.prototype.lineHeight = function() { const os2Table = this.tables.os2; @@ -47,6 +47,11 @@ opentype.Font.prototype.lineHeight = function() { }; export class PathfinderFont { + readonly opentypeFont: opentype.Font; + readonly data: ArrayBuffer; + + private metricsCache: Metrics[]; + constructor(data: ArrayBuffer) { this.data = data; @@ -62,14 +67,15 @@ export class PathfinderFont { this.metricsCache[glyphID] = this.opentypeFont.glyphs.get(glyphID).getMetrics(); return this.metricsCache[glyphID]; } - - readonly opentypeFont: opentype.Font; - readonly data: ArrayBuffer; - - private metricsCache: Metrics[]; } export class TextRun { + readonly glyphIDs: number[]; + advances: number[]; + readonly origin: number[]; + + private readonly font: PathfinderFont; + constructor(text: number[] | string, origin: number[], font: PathfinderFont) { if (typeof(text) === 'string') { 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): glmatrix.vec2 { const textGlyphOrigin = glmatrix.vec2.clone(this.origin); @@ -121,13 +121,19 @@ export class TextRun { return lastAdvance + this.font.opentypeFont.glyphs.get(lastGlyphID).advanceWidth; } - readonly glyphIDs: number[]; - advances: number[]; - readonly origin: number[]; - private readonly font: PathfinderFont; + private pixelMetricsForGlyphAt(index: number, pixelsPerUnit: number, hint: Hint): + PixelMetrics { + const metrics = unwrapNull(this.font.metricsForGlyph(index)); + return calculatePixelMetricsForGlyph(metrics, pixelsPerUnit, hint); + } } export class TextFrame { + readonly runs: TextRun[]; + readonly origin: glmatrix.vec3; + + private readonly font: PathfinderFont; + constructor(runs: TextRun[], font: PathfinderFont) { this.runs = runs; this.origin = glmatrix.vec3.create(); @@ -137,8 +143,7 @@ export class TextFrame { expandMeshes(meshes: PathfinderMeshData, glyphIDs: number[]): ExpandedMeshData { const pathIDs = []; for (const textRun of this.runs) { - for (let glyphIndex = 0; glyphIndex < textRun.glyphIDs.length; glyphIndex++) { - const glyphID = textRun.glyphIDs[glyphIndex]; + for (const glyphID of textRun.glyphIDs) { if (glyphID === 0) continue; const pathID = _.sortedIndexOf(glyphIDs, glyphID); @@ -180,15 +185,13 @@ export class TextFrame { glyphIDs.push(...run.glyphIDs); return glyphIDs; } - - readonly runs: TextRun[]; - readonly origin: glmatrix.vec3; - - private readonly font: PathfinderFont; } /// Stores one copy of each glyph. export class GlyphStore { + readonly font: PathfinderFont; + readonly glyphIDs: number[]; + constructor(font: PathfinderFont, glyphIDs: number[]) { this.font = font; this.glyphIDs = glyphIDs; @@ -209,9 +212,9 @@ export class GlyphStore { // Make the request. return window.fetch(PARTITION_FONT_ENDPOINT_URI, { - method: 'POST', - headers: {'Content-Type': 'application/json'}, body: JSON.stringify(request), + headers: {'Content-Type': 'application/json'}, + method: 'POST', }).then(response => response.text()).then(responseText => { const response = JSON.parse(responseText); if (!('Ok' in response)) @@ -227,12 +230,11 @@ export class GlyphStore { const index = _.sortedIndexOf(this.glyphIDs, glyphID); return index >= 0 ? index : null; } - - readonly font: PathfinderFont; - readonly glyphIDs: number[]; } export class SimpleTextLayout { + readonly textFrame: TextFrame; + constructor(font: PathfinderFont, text: string) { const lineHeight = font.opentypeFont.lineHeight(); const textRuns: TextRun[] = text.split("\n").map((line, lineNumber) => { @@ -244,11 +246,14 @@ export class SimpleTextLayout { layoutRuns() { this.textFrame.runs.forEach(textRun => textRun.layout()); } - - readonly textFrame: TextFrame; } export class Hint { + readonly xHeight: number; + readonly hintedXHeight: number; + + private useHinting: boolean; + constructor(font: PathfinderFont, pixelsPerUnit: number, useHinting: boolean) { this.useHinting = useHinting; @@ -278,10 +283,6 @@ export class Hint { return glmatrix.vec2.fromValues(position[0], position[1] / this.xHeight * this.hintedXHeight); } - - readonly xHeight: number; - readonly hintedXHeight: number; - private useHinting: boolean; } export function calculatePixelXMin(metrics: Metrics, pixelsPerUnit: number): number { @@ -296,10 +297,10 @@ function calculatePixelMetricsForGlyph(metrics: Metrics, pixelsPerUnit: number, PixelMetrics { const top = hint.hintPosition(glmatrix.vec2.fromValues(0, metrics.yMax))[1]; return { - left: calculatePixelXMin(metrics, pixelsPerUnit), - right: Math.ceil(metrics.xMax * pixelsPerUnit), ascent: Math.ceil(top * pixelsPerUnit), descent: calculatePixelDescent(metrics, pixelsPerUnit), + left: calculatePixelXMin(metrics, pixelsPerUnit), + right: Math.ceil(metrics.xMax * pixelsPerUnit), }; } diff --git a/demo/client/src/view.ts b/demo/client/src/view.ts index a24aeaaa..1769ac2f 100644 --- a/demo/client/src/view.ts +++ b/demo/client/src/view.ts @@ -11,13 +11,13 @@ import * as glmatrix from 'gl-matrix'; import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; +import PathfinderBufferTexture from './buffer-texture'; import {Camera} from "./camera"; import {QUAD_ELEMENTS, UniformMap} from './gl-utils'; import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes'; import {PathfinderShaderProgram, SHADER_NAMES, ShaderMap} from './shader-loader'; import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader'; -import {PathfinderError, UINT32_SIZE, expectNotNull, unwrapNull} from './utils'; -import PathfinderBufferTexture from './buffer-texture'; +import {expectNotNull, PathfinderError, UINT32_SIZE, unwrapNull} from './utils'; const TIME_INTERVAL_DELAY: number = 32; @@ -40,18 +40,24 @@ const QUAD_TEX_COORDS: Float32Array = new Float32Array([ ]); export const TIMINGS: {[name: string]: string} = { - rendering: "Rendering", compositing: "Compositing", -} + rendering: "Rendering", +}; export interface Timings { - rendering: number; compositing: number; + rendering: number; } declare class WebGLQuery {} export abstract class PathfinderView { + protected canvas: HTMLCanvasElement; + + protected camera: Camera; + + private dirty: boolean; + constructor() { this.dirty = false; @@ -61,6 +67,29 @@ export abstract class PathfinderView { 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) { const width = window.innerWidth; @@ -84,38 +113,42 @@ export abstract class PathfinderView { 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 { + gl: WebGLRenderingContext; + + shaderPrograms: ShaderMap; + + 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) { super(); @@ -140,7 +173,7 @@ export abstract class PathfinderDemoView extends PathfinderView { subpixelAA: boolean) { this.antialiasingStrategy = this.createAAStrategy(aaType, aaLevel, subpixelAA); - let canvas = this.canvas; + const canvas = this.canvas; this.antialiasingStrategy.init(this); if (this.meshData != null) this.antialiasingStrategy.attachMeshes(this); @@ -156,6 +189,82 @@ export abstract class PathfinderDemoView extends PathfinderView { 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 { super.resized(); @@ -194,65 +303,6 @@ export abstract class PathfinderDemoView extends PathfinderView { this.compositingTimerQuery = this.timerQueryExt.createQueryEXT(); } - private compileShaders(commonSource: string, shaderSources: ShaderMap): - ShaderMap { - let shaders: Partial>> = {}; - - 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; - } - - private linkShaders(shaders: ShaderMap): - ShaderMap { - let shaderProgramMap: Partial> = {}; - for (const shaderName of Object.keys(shaders) as Array>) { - shaderProgramMap[shaderName] = new PathfinderShaderProgram(this.gl, - shaderName, - shaders[shaderName]); - } - return shaderProgramMap as ShaderMap; - } - - 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() { super.redraw(); @@ -309,19 +359,79 @@ export abstract class PathfinderDemoView extends PathfinderView { protected renderingFinished(): void {} - 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)); + protected getModelviewTransform(pathIndex: number): glmatrix.mat4 { + return glmatrix.mat4.create(); + } - const translation = glmatrix.vec4.clone([transform[12], transform[13], 0.0, 1.0]); + protected drawSceneryIfNecessary(): void {} - this.gl.uniform4f(uniforms.uTransformST, - transform[0], - transform[5], - transform[12], - transform[13]); + 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; + } + + 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): + ShaderMap { + const shaders: Partial>> = {}; + + 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; + } + + private linkShaders(shaders: ShaderMap): + ShaderMap { + const shaderProgramMap: Partial> = {}; + for (const shaderName of Object.keys(shaders) as Array>) { + shaderProgramMap[shaderName] = new PathfinderShaderProgram(this.gl, + shaderName, + shaders[shaderName]); + } + return shaderProgramMap as ShaderMap; } private setTransformUniform(uniforms: UniformMap, objectIndex: number) { @@ -450,7 +560,7 @@ export abstract class PathfinderDemoView extends PathfinderView { Array<'atlasRenderingTimerQuery' | 'compositingTimerQuery'>) { if (this.timerQueryExt.getQueryObjectEXT(this[queryName], this.timerQueryExt - .QUERY_RESULT_AVAILABLE_EXT) == 0) { + .QUERY_RESULT_AVAILABLE_EXT) === 0) { return; } } @@ -462,8 +572,8 @@ export abstract class PathfinderDemoView extends PathfinderView { this.timerQueryExt.getQueryObjectEXT(this.compositingTimerQuery, this.timerQueryExt.QUERY_RESULT_EXT); this.lastTimings = { - rendering: atlasRenderingTime / 1000000.0, compositing: compositingTime / 1000000.0, + rendering: atlasRenderingTime / 1000000.0, }; this.newTimingsReceived(); @@ -473,28 +583,6 @@ export abstract class PathfinderDemoView extends PathfinderView { }, 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() { const width = this.canvas.width, height = this.canvas.height; const scratchCanvas = document.createElement('canvas'); @@ -512,61 +600,6 @@ export abstract class PathfinderDemoView extends PathfinderView { 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 destAllocatedSize(): glmatrix.vec2; @@ -578,38 +611,6 @@ export abstract class PathfinderDemoView extends PathfinderView { protected abstract get directCurveProgramName(): keyof ShaderMap; protected abstract get directInteriorProgramName(): keyof ShaderMap; - - protected antialiasingStrategy: AntialiasingStrategy | null; - - gl: WebGLRenderingContext; - - shaderPrograms: ShaderMap; - - 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 { diff --git a/demo/client/tslint.json b/demo/client/tslint.json new file mode 100644 index 00000000..84215060 --- /dev/null +++ b/demo/client/tslint.json @@ -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": [] +} \ No newline at end of file diff --git a/demo/client/webpack.config.js b/demo/client/webpack.config.js index c131cf29..caa209e3 100644 --- a/demo/client/webpack.config.js +++ b/demo/client/webpack.config.js @@ -13,6 +13,15 @@ module.exports = { }, module: { 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?$/, use: 'ts-loader',