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-10-05 13:55:52 -04:00
|
|
|
import * as _ from 'lodash';
|
2017-09-11 14:22:19 -04:00
|
|
|
import * as opentype from "opentype.js";
|
|
|
|
|
2017-09-28 17:34:48 -04:00
|
|
|
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
2017-09-30 01:12:09 -04:00
|
|
|
import {SubpixelAAType} from "./aa-strategy";
|
2018-01-16 18:50:17 -05:00
|
|
|
import {AppController, DemoAppController, setSwitchInputsValue} from "./app-controller";
|
2017-09-28 17:34:48 -04:00
|
|
|
import PathfinderBufferTexture from './buffer-texture';
|
|
|
|
import {OrthographicCamera} from './camera';
|
2017-10-06 19:11:53 -04:00
|
|
|
import {UniformMap} from './gl-utils';
|
2018-03-07 17:06:54 -05:00
|
|
|
import {PathfinderMeshPack, PathfinderPackedMeshes} from "./meshes";
|
2017-12-05 22:08:31 -05:00
|
|
|
import {PathTransformBuffers, Renderer} from './renderer';
|
2017-09-28 17:34:48 -04:00
|
|
|
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
|
2017-09-12 15:40:14 -04:00
|
|
|
import SSAAStrategy from './ssaa-strategy';
|
2017-12-05 22:04:55 -05:00
|
|
|
import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
|
|
|
|
import {SVGRenderer} from './svg-renderer';
|
2017-09-28 17:34:48 -04:00
|
|
|
import {BUILTIN_FONT_URI, ExpandedMeshData, GlyphStore, PathfinderFont, TextFrame} from "./text";
|
2017-10-09 17:14:24 -04:00
|
|
|
import {computeStemDarkeningAmount, TextRun} from "./text";
|
2017-10-15 17:37:44 -04:00
|
|
|
import {assert, lerp, PathfinderError, unwrapNull, unwrapUndef} from "./utils";
|
2017-10-16 19:11:00 -04:00
|
|
|
import {DemoView, Timings} from "./view";
|
2018-02-05 18:10:52 -05:00
|
|
|
import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy';
|
2017-09-11 14:22:19 -04:00
|
|
|
|
|
|
|
const STRING: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
|
|
|
2017-12-05 22:04:55 -05:00
|
|
|
const DEFAULT_FONT: string = 'nimbus-sans';
|
|
|
|
const DEFAULT_SVG_FILE: string = 'tiger';
|
2017-09-11 14:22:19 -04:00
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
const TEXT_COLOR: number[] = [0, 0, 0, 255];
|
|
|
|
|
2017-10-15 17:37:44 -04:00
|
|
|
// In milliseconds.
|
|
|
|
const MIN_RUNTIME: number = 100;
|
|
|
|
const MAX_RUNTIME: number = 3000;
|
2017-10-05 13:55:52 -04:00
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
|
|
|
none: NoAAStrategy,
|
|
|
|
ssaa: SSAAStrategy,
|
2018-02-05 18:10:52 -05:00
|
|
|
xcaa: AdaptiveStencilMeshAAAStrategy,
|
2017-09-12 15:40:14 -04:00
|
|
|
};
|
|
|
|
|
2017-12-05 22:04:55 -05:00
|
|
|
interface BenchmarkModeMap<T> {
|
|
|
|
text: T;
|
|
|
|
svg: T;
|
|
|
|
}
|
|
|
|
|
|
|
|
type BenchmarkMode = 'text' | 'svg';
|
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
interface AntialiasingStrategyTable {
|
|
|
|
none: typeof NoAAStrategy;
|
|
|
|
ssaa: typeof SSAAStrategy;
|
2018-02-05 18:10:52 -05:00
|
|
|
xcaa: typeof AdaptiveStencilMeshAAAStrategy;
|
2017-09-12 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
2017-12-06 13:58:16 -05:00
|
|
|
interface TestParameter {
|
|
|
|
start: number;
|
|
|
|
stop: number;
|
|
|
|
step: number;
|
|
|
|
}
|
|
|
|
|
2017-12-05 22:04:55 -05:00
|
|
|
const DISPLAY_HEADER_LABELS: BenchmarkModeMap<string[]> = {
|
|
|
|
svg: ["Size (px)", "GPU time (ms)"],
|
|
|
|
text: ["Font size (px)", "GPU time per glyph (µs)"],
|
|
|
|
};
|
|
|
|
|
2017-12-06 13:58:16 -05:00
|
|
|
const TEST_SIZES: BenchmarkModeMap<TestParameter> = {
|
|
|
|
svg: { start: 64, stop: 2048, step: 16 },
|
|
|
|
text: { start: 6, stop: 200, step: 1 },
|
|
|
|
};
|
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
2018-02-19 14:52:00 -05:00
|
|
|
font: PathfinderFont | null = null;
|
|
|
|
textRun: TextRun | null = null;
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2018-02-19 14:52:00 -05:00
|
|
|
svgLoader!: SVGLoader;
|
2017-12-05 22:04:55 -05:00
|
|
|
|
2018-02-19 14:52:00 -05:00
|
|
|
mode!: BenchmarkMode;
|
2017-12-05 22:04:55 -05:00
|
|
|
|
|
|
|
protected get defaultFile(): string {
|
|
|
|
if (this.mode === 'text')
|
|
|
|
return DEFAULT_FONT;
|
|
|
|
return DEFAULT_SVG_FILE;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected get builtinFileURI(): string {
|
|
|
|
if (this.mode === 'text')
|
|
|
|
return BUILTIN_FONT_URI;
|
|
|
|
return BUILTIN_SVG_URI;
|
|
|
|
}
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2018-02-19 14:52:00 -05:00
|
|
|
private optionsModal!: HTMLDivElement;
|
2017-12-05 01:02:16 -05:00
|
|
|
|
2018-02-19 14:52:00 -05:00
|
|
|
private resultsModal!: HTMLDivElement;
|
|
|
|
private resultsTableHeader!: HTMLTableSectionElement;
|
|
|
|
private resultsTableBody!: HTMLTableSectionElement;
|
|
|
|
private resultsPartitioningTimeLabel!: HTMLSpanElement;
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2018-02-19 14:52:00 -05:00
|
|
|
private glyphStore!: GlyphStore;
|
2018-03-07 17:06:54 -05:00
|
|
|
private baseMeshes!: PathfinderMeshPack;
|
2018-02-19 14:52:00 -05:00
|
|
|
private expandedMeshes!: ExpandedMeshData;
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2018-02-19 14:52:00 -05:00
|
|
|
private size!: number;
|
|
|
|
private currentRun!: number;
|
|
|
|
private startTime!: number;
|
|
|
|
private elapsedTimes!: ElapsedTime[];
|
|
|
|
private partitionTime!: number;
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2017-11-13 18:26:46 -05:00
|
|
|
start(): void {
|
2017-09-11 14:22:19 -04:00
|
|
|
super.start();
|
|
|
|
|
2017-12-05 22:04:55 -05:00
|
|
|
this.mode = 'text';
|
|
|
|
|
2017-12-05 01:02:16 -05:00
|
|
|
this.optionsModal = unwrapNull(document.getElementById('pf-benchmark-modal')) as
|
|
|
|
HTMLDivElement;
|
|
|
|
|
2017-09-25 22:56:12 -04:00
|
|
|
this.resultsModal = unwrapNull(document.getElementById('pf-benchmark-results-modal')) as
|
|
|
|
HTMLDivElement;
|
2017-12-05 22:04:55 -05:00
|
|
|
this.resultsTableHeader =
|
|
|
|
unwrapNull(document.getElementById('pf-benchmark-results-table-header')) as
|
|
|
|
HTMLTableSectionElement;
|
2017-09-25 22:56:12 -04:00
|
|
|
this.resultsTableBody =
|
|
|
|
unwrapNull(document.getElementById('pf-benchmark-results-table-body')) as
|
|
|
|
HTMLTableSectionElement;
|
2017-09-26 18:38:50 -04:00
|
|
|
this.resultsPartitioningTimeLabel =
|
|
|
|
unwrapNull(document.getElementById('pf-benchmark-results-partitioning-time')) as
|
|
|
|
HTMLSpanElement;
|
2017-09-25 22:56:12 -04:00
|
|
|
|
2017-10-05 13:55:52 -04:00
|
|
|
const resultsSaveCSVButton =
|
|
|
|
unwrapNull(document.getElementById('pf-benchmark-results-save-csv-button'));
|
|
|
|
resultsSaveCSVButton.addEventListener('click', () => this.saveCSV(), false);
|
|
|
|
|
2017-09-25 22:56:12 -04:00
|
|
|
const resultsCloseButton =
|
|
|
|
unwrapNull(document.getElementById('pf-benchmark-results-close-button'));
|
|
|
|
resultsCloseButton.addEventListener('click', () => {
|
|
|
|
window.jQuery(this.resultsModal).modal('hide');
|
|
|
|
}, false);
|
|
|
|
|
2017-09-11 14:22:19 -04:00
|
|
|
const runBenchmarkButton = unwrapNull(document.getElementById('pf-run-benchmark-button'));
|
|
|
|
runBenchmarkButton.addEventListener('click', () => this.runBenchmark(), false);
|
|
|
|
|
2017-12-05 22:04:55 -05:00
|
|
|
const aaLevelFormGroup = unwrapNull(document.getElementById('pf-aa-level-form-group')) as
|
|
|
|
HTMLDivElement;
|
|
|
|
const benchmarkTextForm = unwrapNull(document.getElementById('pf-benchmark-text-form')) as
|
|
|
|
HTMLFormElement;
|
|
|
|
const benchmarkSVGForm = unwrapNull(document.getElementById('pf-benchmark-svg-form')) as
|
|
|
|
HTMLFormElement;
|
|
|
|
|
2017-12-05 01:02:16 -05:00
|
|
|
window.jQuery(this.optionsModal).modal();
|
|
|
|
|
2017-12-05 22:04:55 -05:00
|
|
|
const benchmarkTextTab = document.getElementById('pf-benchmark-text-tab') as
|
|
|
|
HTMLAnchorElement;
|
|
|
|
const benchmarkSVGTab = document.getElementById('pf-benchmark-svg-tab') as
|
|
|
|
HTMLAnchorElement;
|
|
|
|
window.jQuery(benchmarkTextTab).on('shown.bs.tab', event => {
|
|
|
|
this.mode = 'text';
|
|
|
|
if (aaLevelFormGroup.parentElement != null)
|
|
|
|
aaLevelFormGroup.parentElement.removeChild(aaLevelFormGroup);
|
|
|
|
benchmarkTextForm.insertBefore(aaLevelFormGroup, benchmarkTextForm.firstChild);
|
2017-12-06 15:19:24 -05:00
|
|
|
this.modeChanged();
|
2017-12-05 22:04:55 -05:00
|
|
|
});
|
|
|
|
window.jQuery(benchmarkSVGTab).on('shown.bs.tab', event => {
|
|
|
|
this.mode = 'svg';
|
|
|
|
if (aaLevelFormGroup.parentElement != null)
|
|
|
|
aaLevelFormGroup.parentElement.removeChild(aaLevelFormGroup);
|
|
|
|
benchmarkSVGForm.insertBefore(aaLevelFormGroup, benchmarkSVGForm.firstChild);
|
2017-12-06 15:19:24 -05:00
|
|
|
this.modeChanged();
|
2017-12-05 22:04:55 -05:00
|
|
|
});
|
|
|
|
|
2017-09-19 23:19:53 -04:00
|
|
|
this.loadInitialFile(this.builtinFileURI);
|
2017-09-11 14:22:19 -04:00
|
|
|
}
|
|
|
|
|
2017-10-02 22:58:38 -04:00
|
|
|
protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
|
2017-12-05 22:04:55 -05:00
|
|
|
switch (this.mode) {
|
|
|
|
case 'text':
|
|
|
|
this.textFileLoaded(fileData, builtinName);
|
|
|
|
return;
|
|
|
|
case 'svg':
|
|
|
|
this.svgFileLoaded(fileData, builtinName);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-09 12:20:27 -05:00
|
|
|
protected createView(areaLUT: HTMLImageElement,
|
|
|
|
gammaLUT: HTMLImageElement,
|
2017-12-05 22:04:55 -05:00
|
|
|
commonShaderSource: string,
|
|
|
|
shaderSources: ShaderMap<ShaderProgramSource>):
|
|
|
|
BenchmarkTestView {
|
2018-03-09 12:20:27 -05:00
|
|
|
return new BenchmarkTestView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources);
|
2017-12-05 22:04:55 -05:00
|
|
|
}
|
|
|
|
|
2017-12-06 15:19:24 -05:00
|
|
|
private modeChanged(): void {
|
|
|
|
this.loadInitialFile(this.builtinFileURI);
|
|
|
|
if (this.aaLevelSelect != null)
|
|
|
|
this.aaLevelSelect.selectedIndex = 0;
|
2018-03-09 12:20:27 -05:00
|
|
|
if (this.subpixelAASelect != null)
|
|
|
|
this.subpixelAASelect.selectedIndex = 0;
|
2017-12-06 15:19:24 -05:00
|
|
|
this.updateAALevel();
|
|
|
|
}
|
|
|
|
|
2017-12-05 22:04:55 -05:00
|
|
|
private textFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
|
2017-10-02 22:58:38 -04:00
|
|
|
const font = new PathfinderFont(fileData, builtinName);
|
2017-09-12 15:40:14 -04:00
|
|
|
this.font = font;
|
2017-09-11 14:22:19 -04:00
|
|
|
|
2017-09-27 16:02:32 -04:00
|
|
|
const textRun = new TextRun(STRING, [0, 0], font);
|
|
|
|
textRun.layout();
|
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-11 14:22:19 -04:00
|
|
|
|
2017-09-27 16:02:32 -04:00
|
|
|
const glyphIDs = textFrame.allGlyphIDs;
|
|
|
|
glyphIDs.sort((a, b) => a - b);
|
|
|
|
this.glyphStore = new GlyphStore(font, glyphIDs);
|
|
|
|
|
|
|
|
this.glyphStore.partition().then(result => {
|
2017-09-26 18:38:50 -04:00
|
|
|
this.baseMeshes = result.meshes;
|
|
|
|
|
2017-09-27 16:02:32 -04:00
|
|
|
const partitionTime = result.time / this.glyphStore.glyphIDs.length * 1e6;
|
2017-09-26 18:38:50 -04:00
|
|
|
const timeLabel = this.resultsPartitioningTimeLabel;
|
|
|
|
while (timeLabel.firstChild != null)
|
|
|
|
timeLabel.removeChild(timeLabel.firstChild);
|
|
|
|
timeLabel.appendChild(document.createTextNode("" + partitionTime));
|
|
|
|
|
2017-09-27 16:02:32 -04:00
|
|
|
const expandedMeshes = textFrame.expandMeshes(this.baseMeshes, glyphIDs);
|
2017-09-12 15:40:14 -04:00
|
|
|
this.expandedMeshes = expandedMeshes;
|
2017-09-26 18:38:50 -04:00
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
this.view.then(view => {
|
2017-12-05 22:04:55 -05:00
|
|
|
view.recreateRenderer();
|
2017-09-12 15:40:14 -04:00
|
|
|
view.attachMeshes([expandedMeshes.meshes]);
|
2017-09-28 17:34:48 -04:00
|
|
|
});
|
|
|
|
});
|
2017-09-11 14:22:19 -04:00
|
|
|
}
|
|
|
|
|
2017-12-05 22:04:55 -05:00
|
|
|
private svgFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void {
|
|
|
|
this.svgLoader = new SVGLoader;
|
|
|
|
this.svgLoader.loadFile(fileData);
|
|
|
|
this.svgLoader.partition().then(meshes => {
|
|
|
|
this.view.then(view => {
|
|
|
|
view.recreateRenderer();
|
2018-03-07 17:06:54 -05:00
|
|
|
view.attachMeshes([new PathfinderPackedMeshes(meshes)]);
|
2018-01-04 17:14:47 -05:00
|
|
|
view.initCameraBounds(this.svgLoader.svgViewBox);
|
2017-12-05 22:04:55 -05:00
|
|
|
});
|
|
|
|
});
|
2017-09-12 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
2017-10-15 17:37:44 -04:00
|
|
|
private reset(): void {
|
2017-10-05 13:55:52 -04:00
|
|
|
this.currentRun = 0;
|
2017-10-15 17:37:44 -04:00
|
|
|
this.startTime = Date.now();
|
|
|
|
}
|
|
|
|
|
|
|
|
private runBenchmark(): void {
|
2017-12-05 01:02:16 -05:00
|
|
|
window.jQuery(this.optionsModal).modal('hide');
|
|
|
|
|
2017-10-15 17:37:44 -04:00
|
|
|
this.reset();
|
2017-09-12 15:40:14 -04:00
|
|
|
this.elapsedTimes = [];
|
2017-12-06 13:58:16 -05:00
|
|
|
this.size = TEST_SIZES[this.mode].start;
|
2017-09-12 15:40:14 -04:00
|
|
|
this.view.then(view => this.runOneBenchmarkTest(view));
|
|
|
|
}
|
|
|
|
|
2017-10-15 17:37:44 -04:00
|
|
|
private runDone(): boolean {
|
|
|
|
const totalElapsedTime = Date.now() - this.startTime;
|
|
|
|
if (totalElapsedTime < MIN_RUNTIME)
|
|
|
|
return false;
|
|
|
|
if (totalElapsedTime >= MAX_RUNTIME)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Compute median absolute devation.
|
|
|
|
const elapsedTime = unwrapUndef(_.last(this.elapsedTimes));
|
|
|
|
elapsedTime.times.sort((a, b) => a - b);
|
|
|
|
const median = unwrapNull(computeMedian(elapsedTime.times));
|
|
|
|
const absoluteDeviations = elapsedTime.times.map(time => Math.abs(time - median));
|
|
|
|
absoluteDeviations.sort((a, b) => a - b);
|
|
|
|
const medianAbsoluteDeviation = unwrapNull(computeMedian(absoluteDeviations));
|
|
|
|
const medianAbsoluteDeviationFraction = medianAbsoluteDeviation / median;
|
|
|
|
return medianAbsoluteDeviationFraction <= 0.01;
|
|
|
|
}
|
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
private runOneBenchmarkTest(view: BenchmarkTestView): void {
|
|
|
|
const renderedPromise = new Promise<number>((resolve, reject) => {
|
|
|
|
view.renderingPromiseCallback = resolve;
|
2017-12-06 13:58:16 -05:00
|
|
|
view.size = this.size;
|
2017-09-12 15:40:14 -04:00
|
|
|
});
|
|
|
|
renderedPromise.then(elapsedTime => {
|
2017-10-05 13:55:52 -04:00
|
|
|
if (this.currentRun === 0)
|
2017-12-06 13:58:16 -05:00
|
|
|
this.elapsedTimes.push(new ElapsedTime(this.size));
|
2017-10-05 13:55:52 -04:00
|
|
|
unwrapUndef(_.last(this.elapsedTimes)).times.push(elapsedTime);
|
|
|
|
|
|
|
|
this.currentRun++;
|
2017-10-15 17:37:44 -04:00
|
|
|
if (this.runDone()) {
|
|
|
|
this.reset();
|
2017-10-05 13:55:52 -04:00
|
|
|
|
2017-12-06 13:58:16 -05:00
|
|
|
if (this.size >= TEST_SIZES[this.mode].stop) {
|
2017-10-05 13:55:52 -04:00
|
|
|
this.showResults();
|
|
|
|
return;
|
|
|
|
}
|
2017-09-12 15:40:14 -04:00
|
|
|
|
2017-12-06 13:58:16 -05:00
|
|
|
this.size += TEST_SIZES[this.mode].step;
|
2017-09-12 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
this.runOneBenchmarkTest(view);
|
|
|
|
});
|
2017-09-11 14:22:19 -04:00
|
|
|
}
|
|
|
|
|
2017-09-25 22:56:12 -04:00
|
|
|
private showResults(): void {
|
2017-12-05 22:04:55 -05:00
|
|
|
while (this.resultsTableHeader.lastChild != null)
|
|
|
|
this.resultsTableHeader.removeChild(this.resultsTableHeader.lastChild);
|
2017-09-25 22:56:12 -04:00
|
|
|
while (this.resultsTableBody.lastChild != null)
|
|
|
|
this.resultsTableBody.removeChild(this.resultsTableBody.lastChild);
|
|
|
|
|
2017-12-05 22:04:55 -05:00
|
|
|
const tr = document.createElement('tr');
|
|
|
|
for (const headerLabel of DISPLAY_HEADER_LABELS[this.mode]) {
|
|
|
|
const th = document.createElement('th');
|
|
|
|
th.appendChild(document.createTextNode(headerLabel));
|
|
|
|
tr.appendChild(th);
|
|
|
|
}
|
|
|
|
this.resultsTableHeader.appendChild(tr);
|
|
|
|
|
2017-09-25 22:56:12 -04:00
|
|
|
for (const elapsedTime of this.elapsedTimes) {
|
|
|
|
const tr = document.createElement('tr');
|
|
|
|
const sizeTH = document.createElement('th');
|
|
|
|
const timeTD = document.createElement('td');
|
|
|
|
sizeTH.appendChild(document.createTextNode("" + elapsedTime.size));
|
2017-12-05 22:04:55 -05:00
|
|
|
const time = this.mode === 'svg' ? elapsedTime.timeInMS : elapsedTime.time;
|
|
|
|
timeTD.appendChild(document.createTextNode("" + time));
|
2017-09-25 22:56:12 -04:00
|
|
|
sizeTH.scope = 'row';
|
|
|
|
tr.appendChild(sizeTH);
|
|
|
|
tr.appendChild(timeTD);
|
|
|
|
this.resultsTableBody.appendChild(tr);
|
|
|
|
}
|
|
|
|
|
|
|
|
window.jQuery(this.resultsModal).modal();
|
|
|
|
}
|
2017-10-05 13:55:52 -04:00
|
|
|
|
|
|
|
private saveCSV(): void {
|
2017-12-06 13:58:16 -05:00
|
|
|
let output = "Size,Time\n";
|
|
|
|
for (const elapsedTime of this.elapsedTimes) {
|
|
|
|
const time = this.mode === 'svg' ? elapsedTime.timeInMS : elapsedTime.time;
|
|
|
|
output += `${elapsedTime.size},${time}\n`;
|
|
|
|
}
|
2017-10-05 13:55:52 -04:00
|
|
|
|
|
|
|
// https://stackoverflow.com/a/30832210
|
|
|
|
const file = new Blob([output], {type: 'text/csv'});
|
|
|
|
const a = document.createElement('a');
|
|
|
|
const url = URL.createObjectURL(file);
|
|
|
|
a.href = url;
|
|
|
|
a.download = "pathfinder-benchmark-results.csv";
|
|
|
|
document.body.appendChild(a);
|
|
|
|
a.click();
|
|
|
|
|
|
|
|
window.setTimeout(() => {
|
|
|
|
document.body.removeChild(a);
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
}, 0);
|
|
|
|
}
|
2017-09-28 17:34:48 -04:00
|
|
|
}
|
2017-09-25 22:56:12 -04:00
|
|
|
|
2017-10-16 19:11:00 -04:00
|
|
|
class BenchmarkTestView extends DemoView {
|
2018-02-19 14:52:00 -05:00
|
|
|
renderer!: BenchmarkTextRenderer | BenchmarkSVGRenderer;
|
2017-12-05 22:04:55 -05:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
readonly appController: BenchmarkAppController;
|
|
|
|
|
2018-02-19 14:52:00 -05:00
|
|
|
renderingPromiseCallback: ((time: number) => void) | null = null;
|
2017-12-05 01:02:16 -05:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
get camera(): OrthographicCamera {
|
|
|
|
return this.renderer.camera;
|
|
|
|
}
|
|
|
|
|
2017-12-06 13:58:16 -05:00
|
|
|
set size(newSize: number) {
|
2017-12-05 22:04:55 -05:00
|
|
|
if (this.renderer instanceof BenchmarkTextRenderer) {
|
2017-12-06 13:58:16 -05:00
|
|
|
this.renderer.pixelsPerEm = newSize;
|
2017-12-05 22:04:55 -05:00
|
|
|
} else if (this.renderer instanceof BenchmarkSVGRenderer) {
|
|
|
|
const camera = this.renderer.camera;
|
2017-12-06 13:58:16 -05:00
|
|
|
camera.zoomToSize(newSize);
|
2017-12-05 22:04:55 -05:00
|
|
|
camera.center();
|
|
|
|
}
|
2017-10-16 22:29:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
constructor(appController: BenchmarkAppController,
|
2018-03-09 12:20:27 -05:00
|
|
|
areaLUT: HTMLImageElement,
|
2017-11-07 17:13:13 -05:00
|
|
|
gammaLUT: HTMLImageElement,
|
2017-10-16 22:29:13 -04:00
|
|
|
commonShaderSource: string,
|
|
|
|
shaderSources: ShaderMap<ShaderProgramSource>) {
|
2018-03-09 12:20:27 -05:00
|
|
|
super(areaLUT, gammaLUT, commonShaderSource, shaderSources);
|
2017-10-16 22:29:13 -04:00
|
|
|
this.appController = appController;
|
2017-12-05 22:04:55 -05:00
|
|
|
this.recreateRenderer();
|
2017-10-16 22:29:13 -04:00
|
|
|
this.resizeToFit(true);
|
|
|
|
}
|
2017-12-05 01:02:16 -05:00
|
|
|
|
2017-12-05 22:04:55 -05:00
|
|
|
recreateRenderer(): void {
|
|
|
|
switch (this.appController.mode) {
|
|
|
|
case 'svg':
|
|
|
|
this.renderer = new BenchmarkSVGRenderer(this);
|
|
|
|
break;
|
|
|
|
case 'text':
|
|
|
|
this.renderer = new BenchmarkTextRenderer(this);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-04 17:14:47 -05:00
|
|
|
initCameraBounds(viewBox: glmatrix.vec4): void {
|
2017-12-05 22:04:55 -05:00
|
|
|
if (this.renderer instanceof BenchmarkSVGRenderer)
|
2018-01-04 17:14:47 -05:00
|
|
|
this.renderer.initCameraBounds(viewBox);
|
2017-12-05 22:04:55 -05:00
|
|
|
}
|
|
|
|
|
2017-12-05 01:02:16 -05:00
|
|
|
protected renderingFinished(): void {
|
|
|
|
if (this.renderingPromiseCallback == null)
|
|
|
|
return;
|
|
|
|
|
2017-12-05 22:04:55 -05:00
|
|
|
const appController = this.appController;
|
|
|
|
let time = this.renderer.lastTimings.rendering * 1000.0;
|
|
|
|
if (appController.mode === 'text')
|
|
|
|
time /= unwrapNull(appController.textRun).glyphIDs.length;
|
|
|
|
this.renderingPromiseCallback(time);
|
2017-12-05 01:02:16 -05:00
|
|
|
}
|
2017-10-16 22:29:13 -04:00
|
|
|
}
|
|
|
|
|
2017-12-05 22:08:31 -05:00
|
|
|
class BenchmarkTextRenderer extends Renderer {
|
2018-02-19 14:52:00 -05:00
|
|
|
renderContext!: BenchmarkTestView;
|
2017-10-16 22:29:13 -04:00
|
|
|
|
|
|
|
camera: OrthographicCamera;
|
2017-09-11 14:22:19 -04:00
|
|
|
|
2018-02-05 18:10:52 -05:00
|
|
|
needsStencil: boolean = false;
|
|
|
|
isMulticolor: boolean = false;
|
2018-01-02 22:15:19 -05:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
get destFramebuffer(): WebGLFramebuffer | null {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-10-16 19:07:43 -04:00
|
|
|
get bgColor(): glmatrix.vec4 {
|
|
|
|
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 0.0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
get fgColor(): glmatrix.vec4 {
|
|
|
|
return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
|
|
|
|
}
|
2017-09-12 15:40:14 -04:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
get destAllocatedSize(): glmatrix.vec2 {
|
|
|
|
const canvas = this.renderContext.canvas;
|
|
|
|
return glmatrix.vec2.clone([canvas.width, canvas.height]);
|
|
|
|
}
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
get destUsedSize(): glmatrix.vec2 {
|
|
|
|
return this.destAllocatedSize;
|
|
|
|
}
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2018-03-09 12:20:27 -05:00
|
|
|
get allowSubpixelAA(): boolean {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
get emboldenAmount(): glmatrix.vec2 {
|
|
|
|
return this.stemDarkeningAmount;
|
|
|
|
}
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
get pixelsPerEm(): number {
|
|
|
|
return this._pixelsPerEm;
|
|
|
|
}
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
set pixelsPerEm(newPixelsPerEm: number) {
|
|
|
|
this._pixelsPerEm = newPixelsPerEm;
|
|
|
|
this.uploadPathTransforms(1);
|
|
|
|
this.renderContext.setDirty();
|
|
|
|
}
|
2017-09-28 17:34:48 -04:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
protected get usedSizeFactor(): glmatrix.vec2 {
|
|
|
|
return glmatrix.vec2.clone([1.0, 1.0]);
|
|
|
|
}
|
2017-09-12 15:40:14 -04:00
|
|
|
|
2018-01-04 17:14:47 -05:00
|
|
|
protected get worldTransform(): glmatrix.mat4 {
|
2017-10-16 22:29:13 -04:00
|
|
|
const canvas = this.renderContext.canvas;
|
|
|
|
|
|
|
|
const transform = glmatrix.mat4.create();
|
|
|
|
const translation = this.camera.translation;
|
|
|
|
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
|
|
|
|
glmatrix.mat4.scale(transform, transform, [2.0 / canvas.width, 2.0 / canvas.height, 1.0]);
|
|
|
|
glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
|
|
|
|
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
|
|
|
|
|
|
|
|
const pixelsPerUnit = this.pixelsPerUnit;
|
|
|
|
glmatrix.mat4.scale(transform, transform, [pixelsPerUnit, pixelsPerUnit, 1.0]);
|
|
|
|
|
|
|
|
return transform;
|
|
|
|
}
|
|
|
|
|
2017-11-01 19:09:58 -04:00
|
|
|
protected get objectCount(): number {
|
2018-03-07 17:06:54 -05:00
|
|
|
return this.meshBuffers == null ? 0 : this.meshBuffers.length;
|
2017-11-01 19:09:58 -04:00
|
|
|
}
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
private _pixelsPerEm: number = 32.0;
|
|
|
|
|
|
|
|
private get pixelsPerUnit(): number {
|
|
|
|
const font = unwrapNull(this.renderContext.appController.font);
|
|
|
|
return this._pixelsPerEm / font.opentypeFont.unitsPerEm;
|
2017-10-09 17:14:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
private get stemDarkeningAmount(): glmatrix.vec2 {
|
|
|
|
return computeStemDarkeningAmount(this._pixelsPerEm, this.pixelsPerUnit);
|
|
|
|
}
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
constructor(renderContext: BenchmarkTestView) {
|
|
|
|
super(renderContext);
|
2017-09-12 15:40:14 -04:00
|
|
|
|
2018-01-18 20:26:56 -05:00
|
|
|
this.camera = new OrthographicCamera(renderContext.canvas, { fixed: true });
|
2017-10-16 22:29:13 -04:00
|
|
|
this.camera.onPan = () => renderContext.setDirty();
|
|
|
|
this.camera.onZoom = () => renderContext.setDirty();
|
|
|
|
}
|
2017-09-12 15:40:14 -04:00
|
|
|
|
2018-03-07 17:06:54 -05:00
|
|
|
attachMeshes(meshes: PathfinderPackedMeshes[]): void {
|
2017-10-16 22:29:13 -04:00
|
|
|
super.attachMeshes(meshes);
|
|
|
|
|
|
|
|
this.uploadPathColors(1);
|
|
|
|
this.uploadPathTransforms(1);
|
2017-09-12 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
pathCountForObject(objectIndex: number): number {
|
|
|
|
return STRING.length;
|
2017-09-28 17:34:48 -04:00
|
|
|
}
|
|
|
|
|
2017-10-09 17:14:24 -04:00
|
|
|
pathBoundingRects(objectIndex: number): Float32Array {
|
2017-10-16 22:29:13 -04:00
|
|
|
const appController = this.renderContext.appController;
|
|
|
|
const font = unwrapNull(appController.font);
|
2017-10-09 17:14:24 -04:00
|
|
|
|
|
|
|
const boundingRects = new Float32Array((STRING.length + 1) * 4);
|
|
|
|
|
|
|
|
for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
|
2017-10-16 22:29:13 -04:00
|
|
|
const glyphID = unwrapNull(appController.textRun).glyphIDs[glyphIndex];
|
2017-10-09 17:14:24 -04:00
|
|
|
|
|
|
|
const metrics = font.metricsForGlyph(glyphID);
|
|
|
|
if (metrics == null)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
boundingRects[(glyphIndex + 1) * 4 + 0] = metrics.xMin;
|
|
|
|
boundingRects[(glyphIndex + 1) * 4 + 1] = metrics.yMin;
|
|
|
|
boundingRects[(glyphIndex + 1) * 4 + 2] = metrics.xMax;
|
|
|
|
boundingRects[(glyphIndex + 1) * 4 + 3] = metrics.yMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
return boundingRects;
|
|
|
|
}
|
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
setHintsUniform(uniforms: UniformMap): void {
|
|
|
|
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
|
2017-10-09 17:14:24 -04:00
|
|
|
}
|
|
|
|
|
2018-03-09 12:20:27 -05:00
|
|
|
pathTransformsForObject(objectIndex: number): PathTransformBuffers<Float32Array> {
|
2017-10-16 22:29:13 -04:00
|
|
|
const appController = this.renderContext.appController;
|
|
|
|
const canvas = this.renderContext.canvas;
|
|
|
|
const font = unwrapNull(appController.font);
|
|
|
|
|
2017-11-29 13:50:47 -05:00
|
|
|
const pathTransforms = this.createPathTransformBuffers(STRING.length);
|
2017-09-12 19:07:04 -04:00
|
|
|
|
|
|
|
let currentX = 0, currentY = 0;
|
2017-10-16 22:29:13 -04:00
|
|
|
const availableWidth = canvas.width / this.pixelsPerUnit;
|
|
|
|
const lineHeight = font.opentypeFont.lineHeight();
|
2017-09-12 15:40:14 -04:00
|
|
|
|
|
|
|
for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
|
2017-10-16 22:29:13 -04:00
|
|
|
const glyphID = unwrapNull(appController.textRun).glyphIDs[glyphIndex];
|
2017-11-29 13:50:47 -05:00
|
|
|
pathTransforms.st.set([1, 1, currentX, currentY], (glyphIndex + 1) * 4);
|
2017-09-12 19:07:04 -04:00
|
|
|
|
2017-10-16 22:29:13 -04:00
|
|
|
currentX += font.opentypeFont.glyphs.get(glyphID).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;
|
|
|
|
}
|
2017-10-30 16:34:55 -04:00
|
|
|
|
2018-03-09 12:20:27 -05:00
|
|
|
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
|
|
|
aaLevel: number,
|
|
|
|
subpixelAA: SubpixelAAType):
|
|
|
|
AntialiasingStrategy {
|
|
|
|
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-10-31 15:41:38 -04:00
|
|
|
protected directCurveProgramName(): keyof ShaderMap<void> {
|
2017-10-30 16:34:55 -04:00
|
|
|
return 'directCurve';
|
|
|
|
}
|
|
|
|
|
2017-10-31 15:41:38 -04:00
|
|
|
protected directInteriorProgramName(): keyof ShaderMap<void> {
|
2017-10-30 16:34:55 -04:00
|
|
|
return 'directInterior';
|
|
|
|
}
|
2017-09-11 14:22:19 -04:00
|
|
|
}
|
|
|
|
|
2017-12-05 22:04:55 -05:00
|
|
|
class BenchmarkSVGRenderer extends SVGRenderer {
|
2018-02-19 14:52:00 -05:00
|
|
|
renderContext!: BenchmarkTestView;
|
2017-12-05 22:04:55 -05:00
|
|
|
|
|
|
|
protected get loader(): SVGLoader {
|
|
|
|
return this.renderContext.appController.svgLoader;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected get canvas(): HTMLCanvasElement {
|
|
|
|
return this.renderContext.canvas;
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(renderContext: BenchmarkTestView) {
|
2018-01-04 17:14:47 -05:00
|
|
|
super(renderContext, {sizeToFit: false});
|
2017-12-05 22:04:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-15 17:37:44 -04:00
|
|
|
function computeMedian(values: number[]): number | null {
|
|
|
|
if (values.length === 0)
|
|
|
|
return null;
|
|
|
|
const mid = values.length / 2;
|
|
|
|
if (values.length % 2 === 1)
|
|
|
|
return values[Math.floor(mid)];
|
|
|
|
return lerp(values[mid - 1], values[mid], 0.5);
|
|
|
|
}
|
|
|
|
|
2017-10-05 13:55:52 -04:00
|
|
|
class ElapsedTime {
|
|
|
|
readonly size: number;
|
|
|
|
readonly times: number[];
|
|
|
|
|
|
|
|
constructor(size: number) {
|
|
|
|
this.size = size;
|
|
|
|
this.times = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
get time(): number {
|
2017-10-15 17:37:44 -04:00
|
|
|
const median = computeMedian(this.times);
|
|
|
|
return median == null ? 0.0 : median;
|
2017-10-05 13:55:52 -04:00
|
|
|
}
|
2017-12-05 22:04:55 -05:00
|
|
|
|
|
|
|
get timeInMS(): number {
|
|
|
|
return this.time / 1000.0;
|
|
|
|
}
|
2017-10-05 13:55:52 -04:00
|
|
|
}
|
|
|
|
|
2017-09-12 15:40:14 -04:00
|
|
|
function main() {
|
|
|
|
const controller = new BenchmarkAppController;
|
|
|
|
window.addEventListener('load', () => controller.start(), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
main();
|