From 157292175b30e076c635e72ad7be22808844de02 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 3 Oct 2017 15:24:56 -0700 Subject: [PATCH] Use instanced rendering in the 3D demo. This massively decreases the load time and memory usage in exchange for more draw calls. --- demo/client/src/3d-demo.ts | 113 ++++++++++------ demo/client/src/aa-strategy.ts | 24 ++-- demo/client/src/app-controller.ts | 4 +- demo/client/src/benchmark.ts | 4 +- demo/client/src/ecaa-strategy.ts | 98 +++++++------- demo/client/src/ssaa-strategy.ts | 14 +- demo/client/src/svg-demo.ts | 4 +- demo/client/src/text-demo.ts | 4 +- demo/client/src/view.ts | 206 ++++++++++++++++++++++-------- 9 files changed, 303 insertions(+), 168 deletions(-) diff --git a/demo/client/src/3d-demo.ts b/demo/client/src/3d-demo.ts index 88ac34a3..65d062c7 100644 --- a/demo/client/src/3d-demo.ts +++ b/demo/client/src/3d-demo.ts @@ -24,7 +24,7 @@ import SSAAStrategy from "./ssaa-strategy"; 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"; +import {DemoView, Timings} from "./view"; const TEXT_AVAILABLE_WIDTH: number = 150000; const TEXT_PADDING: number = 2000; @@ -94,12 +94,19 @@ interface MonumentSide { lines: TextLine[]; } +interface MeshDescriptor { + glyphID: number; + textFrameIndex: number; + positions: glmatrix.vec2[]; +} + class ThreeDController extends DemoAppController { textFrames: TextFrame[]; glyphStore: GlyphStore; + meshDescriptors: MeshDescriptor[]; private baseMeshes: PathfinderMeshData; - private expandedMeshes: ExpandedMeshData[]; + private expandedMeshes: PathfinderMeshData[]; private monumentPromise: Promise; @@ -196,20 +203,64 @@ class ThreeDController extends DemoAppController { this.glyphStore = new GlyphStore(font, glyphsNeeded); this.glyphStore.partition().then(result => { + const hint = new Hint(this.glyphStore.font, PIXELS_PER_UNIT, false); + this.baseMeshes = result.meshes; - this.expandedMeshes = this.textFrames.map(textFrame => { - return textFrame.expandMeshes(this.baseMeshes, glyphsNeeded); + + this.meshDescriptors = []; + + for (let textFrameIndex = 0; + textFrameIndex < this.textFrames.length; + textFrameIndex++) { + const textFrame = this.textFrames[textFrameIndex]; + + let glyphDescriptors = []; + for (const run of textFrame.runs) { + for (let glyphIndex = 0; glyphIndex < run.glyphIDs.length; glyphIndex++) { + glyphDescriptors.push({ + glyphID: run.glyphIDs[glyphIndex], + position: run.calculatePixelOriginForGlyphAt(glyphIndex, + PIXELS_PER_UNIT, + hint), + }); + } + } + + glyphDescriptors = _.sortBy(glyphDescriptors, descriptor => descriptor.glyphID); + + let currentMeshDescriptor: (MeshDescriptor | null) = null; + for (const glyphDescriptor of glyphDescriptors) { + if (currentMeshDescriptor == null || + glyphDescriptor.glyphID !== currentMeshDescriptor.glyphID) { + if (currentMeshDescriptor != null) + this.meshDescriptors.push(currentMeshDescriptor); + currentMeshDescriptor = { + glyphID: glyphDescriptor.glyphID, + positions: [], + textFrameIndex: textFrameIndex, + }; + } + currentMeshDescriptor.positions.push(glyphDescriptor.position); + } + if (currentMeshDescriptor != null) + this.meshDescriptors.push(currentMeshDescriptor); + } + + this.expandedMeshes = this.meshDescriptors.map(meshDescriptor => { + const glyphIndex = _.sortedIndexOf(glyphsNeeded, meshDescriptor.glyphID); + return this.baseMeshes.expand([glyphIndex + 1]); }); + this.view.then(view => { view.uploadPathColors(this.expandedMeshes.length); view.uploadPathTransforms(this.expandedMeshes.length); - view.attachMeshes(this.expandedMeshes.map(meshes => meshes.meshes)); + view.attachMeshes(this.expandedMeshes); }); }); } } -class ThreeDView extends PathfinderDemoView { +class ThreeDView extends DemoView { destFramebuffer: WebGLFramebuffer | null = null; camera: PerspectiveCamera; @@ -221,6 +272,10 @@ class ThreeDView extends PathfinderDemoView { protected depthFunction: number = this.gl.LESS; + protected get pathIDsAreInstanced(): boolean { + return true; + } + private _scale: number; private appController: ThreeDController; @@ -249,38 +304,18 @@ class ThreeDView extends PathfinderDemoView { this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, CUBE_INDICES, this.gl.STATIC_DRAW); } - 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); - - return pathColors; + protected pathColorsForObject(objectIndex: number): Uint8Array { + return TEXT_COLOR; } - 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); - + protected pathTransformsForObject(objectIndex: number): Float32Array { + const meshDescriptor = this.appController.meshDescriptors[objectIndex]; + const pathCount = meshDescriptor.positions.length; const pathTransforms = new Float32Array(4 * (pathCount + 1)); - - let globalPathIndex = 0; - for (const run of textFrame.runs) { - for (let pathIndex = 0; - pathIndex < run.glyphIDs.length; - pathIndex++, globalPathIndex++) { - const glyphOrigin = run.calculatePixelOriginForGlyphAt(pathIndex, - PIXELS_PER_UNIT, - hint); - pathTransforms.set([1, 1, glyphOrigin[0], glyphOrigin[1]], - (globalPathIndex + 1) * 4); - } + for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) { + const glyphOrigin = meshDescriptor.positions[pathIndex]; + pathTransforms.set([1, 1, glyphOrigin[0], glyphOrigin[1]], (pathIndex + 1) * 4); } - return pathTransforms; } @@ -343,17 +378,19 @@ class ThreeDView extends PathfinderDemoView { } protected getModelviewTransform(objectIndex: number): glmatrix.mat4 { + const textFrameIndex = this.appController.meshDescriptors[objectIndex].textFrameIndex; const transform = glmatrix.mat4.create(); - glmatrix.mat4.rotateY(transform, transform, Math.PI / 2.0 * objectIndex); + glmatrix.mat4.rotateY(transform, transform, Math.PI / 2.0 * textFrameIndex); glmatrix.mat4.translate(transform, transform, TEXT_TRANSLATION); return transform; } // Cheap but effective backface culling. protected shouldRenderObject(objectIndex: number): boolean { + const textFrameIndex = this.appController.meshDescriptors[objectIndex].textFrameIndex; const translation = this.camera.translation; const extent = TEXT_TRANSLATION[2] * TEXT_SCALE[2]; - switch (objectIndex) { + switch (textFrameIndex) { case 0: return translation[2] < -extent; case 1: return translation[0] < -extent; case 2: return translation[2] > extent; @@ -361,6 +398,10 @@ class ThreeDView extends PathfinderDemoView { } } + protected meshInstanceCountForObject(objectIndex: number): number { + return this.appController.meshDescriptors[objectIndex].positions.length; + } + get destAllocatedSize(): glmatrix.vec2 { return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height); } diff --git a/demo/client/src/aa-strategy.ts b/demo/client/src/aa-strategy.ts index 64b2c66c..1ad3f6c0 100644 --- a/demo/client/src/aa-strategy.ts +++ b/demo/client/src/aa-strategy.ts @@ -10,7 +10,7 @@ import * as glmatrix from 'gl-matrix'; -import {PathfinderDemoView} from './view'; +import {DemoView} from './view'; export type AntialiasingStrategyName = 'none' | 'ssaa' | 'ecaa'; @@ -21,15 +21,15 @@ export abstract class AntialiasingStrategy { shouldRenderDirect: boolean; // Prepares any OpenGL data. This is only called on startup and canvas resize. - init(view: PathfinderDemoView): void { + init(view: DemoView): void { this.setFramebufferSize(view); } // Uploads any mesh data. This is called whenever a new set of meshes is supplied. - abstract attachMeshes(view: PathfinderDemoView): void; + abstract attachMeshes(view: DemoView): void; // This is called whenever the framebuffer has changed. - abstract setFramebufferSize(view: PathfinderDemoView): void; + abstract setFramebufferSize(view: DemoView): void; // Returns the transformation matrix that should be applied when directly rendering. abstract get transform(): glmatrix.mat4; @@ -37,17 +37,17 @@ export abstract class AntialiasingStrategy { // Called before direct rendering. // // Typically, this redirects direct rendering to a framebuffer of some sort. - abstract prepare(view: PathfinderDemoView): void; + abstract prepare(view: DemoView): void; // Called after direct rendering. // // This usually performs the actual antialiasing. - abstract antialias(view: PathfinderDemoView): void; + abstract antialias(view: DemoView): void; // Called after antialiasing. // // This usually blits to the real framebuffer. - abstract resolve(view: PathfinderDemoView): void; + abstract resolve(view: DemoView): void; } export class NoAAStrategy extends AntialiasingStrategy { @@ -58,9 +58,9 @@ export class NoAAStrategy extends AntialiasingStrategy { this.framebufferSize = glmatrix.vec2.create(); } - attachMeshes(view: PathfinderDemoView) {} + attachMeshes(view: DemoView) {} - setFramebufferSize(view: PathfinderDemoView) { + setFramebufferSize(view: DemoView) { this.framebufferSize = view.destAllocatedSize; } @@ -68,15 +68,15 @@ export class NoAAStrategy extends AntialiasingStrategy { return glmatrix.mat4.create(); } - prepare(view: PathfinderDemoView) { + prepare(view: DemoView) { view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, view.destFramebuffer); view.gl.viewport(0, 0, this.framebufferSize[0], this.framebufferSize[1]); view.gl.disable(view.gl.SCISSOR_TEST); } - antialias(view: PathfinderDemoView) {} + antialias(view: DemoView) {} - resolve(view: PathfinderDemoView) {} + resolve(view: DemoView) {} get shouldRenderDirect() { return true; diff --git a/demo/client/src/app-controller.ts b/demo/client/src/app-controller.ts index 0360b3fa..b4befa9c 100644 --- a/demo/client/src/app-controller.ts +++ b/demo/client/src/app-controller.ts @@ -12,7 +12,7 @@ import { AntialiasingStrategyName, SubpixelAAType } from "./aa-strategy"; import {FilePickerView} from "./file-picker"; import {ShaderLoader, ShaderMap, ShaderProgramSource} from './shader-loader'; import {expectNotNull, unwrapNull, unwrapUndef} from './utils'; -import {PathfinderDemoView, Timings, TIMINGS} from "./view"; +import {DemoView, Timings, TIMINGS} from "./view"; export abstract class AppController { protected canvas: HTMLCanvasElement; @@ -45,7 +45,7 @@ export abstract class AppController { protected abstract get defaultFile(): string; } -export abstract class DemoAppController extends AppController { +export abstract class DemoAppController extends AppController { view: Promise; protected abstract readonly builtinFileURI: string; diff --git a/demo/client/src/benchmark.ts b/demo/client/src/benchmark.ts index d8cb672f..1c254801 100644 --- a/demo/client/src/benchmark.ts +++ b/demo/client/src/benchmark.ts @@ -23,7 +23,7 @@ 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"; +import {DemoView, MonochromeDemoView, Timings } from "./view"; const STRING: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -178,7 +178,7 @@ class BenchmarkAppController extends DemoAppController { } } -class BenchmarkTestView extends MonochromePathfinderView { +class BenchmarkTestView extends MonochromeDemoView { destFramebuffer: WebGLFramebuffer | null = null; renderingPromiseCallback: ((time: number) => void) | null; diff --git a/demo/client/src/ecaa-strategy.ts b/demo/client/src/ecaa-strategy.ts index f2034f86..711d48d9 100644 --- a/demo/client/src/ecaa-strategy.ts +++ b/demo/client/src/ecaa-strategy.ts @@ -18,7 +18,7 @@ import {WebGLVertexArrayObject} from './gl-utils'; import {B_QUAD_LOWER_INDICES_OFFSET, B_QUAD_SIZE, B_QUAD_UPPER_INDICES_OFFSET} from './meshes'; import {PathfinderShaderProgram} from './shader-loader'; import {UINT32_SIZE, unwrapNull} from './utils'; -import {MonochromePathfinderView} from './view'; +import {MonochromeDemoView} from './view'; interface UpperAndLower { upper: T; @@ -56,14 +56,14 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { this.destFramebufferSize = glmatrix.vec2.create(); } - init(view: MonochromePathfinderView) { + init(view: MonochromeDemoView) { super.init(view); this.bVertexPositionBufferTexture = new PathfinderBufferTexture(view.gl, 'uBVertexPosition'); this.bVertexPathIDBufferTexture = new PathfinderBufferTexture(view.gl, 'uBVertexPathID'); } - attachMeshes(view: MonochromePathfinderView) { + attachMeshes(view: MonochromeDemoView) { const bVertexPositions = new Float32Array(view.meshData[0].bVertexPositions); const bVertexPathIDs = new Uint8Array(view.meshData[0].bVertexPathIDs); this.bVertexPositionBufferTexture.upload(view.gl, bVertexPositions); @@ -76,7 +76,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { this.createResolveVAO(view); } - setFramebufferSize(view: MonochromePathfinderView) { + setFramebufferSize(view: MonochromeDemoView) { this.destFramebufferSize = glmatrix.vec2.clone(view.destAllocatedSize); glmatrix.vec2.mul(this.supersampledFramebufferSize, this.destFramebufferSize, @@ -88,7 +88,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, null); } - prepare(view: MonochromePathfinderView) { + prepare(view: MonochromeDemoView) { const usedSize = this.supersampledUsedSize(view); view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, this.directFramebuffer); view.gl.viewport(0, @@ -123,7 +123,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { ]); } - antialias(view: MonochromePathfinderView) { + antialias(view: MonochromeDemoView) { // Detect edges if necessary. this.detectEdgesIfNecessary(view); @@ -135,7 +135,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { this.antialiasCurves(view); } - resolve(view: MonochromePathfinderView) { + resolve(view: MonochromeDemoView) { // Resolve the antialiasing. this.resolveAA(view); } @@ -144,7 +144,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { return glmatrix.mat4.create(); } - protected initDirectFramebuffer(view: MonochromePathfinderView) { + protected initDirectFramebuffer(view: MonochromeDemoView) { this.directColorTexture = createFramebufferColorTexture(view.gl, this.destFramebufferSize); this.directPathIDTexture = createFramebufferColorTexture(view.gl, this.destFramebufferSize); @@ -155,31 +155,31 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { this.directDepthTexture); } - protected setCoverDepthState(view: MonochromePathfinderView): void { + protected setCoverDepthState(view: MonochromeDemoView): void { view.gl.disable(view.gl.DEPTH_TEST); } - protected setResolveDepthState(view: MonochromePathfinderView): void { + protected setResolveDepthState(view: MonochromeDemoView): void { view.gl.disable(view.gl.DEPTH_TEST); } - protected supersampledUsedSize(view: MonochromePathfinderView): glmatrix.vec2 { + protected supersampledUsedSize(view: MonochromeDemoView): 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, + protected abstract getResolveProgram(view: MonochromeDemoView): PathfinderShaderProgram; + protected abstract initEdgeDetectFramebuffer(view: MonochromeDemoView): void; + protected abstract createEdgeDetectVAO(view: MonochromeDemoView): void; + protected abstract detectEdgesIfNecessary(view: MonochromeDemoView): void; + protected abstract clearForCover(view: MonochromeDemoView): void; + protected abstract setAADepthState(view: MonochromeDemoView): void; + protected abstract clearForResolve(view: MonochromeDemoView): void; + protected abstract setResolveUniforms(view: MonochromeDemoView, program: PathfinderShaderProgram): void; - private initAAAlphaFramebuffer(view: MonochromePathfinderView) { + private initAAAlphaFramebuffer(view: MonochromeDemoView) { this.aaAlphaTexture = unwrapNull(view.gl.createTexture()); view.gl.activeTexture(view.gl.TEXTURE0); view.gl.bindTexture(view.gl.TEXTURE_2D, this.aaAlphaTexture); @@ -203,7 +203,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { this.aaDepthTexture); } - private createCoverVAO(view: MonochromePathfinderView) { + private createCoverVAO(view: MonochromeDemoView) { this.coverVAO = view.vertexArrayObjectExt.createVertexArrayOES(); view.vertexArrayObjectExt.bindVertexArrayOES(this.coverVAO); @@ -235,7 +235,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { view.vertexArrayObjectExt.bindVertexArrayOES(null); } - private createLineVAOs(view: MonochromePathfinderView) { + private createLineVAOs(view: MonochromeDemoView) { const lineProgram = view.shaderPrograms.ecaaLine; const attributes = lineProgram.attributes; @@ -270,7 +270,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { this.lineVAOs = vaos as UpperAndLower; } - private createCurveVAOs(view: MonochromePathfinderView) { + private createCurveVAOs(view: MonochromeDemoView) { const curveProgram = view.shaderPrograms.ecaaCurve; const attributes = curveProgram.attributes; @@ -313,7 +313,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { this.curveVAOs = vaos as UpperAndLower; } - private createResolveVAO(view: MonochromePathfinderView) { + private createResolveVAO(view: MonochromeDemoView) { this.resolveVAO = view.vertexArrayObjectExt.createVertexArrayOES(); view.vertexArrayObjectExt.bindVertexArrayOES(this.resolveVAO); @@ -324,7 +324,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { view.vertexArrayObjectExt.bindVertexArrayOES(null); } - private cover(view: MonochromePathfinderView) { + private cover(view: MonochromeDemoView) { // Set state for conservative coverage. const coverProgram = view.shaderPrograms.ecaaCover; const usedSize = this.supersampledUsedSize(view); @@ -356,7 +356,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { view.vertexArrayObjectExt.bindVertexArrayOES(null); } - private setAAState(view: MonochromePathfinderView) { + private setAAState(view: MonochromeDemoView) { const usedSize = this.supersampledUsedSize(view); view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, this.aaFramebuffer); view.gl.viewport(0, @@ -373,7 +373,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { view.gl.enable(view.gl.BLEND); } - private setAAUniforms(view: MonochromePathfinderView, uniforms: UniformMap) { + private setAAUniforms(view: MonochromeDemoView, uniforms: UniformMap) { view.setTransformSTUniform(uniforms, 0); view.setFramebufferSizeUniform(uniforms); this.bVertexPositionBufferTexture.bind(view.gl, uniforms, 0); @@ -383,7 +383,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { view.pathHintsBufferTexture.bind(view.gl, uniforms, 3); } - private antialiasLines(view: MonochromePathfinderView) { + private antialiasLines(view: MonochromeDemoView) { this.setAAState(view); const lineProgram = view.shaderPrograms.ecaaLine; @@ -408,7 +408,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { view.vertexArrayObjectExt.bindVertexArrayOES(null); } - private antialiasCurves(view: MonochromePathfinderView) { + private antialiasCurves(view: MonochromeDemoView) { this.setAAState(view); const curveProgram = view.shaderPrograms.ecaaCurve; @@ -433,7 +433,7 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { view.vertexArrayObjectExt.bindVertexArrayOES(null); } - private resolveAA(view: MonochromePathfinderView) { + private resolveAA(view: MonochromeDemoView) { // Set state for ECAA resolve. const usedSize = view.destUsedSize; view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, view.destFramebuffer); @@ -477,34 +477,34 @@ export abstract class ECAAStrategy extends AntialiasingStrategy { } export class ECAAMonochromeStrategy extends ECAAStrategy { - protected getResolveProgram(view: MonochromePathfinderView): PathfinderShaderProgram { + protected getResolveProgram(view: MonochromeDemoView): PathfinderShaderProgram { if (this.subpixelAA !== 'none') return view.shaderPrograms.ecaaMonoSubpixelResolve; return view.shaderPrograms.ecaaMonoResolve; } - protected initEdgeDetectFramebuffer(view: MonochromePathfinderView) {} + protected initEdgeDetectFramebuffer(view: MonochromeDemoView) {} - protected createEdgeDetectVAO(view: MonochromePathfinderView) {} + protected createEdgeDetectVAO(view: MonochromeDemoView) {} - protected detectEdgesIfNecessary(view: MonochromePathfinderView) {} + protected detectEdgesIfNecessary(view: MonochromeDemoView) {} - protected clearForCover(view: MonochromePathfinderView) { + protected clearForCover(view: MonochromeDemoView) { view.gl.clearColor(0.0, 0.0, 0.0, 0.0); view.gl.clearDepth(0.0); view.gl.clear(view.gl.COLOR_BUFFER_BIT | view.gl.DEPTH_BUFFER_BIT); } - protected setAADepthState(view: MonochromePathfinderView) { + protected setAADepthState(view: MonochromeDemoView) { view.gl.disable(view.gl.DEPTH_TEST); } - protected clearForResolve(view: MonochromePathfinderView) { + protected clearForResolve(view: MonochromeDemoView) { view.gl.clearColor(0.0, 0.0, 0.0, 0.0); view.gl.clear(view.gl.COLOR_BUFFER_BIT); } - protected setResolveUniforms(view: MonochromePathfinderView, program: PathfinderShaderProgram) { + protected setResolveUniforms(view: MonochromeDemoView, program: PathfinderShaderProgram) { view.gl.uniform4fv(program.uniforms.uBGColor, view.bgColor); view.gl.uniform4fv(program.uniforms.uFGColor, view.fgColor); } @@ -522,17 +522,17 @@ export class ECAAMulticolorStrategy extends ECAAStrategy { private bgColorTexture: WebGLTexture; private fgColorTexture: WebGLTexture; - protected getResolveProgram(view: MonochromePathfinderView): PathfinderShaderProgram { + protected getResolveProgram(view: MonochromeDemoView): PathfinderShaderProgram { return view.shaderPrograms.ecaaMultiResolve; } - protected initDirectFramebuffer(view: MonochromePathfinderView) { + protected initDirectFramebuffer(view: MonochromeDemoView) { this._directDepthTexture = createFramebufferDepthTexture(view.gl, this.supersampledFramebufferSize); super.initDirectFramebuffer(view); } - protected initEdgeDetectFramebuffer(view: MonochromePathfinderView) { + protected initEdgeDetectFramebuffer(view: MonochromeDemoView) { this.bgColorTexture = createFramebufferColorTexture(view.gl, this.supersampledFramebufferSize); this.fgColorTexture = createFramebufferColorTexture(view.gl, @@ -543,7 +543,7 @@ export class ECAAMulticolorStrategy extends ECAAStrategy { this.aaDepthTexture); } - protected createEdgeDetectVAO(view: MonochromePathfinderView) { + protected createEdgeDetectVAO(view: MonochromeDemoView) { this.edgeDetectVAO = view.vertexArrayObjectExt.createVertexArrayOES(); view.vertexArrayObjectExt.bindVertexArrayOES(this.edgeDetectVAO); @@ -554,7 +554,7 @@ export class ECAAMulticolorStrategy extends ECAAStrategy { view.vertexArrayObjectExt.bindVertexArrayOES(null); } - protected detectEdgesIfNecessary(view: MonochromePathfinderView) { + protected detectEdgesIfNecessary(view: MonochromeDemoView) { // Set state for edge detection. const edgeDetectProgram = view.shaderPrograms.ecaaEdgeDetect; view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, this.edgeDetectFramebuffer); @@ -593,32 +593,32 @@ export class ECAAMulticolorStrategy extends ECAAStrategy { view.vertexArrayObjectExt.bindVertexArrayOES(null); } - protected setCoverDepthState(view: MonochromePathfinderView) { + protected setCoverDepthState(view: MonochromeDemoView) { view.gl.depthMask(false); view.gl.depthFunc(view.gl.ALWAYS); view.gl.enable(view.gl.DEPTH_TEST); } - protected clearForCover(view: MonochromePathfinderView) { + protected clearForCover(view: MonochromeDemoView) { view.gl.clearColor(0.0, 0.0, 0.0, 0.0); view.gl.clear(view.gl.COLOR_BUFFER_BIT); } - protected setAADepthState(view: MonochromePathfinderView) { + protected setAADepthState(view: MonochromeDemoView) { view.gl.depthMask(false); view.gl.depthFunc(view.gl.EQUAL); view.gl.enable(view.gl.DEPTH_TEST); } - protected setResolveDepthState(view: MonochromePathfinderView) { + protected setResolveDepthState(view: MonochromeDemoView) { view.gl.depthMask(false); view.gl.depthFunc(view.gl.NOTEQUAL); view.gl.enable(view.gl.DEPTH_TEST); } - protected clearForResolve(view: MonochromePathfinderView) {} + protected clearForResolve(view: MonochromeDemoView) {} - protected setResolveUniforms(view: MonochromePathfinderView, program: PathfinderShaderProgram) { + protected setResolveUniforms(view: MonochromeDemoView, program: PathfinderShaderProgram) { view.gl.activeTexture(view.gl.TEXTURE1); view.gl.bindTexture(view.gl.TEXTURE_2D, this.bgColorTexture); view.gl.uniform1i(program.uniforms.uBGColor, 1); diff --git a/demo/client/src/ssaa-strategy.ts b/demo/client/src/ssaa-strategy.ts index 28c36d30..35e07955 100644 --- a/demo/client/src/ssaa-strategy.ts +++ b/demo/client/src/ssaa-strategy.ts @@ -13,7 +13,7 @@ import * as glmatrix from 'gl-matrix'; import {AntialiasingStrategy, SubpixelAAType} from './aa-strategy'; import {createFramebuffer, createFramebufferDepthTexture, setTextureParameters} from './gl-utils'; import {unwrapNull} from './utils'; -import {PathfinderDemoView} from './view'; +import {DemoView} from './view'; export default class SSAAStrategy extends AntialiasingStrategy { private level: number; @@ -33,9 +33,9 @@ export default class SSAAStrategy extends AntialiasingStrategy { this.supersampledFramebufferSize = glmatrix.vec2.create(); } - attachMeshes(view: PathfinderDemoView) {} + attachMeshes(view: DemoView) {} - setFramebufferSize(view: PathfinderDemoView) { + setFramebufferSize(view: DemoView) { this.destFramebufferSize = glmatrix.vec2.clone(view.destAllocatedSize); this.supersampledFramebufferSize = glmatrix.vec2.create(); @@ -77,7 +77,7 @@ export default class SSAAStrategy extends AntialiasingStrategy { return transform; } - prepare(view: PathfinderDemoView) { + prepare(view: DemoView) { const framebufferSize = this.supersampledFramebufferSize; const usedSize = this.usedSupersampledFramebufferSize(view); view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, this.supersampledFramebuffer); @@ -86,9 +86,9 @@ export default class SSAAStrategy extends AntialiasingStrategy { view.gl.enable(view.gl.SCISSOR_TEST); } - antialias(view: PathfinderDemoView) {} + antialias(view: DemoView) {} - resolve(view: PathfinderDemoView) { + resolve(view: DemoView) { view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, view.destFramebuffer); view.gl.viewport(0, 0, view.destAllocatedSize[0], view.destAllocatedSize[1]); view.gl.disable(view.gl.DEPTH_TEST); @@ -123,7 +123,7 @@ export default class SSAAStrategy extends AntialiasingStrategy { return glmatrix.vec2.clone([this.subpixelAA !== 'none' ? 3 : 2, this.level === 2 ? 1 : 2]); } - private usedSupersampledFramebufferSize(view: PathfinderDemoView): glmatrix.vec2 { + private usedSupersampledFramebufferSize(view: DemoView): glmatrix.vec2 { const result = glmatrix.vec2.create(); glmatrix.vec2.mul(result, view.destUsedSize, this.supersampleScale); return result; diff --git a/demo/client/src/svg-demo.ts b/demo/client/src/svg-demo.ts index 1f5e4de1..cdb79eb1 100644 --- a/demo/client/src/svg-demo.ts +++ b/demo/client/src/svg-demo.ts @@ -22,7 +22,7 @@ import {ShaderMap, ShaderProgramSource} from './shader-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 {DemoView, Timings} from './view'; const parseColor = require('parse-color'); @@ -87,7 +87,7 @@ class SVGDemoController extends DemoAppController { } } -class SVGDemoView extends PathfinderDemoView { +class SVGDemoView extends DemoView { camera: OrthographicCamera; protected depthFunction: number = this.gl.GREATER; diff --git a/demo/client/src/text-demo.ts b/demo/client/src/text-demo.ts index db15f213..422bff69 100644 --- a/demo/client/src/text-demo.ts +++ b/demo/client/src/text-demo.ts @@ -30,7 +30,7 @@ import {calculatePixelDescent, calculatePixelRectForGlyph, PathfinderFont} from 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 {MonochromeDemoView, Timings, TIMINGS} from './view'; const DEFAULT_TEXT: string = `’Twas brillig, and the slithy toves @@ -268,7 +268,7 @@ class TextDemoController extends DemoAppController { } } -class TextDemoView extends MonochromePathfinderView { +class TextDemoView extends MonochromeDemoView { atlasFramebuffer: WebGLFramebuffer; atlasDepthTexture: WebGLTexture; diff --git a/demo/client/src/view.ts b/demo/client/src/view.ts index 7c4d3741..c24bf73d 100644 --- a/demo/client/src/view.ts +++ b/demo/client/src/view.ts @@ -8,9 +8,13 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +// FIXME(pcwalton): This is turning into a fragile inheritance hierarchy. See if we can refactor to +// use composition more. + import * as glmatrix from 'gl-matrix'; -import { AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy, SubpixelAAType } from "./aa-strategy"; +import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; +import {SubpixelAAType} from "./aa-strategy"; import PathfinderBufferTexture from './buffer-texture'; import {Camera} from "./camera"; import {QUAD_ELEMENTS, UniformMap} from './gl-utils'; @@ -19,6 +23,8 @@ import {PathfinderShaderProgram, SHADER_NAMES, ShaderMap} from './shader-loader' import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader'; import {expectNotNull, PathfinderError, UINT32_SIZE, unwrapNull} from './utils'; +const MAX_PATHS: number = 65535; + const TIME_INTERVAL_DELAY: number = 32; const B_LOOP_BLINN_DATA_SIZE: number = 4; @@ -115,7 +121,7 @@ export abstract class PathfinderView { } } -export abstract class PathfinderDemoView extends PathfinderView { +export abstract class DemoView extends PathfinderView { gl: WebGLRenderingContext; shaderPrograms: ShaderMap; @@ -143,6 +149,12 @@ export abstract class PathfinderDemoView extends PathfinderView { protected lastTimings: Timings; + protected get pathIDsAreInstanced(): boolean { + return false; + } + + private instancedPathIDVBO: WebGLBuffer | null; + private atlasRenderingTimerQuery: WebGLQuery; private compositingTimerQuery: WebGLQuery; private timerQueryPollInterval: number | null; @@ -162,6 +174,9 @@ export abstract class PathfinderDemoView extends PathfinderView { this.pathTransformBufferTextures = []; this.pathColorsBufferTextures = []; + if (this.pathIDsAreInstanced) + this.initInstancedPathIDVBO(); + this.wantsScreenshot = false; this.antialiasingStrategy = new NoAAStrategy(0, 'none'); @@ -390,6 +405,10 @@ export abstract class PathfinderDemoView extends PathfinderView { protected abstract compositeIfNecessary(): void; + protected meshInstanceCountForObject(objectIndex: number): number { + return 1; + } + private compileShaders(commonSource: string, shaderSources: ShaderMap): ShaderMap { const shaders: Partial>> = {}; @@ -434,6 +453,17 @@ export abstract class PathfinderDemoView extends PathfinderView { return shaderProgramMap as ShaderMap; } + private initInstancedPathIDVBO(): void { + const pathIDs = new Uint16Array(MAX_PATHS); + for (let pathIndex = 0; pathIndex < MAX_PATHS; pathIndex++) + pathIDs[pathIndex] = pathIndex + 1; + + this.instancedPathIDVBO = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instancedPathIDVBO); + this.gl.bufferData(this.gl.ARRAY_BUFFER, pathIDs, this.gl.STATIC_DRAW); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null); + } + private setTransformUniform(uniforms: UniformMap, objectIndex: number) { const transform = glmatrix.mat4.clone(this.worldTransform); glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex)); @@ -447,6 +477,12 @@ export abstract class PathfinderDemoView extends PathfinderView { const meshes = this.meshes[objectIndex]; + let instanceCount: number | null; + if (!this.pathIDsAreInstanced) + instanceCount = null; + else + instanceCount = this.meshInstanceCountForObject(objectIndex); + // Set up implicit cover state. this.gl.depthFunc(this.depthFunction); this.gl.depthMask(true); @@ -454,25 +490,12 @@ export abstract class PathfinderDemoView extends PathfinderView { this.gl.disable(this.gl.BLEND); // Set up the implicit cover interior VAO. + // + // TODO(pcwalton): Cache these. const directInteriorProgram = this.shaderPrograms[this.directInteriorProgramName]; - this.gl.useProgram(directInteriorProgram.program); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPositions); - this.gl.vertexAttribPointer(directInteriorProgram.attributes.aPosition, - 2, - this.gl.FLOAT, - false, - 0, - 0); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPathIDs); - this.gl.vertexAttribPointer(directInteriorProgram.attributes.aPathID, - 1, - this.gl.UNSIGNED_SHORT, - false, - 0, - 0); - this.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPosition); - this.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPathID); - this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, meshes.coverInteriorIndices); + const implicitCoverInteriorVAO = this.vertexArrayObjectExt.createVertexArrayOES(); + this.vertexArrayObjectExt.bindVertexArrayOES(implicitCoverInteriorVAO); + this.initImplicitCoverInteriorVAO(objectIndex); // Draw direct interior parts. this.setTransformUniform(directInteriorProgram.uniforms, objectIndex); @@ -487,7 +510,15 @@ export abstract class PathfinderDemoView extends PathfinderView { this.pathHintsBufferTexture.bind(this.gl, directInteriorProgram.uniforms, 2); let indexCount = this.gl.getBufferParameter(this.gl.ELEMENT_ARRAY_BUFFER, this.gl.BUFFER_SIZE) / UINT32_SIZE; - this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0); + if (instanceCount == null) { + this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0); + } else { + this.instancedArraysExt.drawElementsInstancedANGLE(this.gl.TRIANGLES, + indexCount, + this.gl.UNSIGNED_INT, + 0, + instanceCount); + } // Set up direct curve state. this.gl.depthMask(false); @@ -497,40 +528,12 @@ export abstract class PathfinderDemoView extends PathfinderView { this.gl.ONE, this.gl.ONE); // Set up the direct curve VAO. + // + // TODO(pcwalton): Cache these. const directCurveProgram = this.shaderPrograms[this.directCurveProgramName]; - this.gl.useProgram(directCurveProgram.program); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPositions); - this.gl.vertexAttribPointer(directCurveProgram.attributes.aPosition, - 2, - this.gl.FLOAT, - false, - 0, - 0); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPathIDs); - this.gl.vertexAttribPointer(directCurveProgram.attributes.aPathID, - 1, - this.gl.UNSIGNED_SHORT, - false, - 0, - 0); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexLoopBlinnData); - this.gl.vertexAttribPointer(directCurveProgram.attributes.aTexCoord, - 2, - this.gl.UNSIGNED_BYTE, - false, - B_LOOP_BLINN_DATA_SIZE, - B_LOOP_BLINN_DATA_TEX_COORD_OFFSET); - this.gl.vertexAttribPointer(directCurveProgram.attributes.aSign, - 1, - this.gl.BYTE, - false, - B_LOOP_BLINN_DATA_SIZE, - B_LOOP_BLINN_DATA_SIGN_OFFSET); - this.gl.enableVertexAttribArray(directCurveProgram.attributes.aPosition); - this.gl.enableVertexAttribArray(directCurveProgram.attributes.aTexCoord); - this.gl.enableVertexAttribArray(directCurveProgram.attributes.aPathID); - this.gl.enableVertexAttribArray(directCurveProgram.attributes.aSign); - this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, meshes.coverCurveIndices); + const implicitCoverCurveVAO = this.vertexArrayObjectExt.createVertexArrayOES(); + this.vertexArrayObjectExt.bindVertexArrayOES(implicitCoverCurveVAO); + this.initImplicitCoverCurveVAO(objectIndex); // Draw direct curve parts. this.setTransformUniform(directCurveProgram.uniforms, objectIndex); @@ -545,10 +548,101 @@ export abstract class PathfinderDemoView extends PathfinderView { this.pathHintsBufferTexture.bind(this.gl, directCurveProgram.uniforms, 2); indexCount = this.gl.getBufferParameter(this.gl.ELEMENT_ARRAY_BUFFER, this.gl.BUFFER_SIZE) / UINT32_SIZE; - this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0); + if (instanceCount == null) { + this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0); + } else { + this.instancedArraysExt.drawElementsInstancedANGLE(this.gl.TRIANGLES, + indexCount, + this.gl.UNSIGNED_INT, + 0, + instanceCount); + } + + this.vertexArrayObjectExt.bindVertexArrayOES(null); } } + private initImplicitCoverInteriorVAO(objectIndex: number): void { + const meshes = this.meshes[objectIndex]; + + const directInteriorProgram = this.shaderPrograms[this.directInteriorProgramName]; + this.gl.useProgram(directInteriorProgram.program); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPositions); + this.gl.vertexAttribPointer(directInteriorProgram.attributes.aPosition, + 2, + this.gl.FLOAT, + false, + 0, + 0); + + if (this.pathIDsAreInstanced) + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instancedPathIDVBO); + else + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPathIDs); + this.gl.vertexAttribPointer(directInteriorProgram.attributes.aPathID, + 1, + this.gl.UNSIGNED_SHORT, + false, + 0, + 0); + if (this.pathIDsAreInstanced) { + this.instancedArraysExt + .vertexAttribDivisorANGLE(directInteriorProgram.attributes.aPathID, 1); + } + + this.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPosition); + this.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPathID); + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, meshes.coverInteriorIndices); + } + + private initImplicitCoverCurveVAO(objectIndex: number): void { + const meshes = this.meshes[objectIndex]; + + const directCurveProgram = this.shaderPrograms[this.directCurveProgramName]; + this.gl.useProgram(directCurveProgram.program); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPositions); + this.gl.vertexAttribPointer(directCurveProgram.attributes.aPosition, + 2, + this.gl.FLOAT, + false, + 0, + 0); + + if (this.pathIDsAreInstanced) + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instancedPathIDVBO); + else + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPathIDs); + this.gl.vertexAttribPointer(directCurveProgram.attributes.aPathID, + 1, + this.gl.UNSIGNED_SHORT, + false, + 0, + 0); + if (this.pathIDsAreInstanced) { + this.instancedArraysExt + .vertexAttribDivisorANGLE(directCurveProgram.attributes.aPathID, 1); + } + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexLoopBlinnData); + this.gl.vertexAttribPointer(directCurveProgram.attributes.aTexCoord, + 2, + this.gl.UNSIGNED_BYTE, + false, + B_LOOP_BLINN_DATA_SIZE, + B_LOOP_BLINN_DATA_TEX_COORD_OFFSET); + this.gl.vertexAttribPointer(directCurveProgram.attributes.aSign, + 1, + this.gl.BYTE, + false, + B_LOOP_BLINN_DATA_SIZE, + B_LOOP_BLINN_DATA_SIGN_OFFSET); + this.gl.enableVertexAttribArray(directCurveProgram.attributes.aPosition); + this.gl.enableVertexAttribArray(directCurveProgram.attributes.aTexCoord); + this.gl.enableVertexAttribArray(directCurveProgram.attributes.aPathID); + this.gl.enableVertexAttribArray(directCurveProgram.attributes.aSign); + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, meshes.coverCurveIndices); + } + private finishTiming() { if (this.timerQueryPollInterval != null) return; @@ -613,7 +707,7 @@ export abstract class PathfinderDemoView extends PathfinderView { protected abstract get directInteriorProgramName(): keyof ShaderMap; } -export abstract class MonochromePathfinderView extends PathfinderDemoView { +export abstract class MonochromeDemoView extends DemoView { abstract get bgColor(): glmatrix.vec4; abstract get fgColor(): glmatrix.vec4; }