Get a bare-minimum benchmark up and running
This commit is contained in:
parent
a7d75f913c
commit
2c8c11b303
|
@ -8,6 +8,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{{>partials/navbar.html isTool=true}}
|
{{>partials/navbar.html isTool=true}}
|
||||||
|
<canvas id="pf-canvas" class="pf-draggable" width="400" height="300"></canvas>
|
||||||
<div class="fixed-bottom mb-3 d-flex justify-content-end align-items-end pf-pointer-events-none">
|
<div class="fixed-bottom mb-3 d-flex justify-content-end align-items-end pf-pointer-events-none">
|
||||||
<div id="pf-toolbar">
|
<div id="pf-toolbar">
|
||||||
<button id="pf-run-benchmark-button" type="button"
|
<button id="pf-run-benchmark-button" type="button"
|
||||||
|
|
|
@ -168,7 +168,8 @@ class ThreeDController extends DemoAppController<ThreeDView> {
|
||||||
this.baseMeshes = baseMeshes;
|
this.baseMeshes = baseMeshes;
|
||||||
this.expandedMeshes = this.glyphStorage.expandMeshes(baseMeshes);
|
this.expandedMeshes = this.glyphStorage.expandMeshes(baseMeshes);
|
||||||
this.view.then(view => {
|
this.view.then(view => {
|
||||||
view.uploadPathMetadata();
|
view.uploadPathColors(this.expandedMeshes.length);
|
||||||
|
view.uploadPathTransforms(this.expandedMeshes.length);
|
||||||
view.attachMeshes(this.expandedMeshes.map(meshes => meshes.meshes));
|
view.attachMeshes(this.expandedMeshes.map(meshes => meshes.meshes));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -216,41 +217,34 @@ class ThreeDView extends PathfinderDemoView {
|
||||||
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, CUBE_INDICES, this.gl.STATIC_DRAW);
|
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, CUBE_INDICES, this.gl.STATIC_DRAW);
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadPathMetadata() {
|
protected pathColorsForObject(textFrameIndex: number): Uint8Array {
|
||||||
this.pathColorsBufferTextures = [];
|
const textFrame = this.appController.glyphStorage.textFrames[textFrameIndex];
|
||||||
this.pathTransformBufferTextures = [];
|
const textGlyphs = textFrame.allGlyphs;
|
||||||
|
const pathCount = textGlyphs.length;
|
||||||
|
|
||||||
const textFrameCount = this.appController.glyphStorage.textFrames.length;
|
const pathColors = new Uint8Array(4 * (pathCount + 1));
|
||||||
for (let textFrameIndex = 0;
|
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++)
|
||||||
textFrameIndex < textFrameCount;
|
pathColors.set(TEXT_COLOR, (pathIndex + 1) * 4);
|
||||||
textFrameIndex++) {
|
|
||||||
|
return pathColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected pathTransformsForObject(textFrameIndex: number): Float32Array {
|
||||||
const textFrame = this.appController.glyphStorage.textFrames[textFrameIndex];
|
const textFrame = this.appController.glyphStorage.textFrames[textFrameIndex];
|
||||||
const textGlyphs = textFrame.allGlyphs;
|
const textGlyphs = textFrame.allGlyphs;
|
||||||
const pathCount = textGlyphs.length;
|
const pathCount = textGlyphs.length;
|
||||||
|
|
||||||
const hint = new Hint(this.appController.glyphStorage.font, PIXELS_PER_UNIT, false);
|
const hint = new Hint(this.appController.glyphStorage.font, PIXELS_PER_UNIT, false);
|
||||||
|
|
||||||
const pathColors = new Uint8Array(4 * (pathCount + 1));
|
|
||||||
const pathTransforms = new Float32Array(4 * (pathCount + 1));
|
const pathTransforms = new Float32Array(4 * (pathCount + 1));
|
||||||
|
|
||||||
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
|
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
|
||||||
const startOffset = (pathIndex + 1) * 4;
|
|
||||||
|
|
||||||
pathColors.set(TEXT_COLOR, startOffset);
|
|
||||||
|
|
||||||
const textGlyph = textGlyphs[pathIndex];
|
const textGlyph = textGlyphs[pathIndex];
|
||||||
const glyphOrigin = textGlyph.calculatePixelOrigin(hint, PIXELS_PER_UNIT);
|
const glyphOrigin = textGlyph.calculatePixelOrigin(hint, PIXELS_PER_UNIT);
|
||||||
pathTransforms.set([1, 1, glyphOrigin[0], glyphOrigin[1]], startOffset);
|
pathTransforms.set([1, 1, glyphOrigin[0], glyphOrigin[1]], (pathIndex + 1) * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathColorsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathColors');
|
return pathTransforms;
|
||||||
const pathTransformBufferTexture = new PathfinderBufferTexture(this.gl,
|
|
||||||
'uPathTransform');
|
|
||||||
pathColorsBufferTexture.upload(this.gl, pathColors);
|
|
||||||
pathTransformBufferTexture.upload(this.gl, pathTransforms);
|
|
||||||
this.pathColorsBufferTextures.push(pathColorsBufferTexture);
|
|
||||||
this.pathTransformBufferTextures.push(pathTransformBufferTexture);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
||||||
|
@ -304,17 +298,13 @@ class ThreeDView extends PathfinderDemoView {
|
||||||
return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height);
|
return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
get destFramebuffer(): WebGLFramebuffer | null {
|
destFramebuffer: WebGLFramebuffer | null = null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get destUsedSize(): glmatrix.vec2 {
|
get destUsedSize(): glmatrix.vec2 {
|
||||||
return this.destAllocatedSize;
|
return this.destAllocatedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get usedSizeFactor(): glmatrix.vec2 {
|
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.clone([1.0, 1.0]);
|
||||||
return glmatrix.vec2.fromValues(1.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private calculateWorldTransform(modelviewTranslation: glmatrix.vec3,
|
private calculateWorldTransform(modelviewTranslation: glmatrix.vec3,
|
||||||
modelviewScale: glmatrix.vec3):
|
modelviewScale: glmatrix.vec3):
|
||||||
|
@ -355,13 +345,8 @@ class ThreeDView extends PathfinderDemoView {
|
||||||
return transform;
|
return transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get directCurveProgramName(): keyof ShaderMap<void> {
|
protected directCurveProgramName: keyof ShaderMap<void> = 'direct3DCurve';
|
||||||
return 'direct3DCurve';
|
protected directInteriorProgramName: keyof ShaderMap<void> = 'direct3DInterior';
|
||||||
}
|
|
||||||
|
|
||||||
protected get directInteriorProgramName(): keyof ShaderMap<void> {
|
|
||||||
return 'direct3DInterior';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected depthFunction: number = this.gl.LESS;
|
protected depthFunction: number = this.gl.LESS;
|
||||||
|
|
||||||
|
|
|
@ -58,22 +58,29 @@ export abstract class DemoAppController<View extends PathfinderDemoView> extends
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
this.settingsCard = document.getElementById('pf-settings') as HTMLElement;
|
const settingsCard = document.getElementById('pf-settings') as (HTMLElement | null);
|
||||||
this.settingsButton = document.getElementById('pf-settings-button') as HTMLButtonElement;
|
const settingsButton = document.getElementById('pf-settings-button') as
|
||||||
this.settingsCloseButton = document.getElementById('pf-settings-close-button') as
|
(HTMLButtonElement | null);
|
||||||
HTMLButtonElement;
|
const settingsCloseButton = document.getElementById('pf-settings-close-button') as
|
||||||
|
(HTMLButtonElement | null);
|
||||||
|
|
||||||
this.settingsButton.addEventListener('click', event => {
|
if (settingsButton != null) {
|
||||||
|
settingsButton.addEventListener('click', event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.settingsCard.classList.toggle('pf-invisible');
|
unwrapNull(settingsCard).classList.toggle('pf-invisible');
|
||||||
}, false);
|
}, false);
|
||||||
this.settingsCloseButton.addEventListener('click', () => {
|
}
|
||||||
this.settingsCard.classList.add('pf-invisible');
|
if (settingsCloseButton != null) {
|
||||||
|
settingsCloseButton.addEventListener('click', () => {
|
||||||
|
unwrapNull(settingsCard).classList.add('pf-invisible');
|
||||||
}, false);
|
}, false);
|
||||||
|
}
|
||||||
|
if (settingsCard != null) {
|
||||||
document.body.addEventListener('click', () => {
|
document.body.addEventListener('click', () => {
|
||||||
this.settingsCard.classList.add('pf-invisible');
|
settingsCard.classList.add('pf-invisible');
|
||||||
}, false);
|
}, false);
|
||||||
this.settingsCard.addEventListener('click', event => event.stopPropagation(), false);
|
settingsCard.addEventListener('click', event => event.stopPropagation(), false);
|
||||||
|
}
|
||||||
|
|
||||||
const screenshotButton = document.getElementById('pf-screenshot-button') as
|
const screenshotButton = document.getElementById('pf-screenshot-button') as
|
||||||
HTMLButtonElement | null;
|
HTMLButtonElement | null;
|
||||||
|
@ -124,20 +131,31 @@ export abstract class DemoAppController<View extends PathfinderDemoView> extends
|
||||||
return this.createView();
|
return this.createView();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.aaLevelSelect = document.getElementById('pf-aa-level-select') as HTMLSelectElement;
|
this.aaLevelSelect = document.getElementById('pf-aa-level-select') as
|
||||||
|
(HTMLSelectElement | null);
|
||||||
|
if (this.aaLevelSelect != null)
|
||||||
|
this.aaLevelSelect.addEventListener('change', () => this.updateAALevel(), false);
|
||||||
|
|
||||||
this.subpixelAASwitch =
|
this.subpixelAASwitch =
|
||||||
document.getElementById('pf-subpixel-aa') as HTMLInputElement | null;
|
document.getElementById('pf-subpixel-aa') as HTMLInputElement | null;
|
||||||
this.aaLevelSelect.addEventListener('change', () => this.updateAALevel(), false);
|
|
||||||
if (this.subpixelAASwitch != null)
|
if (this.subpixelAASwitch != null)
|
||||||
this.subpixelAASwitch.addEventListener('change', () => this.updateAALevel(), false);
|
this.subpixelAASwitch.addEventListener('change', () => this.updateAALevel(), false);
|
||||||
|
|
||||||
this.updateAALevel();
|
this.updateAALevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateAALevel() {
|
private updateAALevel() {
|
||||||
|
let aaType: AntialiasingStrategyName, aaLevel: number;
|
||||||
|
if (this.aaLevelSelect != null) {
|
||||||
const selectedOption = this.aaLevelSelect.selectedOptions[0];
|
const selectedOption = this.aaLevelSelect.selectedOptions[0];
|
||||||
const aaValues = unwrapNull(/^([a-z-]+)(?:-([0-9]+))?$/.exec(selectedOption.value));
|
const aaValues = unwrapNull(/^([a-z-]+)(?:-([0-9]+))?$/.exec(selectedOption.value));
|
||||||
const aaType = aaValues[1] as AntialiasingStrategyName;
|
aaType = aaValues[1] as AntialiasingStrategyName;
|
||||||
const aaLevel = aaValues[2] === "" ? 1 : parseInt(aaValues[2]);
|
aaLevel = aaValues[2] === "" ? 1 : parseInt(aaValues[2]);
|
||||||
|
} else {
|
||||||
|
aaType = 'none';
|
||||||
|
aaLevel = 0;
|
||||||
|
}
|
||||||
|
|
||||||
const subpixelAA = this.subpixelAASwitch == null ? false : this.subpixelAASwitch.checked;
|
const subpixelAA = this.subpixelAASwitch == null ? false : this.subpixelAASwitch.checked;
|
||||||
this.view.then(view => view.setAntialiasingOptions(aaType, aaLevel, subpixelAA));
|
this.view.then(view => view.setAntialiasingOptions(aaType, aaLevel, subpixelAA));
|
||||||
}
|
}
|
||||||
|
@ -187,10 +205,6 @@ export abstract class DemoAppController<View extends PathfinderDemoView> extends
|
||||||
protected commonShaderSource: string | null;
|
protected commonShaderSource: string | null;
|
||||||
protected shaderSources: ShaderMap<ShaderProgramSource> | null;
|
protected shaderSources: ShaderMap<ShaderProgramSource> | null;
|
||||||
|
|
||||||
private aaLevelSelect: HTMLSelectElement;
|
private aaLevelSelect: HTMLSelectElement | null;
|
||||||
private subpixelAASwitch: HTMLInputElement | null;
|
private subpixelAASwitch: HTMLInputElement | null;
|
||||||
|
|
||||||
private settingsCard: HTMLElement;
|
|
||||||
private settingsButton: HTMLButtonElement;
|
|
||||||
private settingsCloseButton: HTMLButtonElement;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,18 +8,44 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
|
import * as glmatrix from 'gl-matrix';
|
||||||
import * as opentype from "opentype.js";
|
import * as opentype from "opentype.js";
|
||||||
|
|
||||||
import {AppController} from "./app-controller";
|
import { AppController, DemoAppController } from "./app-controller";
|
||||||
import {PathfinderMeshData} from "./meshes";
|
import {PathfinderMeshData} from "./meshes";
|
||||||
import {BUILTIN_FONT_URI, GlyphStorage, PathfinderGlyph, TextFrame, TextRun} from "./text";
|
import { BUILTIN_FONT_URI, GlyphStorage, PathfinderGlyph, TextFrame, TextRun, ExpandedMeshData } from "./text";
|
||||||
import {assert, unwrapNull} from "./utils";
|
import { assert, unwrapNull, PathfinderError } from "./utils";
|
||||||
|
import { PathfinderDemoView, Timings } from "./view";
|
||||||
|
import { ShaderMap, ShaderProgramSource } from "./shader-loader";
|
||||||
|
import { AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy } from "./aa-strategy";
|
||||||
|
import SSAAStrategy from './ssaa-strategy';
|
||||||
|
import { OrthographicCamera } from './camera';
|
||||||
|
|
||||||
const STRING: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
const STRING: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
||||||
const FONT: string = 'nimbus-sans';
|
const FONT: string = 'nimbus-sans';
|
||||||
|
|
||||||
class BenchmarkAppController extends AppController {
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ElapsedTime {
|
||||||
|
size: number;
|
||||||
|
time: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AntialiasingStrategyTable {
|
||||||
|
none: typeof NoAAStrategy;
|
||||||
|
ssaa: typeof SSAAStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
|
@ -31,29 +57,176 @@ class BenchmarkAppController extends AppController {
|
||||||
|
|
||||||
protected fileLoaded(): void {
|
protected fileLoaded(): void {
|
||||||
const font = opentype.parse(this.fileData);
|
const font = opentype.parse(this.fileData);
|
||||||
assert(font.isSupported(), "The font type is unsupported!");
|
this.font = font;
|
||||||
|
assert(this.font.isSupported(), "The font type is unsupported!");
|
||||||
|
|
||||||
const createGlyph = (glyph: opentype.Glyph) => new BenchmarkGlyph(glyph);
|
const createGlyph = (glyph: opentype.Glyph) => new BenchmarkGlyph(glyph);
|
||||||
const textRun = new TextRun<BenchmarkGlyph>(STRING, [0, 0], font, createGlyph);
|
const textRun = new TextRun<BenchmarkGlyph>(STRING, [0, 0], font, createGlyph);
|
||||||
|
this.textRun = textRun;
|
||||||
const textFrame = new TextFrame([textRun], font);
|
const textFrame = new TextFrame([textRun], font);
|
||||||
this.glyphStorage = new GlyphStorage(this.fileData, [textFrame], createGlyph, font);
|
this.glyphStorage = new GlyphStorage(this.fileData, [textFrame], createGlyph, font);
|
||||||
|
|
||||||
this.glyphStorage.partition().then(meshes => {
|
this.glyphStorage.partition().then(baseMeshes => {
|
||||||
this.meshes = meshes;
|
this.baseMeshes = baseMeshes;
|
||||||
// TODO(pcwalton)
|
const expandedMeshes = this.glyphStorage.expandMeshes(baseMeshes)[0];
|
||||||
// this.renderer.attachMeshes();
|
this.expandedMeshes = expandedMeshes;
|
||||||
|
this.view.then(view => {
|
||||||
|
view.uploadPathColors(1);
|
||||||
|
view.uploadPathTransforms(1);
|
||||||
|
view.attachMeshes([expandedMeshes.meshes]);
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected createView(): BenchmarkTestView {
|
||||||
|
return new BenchmarkTestView(this,
|
||||||
|
unwrapNull(this.commonShaderSource),
|
||||||
|
unwrapNull(this.shaderSources));
|
||||||
|
}
|
||||||
|
|
||||||
private runBenchmark(): void {
|
private runBenchmark(): void {
|
||||||
// TODO(pcwalton)
|
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly defaultFile: string = FONT;
|
protected readonly defaultFile: string = FONT;
|
||||||
protected readonly builtinFileURI: string = BUILTIN_FONT_URI;
|
protected readonly builtinFileURI: string = BUILTIN_FONT_URI;
|
||||||
|
|
||||||
private glyphStorage: GlyphStorage<BenchmarkGlyph>;
|
private glyphStorage: GlyphStorage<BenchmarkGlyph>;
|
||||||
private meshes: PathfinderMeshData;
|
private baseMeshes: PathfinderMeshData;
|
||||||
|
private expandedMeshes: ExpandedMeshData;
|
||||||
|
|
||||||
|
private pixelsPerEm: number;
|
||||||
|
private elapsedTimes: ElapsedTime[];
|
||||||
|
|
||||||
|
font: opentype.Font | null;
|
||||||
|
textRun: TextRun<BenchmarkGlyph> | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BenchmarkTestView extends PathfinderDemoView {
|
||||||
|
constructor(appController: BenchmarkAppController,
|
||||||
|
commonShaderSource: string,
|
||||||
|
shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||||
|
super(commonShaderSource, shaderSources);
|
||||||
|
|
||||||
|
this.appController = appController;
|
||||||
|
|
||||||
|
this.camera = new OrthographicCamera(this.canvas);
|
||||||
|
this.camera.onPan = () => this.setDirty();
|
||||||
|
this.camera.onZoom = () => this.setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
||||||
|
aaLevel: number,
|
||||||
|
subpixelAA: boolean):
|
||||||
|
AntialiasingStrategy {
|
||||||
|
if (aaType !== 'ecaa')
|
||||||
|
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
|
||||||
|
throw new PathfinderError("Unsupported antialiasing type!");
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
let currentX = 0;
|
||||||
|
|
||||||
|
for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
|
||||||
|
const glyph = unwrapNull(this.appController.textRun).glyphs[glyphIndex];
|
||||||
|
pathTransforms.set([1, 1, currentX, 0], (glyphIndex + 1) * 4);
|
||||||
|
currentX += glyph.advanceWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathTransforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderingFinished(): void {
|
||||||
|
if (this.renderingPromiseCallback != null)
|
||||||
|
this.renderingPromiseCallback(this.lastTimings.atlasRendering);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
glmatrix.mat4.fromTranslation(transform, [translation[0], translation[1], 0]);
|
||||||
|
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
|
||||||
|
|
||||||
|
const pixelsPerUnit = this._pixelsPerEm / unwrapNull(this.appController.font).unitsPerEm;
|
||||||
|
glmatrix.mat4.scale(transform, transform, [pixelsPerUnit, pixelsPerUnit, 1.0]);
|
||||||
|
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
get pixelsPerEm(): number {
|
||||||
|
return this._pixelsPerEm;
|
||||||
|
}
|
||||||
|
|
||||||
|
set pixelsPerEm(newPixelsPerEm: number) {
|
||||||
|
this._pixelsPerEm = newPixelsPerEm;
|
||||||
|
this.setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderingPromiseCallback: ((time: number) => void) | null;
|
||||||
|
|
||||||
|
private _pixelsPerEm: number = 32.0;
|
||||||
|
|
||||||
|
protected directCurveProgramName: keyof ShaderMap<void> = 'directCurve';
|
||||||
|
protected directInteriorProgramName: keyof ShaderMap<void> = 'directInterior';
|
||||||
|
|
||||||
|
protected depthFunction: number = this.gl.GREATER;
|
||||||
|
|
||||||
|
protected camera: OrthographicCamera;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BenchmarkGlyph extends PathfinderGlyph {}
|
class BenchmarkGlyph extends PathfinderGlyph {}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const controller = new BenchmarkAppController;
|
||||||
|
window.addEventListener('load', () => controller.start(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
|
@ -190,7 +190,8 @@ class SVGDemoController extends DemoAppController<SVGDemoView> {
|
||||||
|
|
||||||
private meshesReceived(bounds: glmatrix.vec4): void {
|
private meshesReceived(bounds: glmatrix.vec4): void {
|
||||||
this.view.then(view => {
|
this.view.then(view => {
|
||||||
view.uploadPathMetadata(this.pathInstances);
|
view.uploadPathColors(1);
|
||||||
|
view.uploadPathTransforms(1);
|
||||||
view.attachMeshes([this.meshes]);
|
view.attachMeshes([this.meshes]);
|
||||||
|
|
||||||
view.camera.bounds = bounds;
|
view.camera.bounds = bounds;
|
||||||
|
@ -198,8 +199,9 @@ class SVGDemoController extends DemoAppController<SVGDemoView> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pathInstances: PathInstance[];
|
||||||
|
|
||||||
private svg: SVGSVGElement;
|
private svg: SVGSVGElement;
|
||||||
private pathInstances: PathInstance[];
|
|
||||||
private meshes: PathfinderMeshData;
|
private meshes: PathfinderMeshData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,9 +230,10 @@ class SVGDemoView extends PathfinderDemoView {
|
||||||
return this.destAllocatedSize;
|
return this.destAllocatedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadPathMetadata(instances: PathInstance[]) {
|
protected pathColorsForObject(objectIndex: number): Uint8Array {
|
||||||
|
const instances = this.appController.pathInstances;
|
||||||
const pathColors = new Uint8Array(4 * (instances.length + 1));
|
const pathColors = new Uint8Array(4 * (instances.length + 1));
|
||||||
const pathTransforms = new Float32Array(4 * (instances.length + 1));
|
|
||||||
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
|
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
|
||||||
const startOffset = (pathIndex + 1) * 4;
|
const startOffset = (pathIndex + 1) * 4;
|
||||||
|
|
||||||
|
@ -241,17 +244,22 @@ class SVGDemoView extends PathfinderDemoView {
|
||||||
style[property] === 'none' ? [0, 0, 0, 0] : parseColor(style[property]).rgba;
|
style[property] === 'none' ? [0, 0, 0, 0] : parseColor(style[property]).rgba;
|
||||||
pathColors.set(color.slice(0, 3), startOffset);
|
pathColors.set(color.slice(0, 3), startOffset);
|
||||||
pathColors[startOffset + 3] = color[3] * 255;
|
pathColors[startOffset + 3] = color[3] * 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
||||||
|
const instances = this.appController.pathInstances;
|
||||||
|
const pathTransforms = new Float32Array(4 * (instances.length + 1));
|
||||||
|
|
||||||
|
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
|
||||||
// TODO(pcwalton): Set transform.
|
// TODO(pcwalton): Set transform.
|
||||||
|
const startOffset = (pathIndex + 1) * 4;
|
||||||
pathTransforms.set([1, 1, 0, 0], startOffset);
|
pathTransforms.set([1, 1, 0, 0], startOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathColorsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathColors');
|
return pathTransforms;
|
||||||
const pathTransformBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathTransform');
|
|
||||||
pathColorsBufferTexture.upload(this.gl, pathColors);
|
|
||||||
pathTransformBufferTexture.upload(this.gl, pathTransforms);
|
|
||||||
this.pathColorsBufferTextures = [pathColorsBufferTexture];
|
|
||||||
this.pathTransformBufferTextures = [pathTransformBufferTexture];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
||||||
|
|
|
@ -178,7 +178,7 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
this.meshes = meshes;
|
this.meshes = meshes;
|
||||||
this.view.then(view => {
|
this.view.then(view => {
|
||||||
view.attachText();
|
view.attachText();
|
||||||
view.uploadPathMetadata(this.layout.glyphStorage.uniqueGlyphs.length);
|
view.uploadPathColors(1);
|
||||||
view.attachMeshes([this.meshes]);
|
view.attachMeshes([this.meshes]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -262,17 +262,19 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
super.initContext();
|
super.initContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadPathMetadata(pathCount: number) {
|
protected pathColorsForObject(objectIndex: number): Uint8Array {
|
||||||
|
const atlasGlyphs = this.appController.atlasGlyphs;
|
||||||
|
const pathCount = atlasGlyphs.length;
|
||||||
|
|
||||||
const pathColors = new Uint8Array(4 * (pathCount + 1));
|
const pathColors = new Uint8Array(4 * (pathCount + 1));
|
||||||
|
|
||||||
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
|
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
|
||||||
for (let channel = 0; channel < 3; channel++)
|
for (let channel = 0; channel < 3; channel++)
|
||||||
pathColors[(pathIndex + 1) * 4 + channel] = 0x00; // RGB
|
pathColors[(pathIndex + 1) * 4 + channel] = 0x00; // RGB
|
||||||
pathColors[(pathIndex + 1) * 4 + 3] = 0xff; // alpha
|
pathColors[(pathIndex + 1) * 4 + 3] = 0xff; // alpha
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathColorsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathColors');
|
return pathColors;
|
||||||
pathColorsBufferTexture.upload(this.gl, pathColors);
|
|
||||||
this.pathColorsBufferTextures = [pathColorsBufferTexture];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lays out glyphs on the canvas.
|
/// Lays out glyphs on the canvas.
|
||||||
|
@ -342,10 +344,37 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
const uniqueGlyphIndices = uniqueGlyphs.map(glyph => glyph.index);
|
const uniqueGlyphIndices = uniqueGlyphs.map(glyph => glyph.index);
|
||||||
uniqueGlyphIndices.sort((a, b) => a - b);
|
uniqueGlyphIndices.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
this.uploadPathTransforms(1);
|
||||||
|
|
||||||
// TODO(pcwalton): Regenerate the IBOs to include only the glyphs we care about.
|
// TODO(pcwalton): Regenerate the IBOs to include only the glyphs we care about.
|
||||||
const transforms = new Float32Array((uniqueGlyphs.length + 1) * 4);
|
|
||||||
const pathHints = new Float32Array((uniqueGlyphs.length + 1) * 4);
|
const pathHints = new Float32Array((uniqueGlyphs.length + 1) * 4);
|
||||||
|
|
||||||
|
for (let glyphIndex = 0; glyphIndex < atlasGlyphs.length; glyphIndex++) {
|
||||||
|
const glyph = atlasGlyphs[glyphIndex];
|
||||||
|
|
||||||
|
let pathID = _.sortedIndexOf(uniqueGlyphIndices, glyph.index);
|
||||||
|
assert(pathID >= 0, "No path ID!");
|
||||||
|
pathID++;
|
||||||
|
|
||||||
|
pathHints[pathID * 4 + 0] = hint.xHeight;
|
||||||
|
pathHints[pathID * 4 + 1] = hint.hintedXHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathHintsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathHints');
|
||||||
|
pathHintsBufferTexture.upload(this.gl, pathHints);
|
||||||
|
this.pathHintsBufferTexture = pathHintsBufferTexture;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
||||||
|
const atlasGlyphs = this.appController.atlasGlyphs;
|
||||||
|
const pixelsPerUnit = this.appController.pixelsPerUnit;
|
||||||
|
|
||||||
|
const uniqueGlyphs = this.appController.layout.glyphStorage.uniqueGlyphs;
|
||||||
|
const uniqueGlyphIndices = uniqueGlyphs.map(glyph => glyph.index);
|
||||||
|
uniqueGlyphIndices.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
const transforms = new Float32Array((uniqueGlyphs.length + 1) * 4);
|
||||||
|
|
||||||
for (let glyphIndex = 0; glyphIndex < atlasGlyphs.length; glyphIndex++) {
|
for (let glyphIndex = 0; glyphIndex < atlasGlyphs.length; glyphIndex++) {
|
||||||
const glyph = atlasGlyphs[glyphIndex];
|
const glyph = atlasGlyphs[glyphIndex];
|
||||||
|
|
||||||
|
@ -359,18 +388,9 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
transforms[pathID * 4 + 1] = pixelsPerUnit;
|
transforms[pathID * 4 + 1] = pixelsPerUnit;
|
||||||
transforms[pathID * 4 + 2] = atlasOrigin[0];
|
transforms[pathID * 4 + 2] = atlasOrigin[0];
|
||||||
transforms[pathID * 4 + 3] = atlasOrigin[1];
|
transforms[pathID * 4 + 3] = atlasOrigin[1];
|
||||||
|
|
||||||
pathHints[pathID * 4 + 0] = hint.xHeight;
|
|
||||||
pathHints[pathID * 4 + 1] = hint.hintedXHeight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathTransformBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathTransform');
|
return transforms;
|
||||||
pathTransformBufferTexture.upload(this.gl, transforms);
|
|
||||||
this.pathTransformBufferTextures = [pathTransformBufferTexture];
|
|
||||||
|
|
||||||
const pathHintsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathHints');
|
|
||||||
pathHintsBufferTexture.upload(this.gl, pathHints);
|
|
||||||
this.pathHintsBufferTexture = pathHintsBufferTexture;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createAtlasFramebuffer() {
|
private createAtlasFramebuffer() {
|
||||||
|
|
|
@ -109,6 +109,8 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
|
|
||||||
this.initContext();
|
this.initContext();
|
||||||
|
|
||||||
|
this.lastTimings = { atlasRendering: 0, compositing: 0 };
|
||||||
|
|
||||||
const shaderSource = this.compileShaders(commonShaderSource, shaderSources);
|
const shaderSource = this.compileShaders(commonShaderSource, shaderSources);
|
||||||
this.shaderPrograms = this.linkShaders(shaderSource);
|
this.shaderPrograms = this.linkShaders(shaderSource);
|
||||||
|
|
||||||
|
@ -281,6 +283,9 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
// Finish timing.
|
// Finish timing.
|
||||||
this.finishTiming();
|
this.finishTiming();
|
||||||
|
|
||||||
|
// Invoke the post-render hook.
|
||||||
|
this.renderingFinished();
|
||||||
|
|
||||||
// Take a screenshot if desired.
|
// Take a screenshot if desired.
|
||||||
if (this.wantsScreenshot) {
|
if (this.wantsScreenshot) {
|
||||||
this.wantsScreenshot = false;
|
this.wantsScreenshot = false;
|
||||||
|
@ -288,6 +293,8 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected renderingFinished(): void {}
|
||||||
|
|
||||||
private setTransformUniform(uniforms: UniformMap, objectIndex: number) {
|
private setTransformUniform(uniforms: UniformMap, objectIndex: number) {
|
||||||
const transform = glmatrix.mat4.create();
|
const transform = glmatrix.mat4.create();
|
||||||
if (this.antialiasingStrategy != null)
|
if (this.antialiasingStrategy != null)
|
||||||
|
@ -423,10 +430,10 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
const compositingTime =
|
const compositingTime =
|
||||||
this.timerQueryExt.getQueryObjectEXT(this.compositingTimerQuery,
|
this.timerQueryExt.getQueryObjectEXT(this.compositingTimerQuery,
|
||||||
this.timerQueryExt.QUERY_RESULT_EXT);
|
this.timerQueryExt.QUERY_RESULT_EXT);
|
||||||
this.updateTimings({
|
this.lastTimings = {
|
||||||
atlasRendering: atlasRenderingTime / 1000000.0,
|
atlasRendering: atlasRenderingTime / 1000000.0,
|
||||||
compositing: compositingTime / 1000000.0,
|
compositing: compositingTime / 1000000.0,
|
||||||
});
|
};
|
||||||
|
|
||||||
window.clearInterval(this.timerQueryPollInterval!);
|
window.clearInterval(this.timerQueryPollInterval!);
|
||||||
this.timerQueryPollInterval = null;
|
this.timerQueryPollInterval = null;
|
||||||
|
@ -485,6 +492,34 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 abstract pathColorsForObject(objectIndex: number): Uint8Array;
|
||||||
|
protected abstract pathTransformsForObject(objectIndex: number): Float32Array;
|
||||||
|
|
||||||
protected abstract get depthFunction(): number;
|
protected abstract get depthFunction(): number;
|
||||||
|
|
||||||
protected abstract createAAStrategy(aaType: AntialiasingStrategyName,
|
protected abstract createAAStrategy(aaType: AntialiasingStrategyName,
|
||||||
|
@ -494,8 +529,6 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
|
|
||||||
protected abstract compositeIfNecessary(): void;
|
protected abstract compositeIfNecessary(): void;
|
||||||
|
|
||||||
protected abstract updateTimings(timings: Timings): void;
|
|
||||||
|
|
||||||
abstract get destFramebuffer(): WebGLFramebuffer | null;
|
abstract get destFramebuffer(): WebGLFramebuffer | null;
|
||||||
|
|
||||||
abstract get destAllocatedSize(): glmatrix.vec2;
|
abstract get destAllocatedSize(): glmatrix.vec2;
|
||||||
|
@ -536,6 +569,8 @@ export abstract class PathfinderDemoView extends PathfinderView {
|
||||||
private compositingTimerQuery: WebGLQuery;
|
private compositingTimerQuery: WebGLQuery;
|
||||||
private timerQueryPollInterval: number | null;
|
private timerQueryPollInterval: number | null;
|
||||||
|
|
||||||
|
protected lastTimings: Timings;
|
||||||
|
|
||||||
private wantsScreenshot: boolean;
|
private wantsScreenshot: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue