2017-09-11 14:22:19 -04:00
|
|
|
// pathfinder/client/src/benchmark.ts
|
|
|
|
//
|
|
|
|
// Copyright © 2017 The Pathfinder Project Developers.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
import * as glmatrix from 'gl-matrix';
|
2017-09-11 14:22:19 -04:00
|
|
|
import * as opentype from "opentype.js";
|
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
import { AppController, DemoAppController } from "./app-controller";
|
2017-09-11 14:22:19 -04:00
|
|
|
import {PathfinderMeshData} from "./meshes";
|
2017-09-19 20:35:11 -04:00
|
|
|
import { BUILTIN_FONT_URI, TextFrameGlyphStorage, PathfinderGlyph, TextFrame, TextRun, ExpandedMeshData } from "./text";
|
2017-09-12 15:40:14 -04:00
|
|
|
import { assert, unwrapNull, PathfinderError } from "./utils";
|
2017-09-14 00:41:33 -04:00
|
|
|
import { PathfinderDemoView, Timings, MonochromePathfinderView } from "./view";
|
2017-09-12 15:40:14 -04:00
|
|
|
import { ShaderMap, ShaderProgramSource } from "./shader-loader";
|
|
|
|
import { AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy } from "./aa-strategy";
|
|
|
|
import SSAAStrategy from './ssaa-strategy';
|
|
|
|
import { OrthographicCamera } from './camera';
|
2017-09-14 00:41:33 -04:00
|
|
|
import { ECAAStrategy, ECAAMonochromeStrategy } from './ecaa-strategy';
|
|
|
|
import PathfinderBufferTexture from './buffer-texture';
|
2017-09-11 14:22:19 -04:00
|
|
|
|
|
|
|
const STRING: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
|
|
|
|
|
|
const FONT: string = 'nimbus-sans';
|
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
const TEXT_COLOR: number[] = [0, 0, 0, 255];
|
|
|
|
|
|
|
|
const MIN_FONT_SIZE: number = 6;
|
|
|
|
const MAX_FONT_SIZE: number = 200;
|
|
|
|
|
|
|
|
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
|
|
|
none: NoAAStrategy,
|
|
|
|
ssaa: SSAAStrategy,
|
2017-09-14 00:41:33 -04:00
|
|
|
ecaa: ECAAMonochromeStrategy,
|
2017-09-12 15:40:14 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
interface ElapsedTime {
|
|
|
|
size: number;
|
|
|
|
time: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface AntialiasingStrategyTable {
|
|
|
|
none: typeof NoAAStrategy;
|
|
|
|
ssaa: typeof SSAAStrategy;
|
2017-09-14 00:41:33 -04:00
|
|
|
ecaa: typeof ECAAStrategy;
|
2017-09-12 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
2017-09-11 14:22:19 -04:00
|
|
|
start() {
|
|
|
|
super.start();
|
|
|
|
|
|
|
|
const runBenchmarkButton = unwrapNull(document.getElementById('pf-run-benchmark-button'));
|
|
|
|
runBenchmarkButton.addEventListener('click', () => this.runBenchmark(), false);
|
|
|
|
|
2017-09-19 23:19:53 -04:00
|
|
|
this.loadInitialFile(this.builtinFileURI);
|
2017-09-11 14:22:19 -04:00
|
|
|
}
|
|
|
|
|
2017-09-23 16:09:45 -04:00
|
|
|
protected fileLoaded(fileData: ArrayBuffer): void {
|
|
|
|
const font = opentype.parse(fileData);
|
2017-09-12 15:40:14 -04:00
|
|
|
this.font = font;
|
|
|
|
assert(this.font.isSupported(), "The font type is unsupported!");
|
2017-09-11 14:22:19 -04:00
|
|
|
|
|
|
|
const createGlyph = (glyph: opentype.Glyph) => new BenchmarkGlyph(glyph);
|
|
|
|
const textRun = new TextRun<BenchmarkGlyph>(STRING, [0, 0], font, createGlyph);
|
2017-09-12 15:40:14 -04:00
|
|
|
this.textRun = textRun;
|
2017-09-11 19:07:11 -04:00
|
|
|
const textFrame = new TextFrame([textRun], font);
|
2017-09-23 16:09:45 -04:00
|
|
|
this.glyphStorage = new TextFrameGlyphStorage(fileData, [textFrame], font);
|
2017-09-11 14:22:19 -04:00
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
this.glyphStorage.partition().then(baseMeshes => {
|
|
|
|
this.baseMeshes = baseMeshes;
|
|
|
|
const expandedMeshes = this.glyphStorage.expandMeshes(baseMeshes)[0];
|
|
|
|
this.expandedMeshes = expandedMeshes;
|
|
|
|
this.view.then(view => {
|
|
|
|
view.uploadPathColors(1);
|
|
|
|
view.uploadPathTransforms(1);
|
2017-09-14 00:41:33 -04:00
|
|
|
view.uploadHints();
|
2017-09-12 15:40:14 -04:00
|
|
|
view.attachMeshes([expandedMeshes.meshes]);
|
|
|
|
})
|
2017-09-11 14:22:19 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
protected createView(): BenchmarkTestView {
|
|
|
|
return new BenchmarkTestView(this,
|
|
|
|
unwrapNull(this.commonShaderSource),
|
|
|
|
unwrapNull(this.shaderSources));
|
|
|
|
}
|
|
|
|
|
2017-09-11 14:22:19 -04:00
|
|
|
private runBenchmark(): void {
|
2017-09-12 15:40:14 -04:00
|
|
|
this.pixelsPerEm = MIN_FONT_SIZE;
|
|
|
|
this.elapsedTimes = [];
|
|
|
|
this.view.then(view => this.runOneBenchmarkTest(view));
|
|
|
|
}
|
|
|
|
|
|
|
|
private runOneBenchmarkTest(view: BenchmarkTestView): void {
|
|
|
|
const renderedPromise = new Promise<number>((resolve, reject) => {
|
|
|
|
view.renderingPromiseCallback = resolve;
|
|
|
|
view.pixelsPerEm = this.pixelsPerEm;
|
|
|
|
});
|
|
|
|
renderedPromise.then(elapsedTime => {
|
|
|
|
this.elapsedTimes.push({ size: this.pixelsPerEm, time: elapsedTime });
|
|
|
|
|
|
|
|
if (this.pixelsPerEm == MAX_FONT_SIZE) {
|
|
|
|
console.info(this.elapsedTimes);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.pixelsPerEm++;
|
|
|
|
this.runOneBenchmarkTest(view);
|
|
|
|
});
|
2017-09-11 14:22:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
protected readonly defaultFile: string = FONT;
|
|
|
|
protected readonly builtinFileURI: string = BUILTIN_FONT_URI;
|
|
|
|
|
2017-09-19 20:35:11 -04:00
|
|
|
private glyphStorage: TextFrameGlyphStorage<BenchmarkGlyph>;
|
2017-09-12 15:40:14 -04:00
|
|
|
private baseMeshes: PathfinderMeshData;
|
|
|
|
private expandedMeshes: ExpandedMeshData;
|
|
|
|
|
|
|
|
private pixelsPerEm: number;
|
|
|
|
private elapsedTimes: ElapsedTime[];
|
|
|
|
|
|
|
|
font: opentype.Font | null;
|
|
|
|
textRun: TextRun<BenchmarkGlyph> | null;
|
|
|
|
}
|
|
|
|
|
2017-09-14 00:41:33 -04:00
|
|
|
class BenchmarkTestView extends MonochromePathfinderView {
|
2017-09-12 15:40:14 -04:00
|
|
|
constructor(appController: BenchmarkAppController,
|
|
|
|
commonShaderSource: string,
|
|
|
|
shaderSources: ShaderMap<ShaderProgramSource>) {
|
|
|
|
super(commonShaderSource, shaderSources);
|
|
|
|
|
|
|
|
this.appController = appController;
|
|
|
|
|
2017-09-13 16:30:26 -04:00
|
|
|
this.camera = new OrthographicCamera(this.canvas);
|
2017-09-12 15:40:14 -04:00
|
|
|
this.camera.onPan = () => this.setDirty();
|
|
|
|
this.camera.onZoom = () => this.setDirty();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
|
|
|
aaLevel: number,
|
|
|
|
subpixelAA: boolean):
|
|
|
|
AntialiasingStrategy {
|
2017-09-14 00:41:33 -04:00
|
|
|
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
|
2017-09-12 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
protected compositeIfNecessary(): void {}
|
|
|
|
|
|
|
|
protected updateTimings(timings: Timings): void {
|
|
|
|
// TODO(pcwalton)
|
|
|
|
}
|
|
|
|
|
|
|
|
protected pathColorsForObject(objectIndex: number): Uint8Array {
|
|
|
|
const pathColors = new Uint8Array(4 * (STRING.length + 1));
|
|
|
|
for (let pathIndex = 0; pathIndex < STRING.length; pathIndex++)
|
|
|
|
pathColors.set(TEXT_COLOR, (pathIndex + 1) * 4);
|
|
|
|
return pathColors;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
|
|
|
const pathTransforms = new Float32Array(4 * (STRING.length + 1));
|
2017-09-12 19:07:04 -04:00
|
|
|
|
|
|
|
let currentX = 0, currentY = 0;
|
|
|
|
const availableWidth = this.canvas.width / this.pixelsPerUnit;
|
|
|
|
const lineHeight = unwrapNull(this.appController.font).lineHeight();
|
2017-09-12 15:40:14 -04:00
|
|
|
|
|
|
|
for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
|
|
|
|
const glyph = unwrapNull(this.appController.textRun).glyphs[glyphIndex];
|
2017-09-12 19:07:04 -04:00
|
|
|
pathTransforms.set([1, 1, currentX, currentY], (glyphIndex + 1) * 4);
|
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
currentX += glyph.advanceWidth;
|
2017-09-12 19:07:04 -04:00
|
|
|
if (currentX > availableWidth) {
|
|
|
|
currentX = 0;
|
|
|
|
currentY += lineHeight;
|
|
|
|
}
|
2017-09-12 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return pathTransforms;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected renderingFinished(): void {
|
2017-09-12 19:07:04 -04:00
|
|
|
if (this.renderingPromiseCallback != null) {
|
|
|
|
const glyphCount = unwrapNull(this.appController.textRun).glyphs.length;
|
2017-09-13 14:56:40 -04:00
|
|
|
const usPerGlyph = this.lastTimings.rendering * 1000.0 / glyphCount;
|
2017-09-12 19:07:04 -04:00
|
|
|
this.renderingPromiseCallback(usPerGlyph);
|
|
|
|
}
|
2017-09-12 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
2017-09-14 00:41:33 -04:00
|
|
|
uploadHints(): void {
|
|
|
|
const glyphCount = unwrapNull(this.appController.textRun).glyphs.length;
|
|
|
|
const pathHints = new Float32Array((glyphCount + 1) * 4);
|
|
|
|
|
|
|
|
const pathHintsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathHints');
|
|
|
|
pathHintsBufferTexture.upload(this.gl, pathHints);
|
|
|
|
this.pathHintsBufferTexture = pathHintsBufferTexture;
|
|
|
|
}
|
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
destFramebuffer: WebGLFramebuffer | null = null;
|
|
|
|
|
|
|
|
get destAllocatedSize(): glmatrix.vec2 {
|
|
|
|
return glmatrix.vec2.clone([this.canvas.width, this.canvas.height]);
|
|
|
|
}
|
|
|
|
|
|
|
|
get destUsedSize(): glmatrix.vec2 {
|
|
|
|
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;
|
2017-09-14 00:03:10 -04:00
|
|
|
glmatrix.mat4.translate(transform, 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, [translation[0], translation[1], 0]);
|
2017-09-12 15:40:14 -04:00
|
|
|
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
|
|
|
|
|
2017-09-12 19:07:04 -04:00
|
|
|
const pixelsPerUnit = this.pixelsPerUnit;
|
2017-09-12 15:40:14 -04:00
|
|
|
glmatrix.mat4.scale(transform, transform, [pixelsPerUnit, pixelsPerUnit, 1.0]);
|
|
|
|
|
|
|
|
return transform;
|
|
|
|
}
|
|
|
|
|
2017-09-12 19:07:04 -04:00
|
|
|
private get pixelsPerUnit(): number {
|
|
|
|
return this._pixelsPerEm / unwrapNull(this.appController.font).unitsPerEm;
|
|
|
|
}
|
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
get pixelsPerEm(): number {
|
|
|
|
return this._pixelsPerEm;
|
|
|
|
}
|
|
|
|
|
|
|
|
set pixelsPerEm(newPixelsPerEm: number) {
|
|
|
|
this._pixelsPerEm = newPixelsPerEm;
|
2017-09-12 19:07:04 -04:00
|
|
|
this.uploadPathTransforms(1);
|
2017-09-12 15:40:14 -04:00
|
|
|
this.setDirty();
|
|
|
|
}
|
|
|
|
|
|
|
|
renderingPromiseCallback: ((time: number) => void) | null;
|
|
|
|
|
|
|
|
private _pixelsPerEm: number = 32.0;
|
|
|
|
|
2017-09-14 00:41:33 -04:00
|
|
|
readonly bgColor: glmatrix.vec4 = glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
|
|
|
|
readonly fgColor: glmatrix.vec4 = glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
|
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
protected directCurveProgramName: keyof ShaderMap<void> = 'directCurve';
|
|
|
|
protected directInteriorProgramName: keyof ShaderMap<void> = 'directInterior';
|
|
|
|
|
|
|
|
protected depthFunction: number = this.gl.GREATER;
|
|
|
|
|
|
|
|
protected camera: OrthographicCamera;
|
2017-09-11 14:22:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
class BenchmarkGlyph extends PathfinderGlyph {}
|
2017-09-12 15:40:14 -04:00
|
|
|
|
|
|
|
function main() {
|
|
|
|
const controller = new BenchmarkAppController;
|
|
|
|
window.addEventListener('load', () => controller.start(), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
main();
|