diff --git a/demo/client/src/aa-strategy.ts b/demo/client/src/aa-strategy.ts index 287e1e6a..ba0f32d0 100644 --- a/demo/client/src/aa-strategy.ts +++ b/demo/client/src/aa-strategy.ts @@ -12,6 +12,8 @@ import * as glmatrix from 'gl-matrix'; import {PathfinderView} from './view'; +export type AntialiasingStrategyName = 'none' | 'ssaa' | 'ecaa'; + export interface AntialiasingStrategy { // Prepares any OpenGL data. This is only called on startup and canvas resize. init(view: PathfinderView): void; diff --git a/demo/client/src/svg.ts b/demo/client/src/svg.ts index c7997121..7ad01737 100644 --- a/demo/client/src/svg.ts +++ b/demo/client/src/svg.ts @@ -11,15 +11,25 @@ import * as glmatrix from 'gl-matrix'; import 'path-data-polyfill.js'; +import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; +import {ECAAStrategy, ECAAMulticolorStrategy} from "./ecaa-strategy"; +import {PathfinderMeshData} from "./meshes"; import {ShaderMap, ShaderProgramSource} from './shader-loader'; -import {PathfinderView} from './view'; import {panic} from './utils'; +import {PathfinderView, Timings} from './view'; import AppController from './app-controller'; +import SSAAStrategy from "./ssaa-strategy"; const SVG_NS: string = "http://www.w3.org/2000/svg"; const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths"; +const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { + none: NoAAStrategy, + ssaa: SSAAStrategy, + ecaa: ECAAMulticolorStrategy, +}; + declare class SVGPathSegment { type: string; values: number[]; @@ -29,6 +39,12 @@ declare class SVGPathElement { getPathData(settings: any): SVGPathSegment[]; } +interface AntialiasingStrategyTable { + none: typeof NoAAStrategy; + ssaa: typeof SSAAStrategy; + ecaa: typeof ECAAStrategy; +} + class SVGDemoController extends AppController { start() { super.start(); @@ -83,7 +99,6 @@ class SVGDemoController extends AppController { // Build the partitioning request to the server. const request = {paths: pathData.map(segments => ({segments: segments}))}; - console.log(JSON.stringify(request)); // Make the request. window.fetch(PARTITION_SVG_PATHS_ENDPOINT_URL, { @@ -91,12 +106,25 @@ class SVGDemoController extends AppController { headers: {'Content-Type': 'application/json'}, body: JSON.stringify(request), }).then(response => response.text()).then(responseText => { - console.log(JSON.parse(responseText)); + const response = JSON.parse(responseText); + if (!('Ok' in response)) + panic("Failed to partition the font!"); + const meshes = response.Ok.pathData; + this.meshes = new PathfinderMeshData(meshes); + this.meshesReceived(); }); } + private meshesReceived() { + this.view.then(view => { + // TODO(pcwalton): Upload path color data. + view.attachMeshes(this.meshes); + }) + } + private svg: SVGSVGElement; private pathElements: Array; + private meshes: PathfinderMeshData; } class SVGDemoView extends PathfinderView { @@ -137,6 +165,17 @@ class SVGDemoView extends PathfinderView { panic("TODO"); } + protected createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number): + AntialiasingStrategy { + return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel); + } + + protected compositeIfNecessary(): void {} + + protected updateTimings(timings: Timings) { + // TODO(pcwalton) + } + private appController: SVGDemoController; } diff --git a/demo/client/src/text.ts b/demo/client/src/text.ts index f734e425..8d97c941 100644 --- a/demo/client/src/text.ts +++ b/demo/client/src/text.ts @@ -13,15 +13,15 @@ import * as base64js from 'base64-js'; import * as glmatrix from 'gl-matrix'; import * as opentype from 'opentype.js'; -import {AntialiasingStrategy, NoAAStrategy} from './aa-strategy'; +import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy'; import {ECAAMonochromeStrategy, ECAAStrategy} from './ecaa-strategy'; import {createFramebuffer, createFramebufferColorTexture} from './gl-utils'; import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from './gl-utils'; import {UniformMap} from './gl-utils'; import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes'; import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader'; -import { PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull, panic } from './utils'; -import {MonochromePathfinderView} from './view'; +import {PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull, panic} from './utils'; +import {MonochromePathfinderView, Timings} from './view'; import AppController from './app-controller'; import PathfinderBufferTexture from './buffer-texture'; import SSAAStrategy from './ssaa-strategy'; @@ -66,18 +66,12 @@ const INITIAL_FONT_SIZE: number = 72.0; const SCALE_FACTOR: number = 1.0 / 100.0; -const TIME_INTERVAL_DELAY: number = 32; - const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font"; const B_POSITION_SIZE: number = 8; const B_PATH_INDEX_SIZE: number = 2; -const B_LOOP_BLINN_DATA_SIZE: number = 4; -const B_LOOP_BLINN_DATA_TEX_COORD_OFFSET: number = 0; -const B_LOOP_BLINN_DATA_SIGN_OFFSET: number = 2; - const ATLAS_SIZE: glmatrix.vec2 = glmatrix.vec2.fromValues(3072, 3072); type Matrix4D = Float32Array; @@ -93,8 +87,6 @@ type Size2D = glmatrix.vec2; type ShaderType = number; -type WebGLQuery = any; - // `opentype.js` monkey patches declare module 'opentype.js' { @@ -224,7 +216,7 @@ class TextDemoController extends AppController { this.view.then(view => view.attachText()); } - updateTiming(newTimes: {atlasRendering: number, compositing: number}) { + updateTimings(newTimes: Timings) { this.fpsLabel.innerHTML = `${newTimes.atlasRendering} ms atlas, ${newTimes.compositing} ms compositing`; } @@ -262,29 +254,10 @@ class TextDemoView extends MonochromePathfinderView { this.translation = glmatrix.vec2.create(); this.canvas.addEventListener('wheel', event => this.onWheel(event), false); - - this.antialiasingStrategy = new NoAAStrategy(0); - this.antialiasingStrategy.init(this); - } - - setAntialiasingOptions(aaType: keyof AntialiasingStrategyTable, aaLevel: number) { - this.antialiasingStrategy = new (ANTIALIASING_STRATEGIES[aaType])(aaLevel); - - let canvas = this.canvas; - this.antialiasingStrategy.init(this); - this.antialiasingStrategy.setFramebufferSize(this, ATLAS_SIZE); - if (this.meshData != null) - this.antialiasingStrategy.attachMeshes(this); - - this.setDirty(); } protected initContext() { super.initContext(); - - // Set up our timer queries for profiling. - this.atlasRenderingTimerQuery = this.timerQueryExt.createQueryEXT(); - this.compositingTimerQuery = this.timerQueryExt.createQueryEXT(); } uploadPathData(pathCount: number) { @@ -298,14 +271,6 @@ class TextDemoView extends MonochromePathfinderView { this.pathColorsBufferTexture.upload(this.gl, pathColors); } - attachMeshes(meshes: PathfinderMeshData) { - this.meshData = meshes; - this.meshes = new PathfinderMeshBuffers(this.gl, meshes); - this.antialiasingStrategy.attachMeshes(this); - - this.setDirty(); - } - /// Lays out glyphs on the canvas. private layoutGlyphs() { const lineGlyphs = this.appController.lineGlyphs; @@ -488,13 +453,6 @@ class TextDemoView extends MonochromePathfinderView { this.setDirty(); } - private setDirty() { - if (this.dirty) - return; - this.dirty = true; - window.requestAnimationFrame(() => this.redraw()); - } - private onWheel(event: WheelEvent) { event.preventDefault(); @@ -536,80 +494,6 @@ class TextDemoView extends MonochromePathfinderView { this.setDirty(); } - private redraw() { - if (this.meshes == null) { - this.dirty = false; - return; - } - - // Start timing rendering. - if (this.timerQueryPollInterval == null) { - this.timerQueryExt.beginQueryEXT(this.timerQueryExt.TIME_ELAPSED_EXT, - this.atlasRenderingTimerQuery); - } - - // Prepare for direct rendering. - this.antialiasingStrategy.prepare(this); - - // Perform direct rendering (Loop-Blinn). - if (this.antialiasingStrategy.shouldRenderDirect) - this.renderDirect(); - - // Antialias. - this.antialiasingStrategy.resolve(this); - - // End the timer, and start a new one. - if (this.timerQueryPollInterval == null) { - this.timerQueryExt.endQueryEXT(this.timerQueryExt.TIME_ELAPSED_EXT); - this.timerQueryExt.beginQueryEXT(this.timerQueryExt.TIME_ELAPSED_EXT, - this.compositingTimerQuery); - } - - // Draw the glyphs with the resolved atlas to the default framebuffer. - this.composite(); - - // Finish timing, clear dirty bit and finish. - this.finishTiming(); - this.dirty = false; - } - - private finishTiming() { - if (this.timerQueryPollInterval != null) - return; - - this.timerQueryExt.endQueryEXT(this.timerQueryExt.TIME_ELAPSED_EXT); - - this.timerQueryPollInterval = window.setInterval(() => { - for (const queryName of ['atlasRenderingTimerQuery', 'compositingTimerQuery'] as - Array<'atlasRenderingTimerQuery' | 'compositingTimerQuery'>) { - if (this.timerQueryExt.getQueryObjectEXT(this[queryName], - this.timerQueryExt - .QUERY_RESULT_AVAILABLE_EXT) == 0) { - return; - } - } - - const atlasRenderingTime = - this.timerQueryExt.getQueryObjectEXT(this.atlasRenderingTimerQuery, - this.timerQueryExt.QUERY_RESULT_EXT); - const compositingTime = - this.timerQueryExt.getQueryObjectEXT(this.compositingTimerQuery, - this.timerQueryExt.QUERY_RESULT_EXT); - this.appController.updateTiming({ - atlasRendering: atlasRenderingTime / 1000000.0, - compositing: compositingTime / 1000000.0, - }); - - window.clearInterval(this.timerQueryPollInterval!); - this.timerQueryPollInterval = null; - }, TIME_INTERVAL_DELAY); - } - - private setTransformUniform(uniforms: UniformMap) { - const transform = this.antialiasingStrategy.transform(); - this.gl.uniformMatrix4fv(uniforms.uTransform, false, this.antialiasingStrategy.transform()); - } - setIdentityTexScaleUniform(uniforms: UniformMap) { this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0); } @@ -637,96 +521,7 @@ class TextDemoView extends MonochromePathfinderView { this.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]); } - private renderDirect() { - // Set up implicit cover state. - this.gl.depthFunc(this.gl.GREATER); - this.gl.depthMask(true); - this.gl.enable(this.gl.DEPTH_TEST); - this.gl.disable(this.gl.BLEND); - - // Set up the implicit cover interior VAO. - const directInteriorProgram = this.shaderPrograms.directInterior; - this.gl.useProgram(directInteriorProgram.program); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.meshes.bVertexPositions); - this.gl.vertexAttribPointer(directInteriorProgram.attributes.aPosition, - 2, - this.gl.FLOAT, - false, - 0, - 0); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.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, this.meshes.coverInteriorIndices); - - // Draw direct interior parts. - this.setTransformUniform(directInteriorProgram.uniforms); - this.setFramebufferSizeUniform(directInteriorProgram.uniforms); - this.pathColorsBufferTexture.bind(this.gl, directInteriorProgram.uniforms, 0); - this.atlasTransformBuffer.bind(this.gl, directInteriorProgram.uniforms, 1); - 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); - - // Set up direct curve state. - this.gl.depthMask(false); - this.gl.enable(this.gl.BLEND); - this.gl.blendEquation(this.gl.FUNC_ADD); - this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); - - // Set up the direct curve VAO. - const directCurveProgram = this.shaderPrograms.directCurve; - this.gl.useProgram(directCurveProgram.program); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.meshes.bVertexPositions); - this.gl.vertexAttribPointer(directCurveProgram.attributes.aPosition, - 2, - this.gl.FLOAT, - false, - 0, - 0); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.meshes.bVertexPathIDs); - this.gl.vertexAttribPointer(directCurveProgram.attributes.aPathID, - 1, - this.gl.UNSIGNED_SHORT, - false, - 0, - 0); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.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, this.meshes.coverCurveIndices); - - // Draw direct curve parts. - this.setTransformUniform(directCurveProgram.uniforms); - this.setFramebufferSizeUniform(directCurveProgram.uniforms); - this.pathColorsBufferTexture.bind(this.gl, directCurveProgram.uniforms, 0); - this.atlasTransformBuffer.bind(this.gl, directCurveProgram.uniforms, 1); - 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); - } - - private composite() { + protected compositeIfNecessary() { // Set up composite state. this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); @@ -796,11 +591,14 @@ class TextDemoView extends MonochromePathfinderView { return this.appController.atlas.usedSize; } - private antialiasingStrategy: AntialiasingStrategy; + protected createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number): + AntialiasingStrategy { + return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel); + } - private atlasRenderingTimerQuery: WebGLQuery; - private compositingTimerQuery: WebGLQuery; - private timerQueryPollInterval: number | null; + protected updateTimings(timings: Timings) { + this.appController.updateTimings(timings); + } private translation: glmatrix.vec2; @@ -816,8 +614,6 @@ class TextDemoView extends MonochromePathfinderView { atlasTransformBuffer: PathfinderBufferTexture; appController: TextDemoController; - - private dirty: boolean; } interface AntialiasingStrategyTable { diff --git a/demo/client/src/view.ts b/demo/client/src/view.ts index 6a310243..8163b124 100644 --- a/demo/client/src/view.ts +++ b/demo/client/src/view.ts @@ -10,13 +10,20 @@ import * as glmatrix from 'gl-matrix'; +import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; import {QUAD_ELEMENTS, UniformMap} from './gl-utils'; import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes'; import {PathfinderShaderProgram, SHADER_NAMES, ShaderMap} from './shader-loader'; import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader'; -import {PathfinderError, expectNotNull, unwrapNull} from './utils'; +import {PathfinderError, UINT32_SIZE, expectNotNull, unwrapNull} from './utils'; import PathfinderBufferTexture from './buffer-texture'; +const TIME_INTERVAL_DELAY: number = 32; + +const B_LOOP_BLINN_DATA_SIZE: number = 4; +const B_LOOP_BLINN_DATA_TEX_COORD_OFFSET: number = 0; +const B_LOOP_BLINN_DATA_SIGN_OFFSET: number = 2; + const QUAD_POSITIONS: Float32Array = new Float32Array([ 0.0, 1.0, 1.0, 1.0, @@ -31,6 +38,13 @@ const QUAD_TEX_COORDS: Float32Array = new Float32Array([ 1.0, 0.0, ]); +export interface Timings { + atlasRendering: number; + compositing: number; +} + +declare class WebGLQuery {} + export abstract class PathfinderView { constructor(canvas: HTMLCanvasElement, commonShaderSource: string, @@ -45,10 +59,33 @@ export abstract class PathfinderView { this.atlasTransformBuffer = new PathfinderBufferTexture(this.gl, 'uPathTransform'); this.pathColorsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathColors'); + this.antialiasingStrategy = new NoAAStrategy(0); + this.antialiasingStrategy.init(this); + window.addEventListener('resize', () => this.resizeToFit(false), false); this.resizeToFit(true); } + setAntialiasingOptions(aaType: AntialiasingStrategyName, aaLevel: number) { + this.antialiasingStrategy = this.createAAStrategy(aaType, aaLevel); + + let canvas = this.canvas; + this.antialiasingStrategy.init(this); + this.antialiasingStrategy.setFramebufferSize(this, this.destAllocatedSize); + if (this.meshData != null) + this.antialiasingStrategy.attachMeshes(this); + + this.setDirty(); + } + + attachMeshes(meshes: PathfinderMeshData) { + this.meshData = meshes; + this.meshes = new PathfinderMeshBuffers(this.gl, meshes); + this.antialiasingStrategy.attachMeshes(this); + + this.setDirty(); + } + private resizeToFit(initialSize: boolean) { const width = window.innerWidth; const height = window.scrollY + window.innerHeight - @@ -93,6 +130,10 @@ export abstract class PathfinderView { this.quadElementsBuffer = unwrapNull(this.gl.createBuffer()); this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.quadElementsBuffer); this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, QUAD_ELEMENTS, this.gl.STATIC_DRAW); + + // Set up our timer queries for profiling. + this.atlasRenderingTimerQuery = this.timerQueryExt.createQueryEXT(); + this.compositingTimerQuery = this.timerQueryExt.createQueryEXT(); } private compileShaders(commonSource: string, shaderSources: ShaderMap): @@ -154,15 +195,194 @@ export abstract class PathfinderView { this.gl.uniform2i(uniforms.uFramebufferSize, currentViewport[2], currentViewport[3]); } + protected redraw() { + if (this.meshes == null) { + this.dirty = false; + return; + } + + // Start timing rendering. + if (this.timerQueryPollInterval == null) { + this.timerQueryExt.beginQueryEXT(this.timerQueryExt.TIME_ELAPSED_EXT, + this.atlasRenderingTimerQuery); + } + + // Prepare for direct rendering. + this.antialiasingStrategy.prepare(this); + + // Perform direct rendering (Loop-Blinn). + if (this.antialiasingStrategy.shouldRenderDirect) + this.renderDirect(); + + // Antialias. + this.antialiasingStrategy.resolve(this); + + // End the timer, and start a new one. + if (this.timerQueryPollInterval == null) { + this.timerQueryExt.endQueryEXT(this.timerQueryExt.TIME_ELAPSED_EXT); + this.timerQueryExt.beginQueryEXT(this.timerQueryExt.TIME_ELAPSED_EXT, + this.compositingTimerQuery); + } + + // Draw the glyphs with the resolved atlas to the default framebuffer. + this.compositeIfNecessary(); + + // Finish timing, clear dirty bit and finish. + this.finishTiming(); + this.dirty = false; + } + + private setTransformUniform(uniforms: UniformMap) { + const transform = this.antialiasingStrategy.transform(); + this.gl.uniformMatrix4fv(uniforms.uTransform, false, this.antialiasingStrategy.transform()); + } + + private renderDirect() { + // Set up implicit cover state. + this.gl.depthFunc(this.gl.GREATER); + this.gl.depthMask(true); + this.gl.enable(this.gl.DEPTH_TEST); + this.gl.disable(this.gl.BLEND); + + // Set up the implicit cover interior VAO. + const directInteriorProgram = this.shaderPrograms.directInterior; + this.gl.useProgram(directInteriorProgram.program); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.meshes.bVertexPositions); + this.gl.vertexAttribPointer(directInteriorProgram.attributes.aPosition, + 2, + this.gl.FLOAT, + false, + 0, + 0); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.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, this.meshes.coverInteriorIndices); + + // Draw direct interior parts. + this.setTransformUniform(directInteriorProgram.uniforms); + this.setFramebufferSizeUniform(directInteriorProgram.uniforms); + this.pathColorsBufferTexture.bind(this.gl, directInteriorProgram.uniforms, 0); + this.atlasTransformBuffer.bind(this.gl, directInteriorProgram.uniforms, 1); + 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); + + // Set up direct curve state. + this.gl.depthMask(false); + this.gl.enable(this.gl.BLEND); + this.gl.blendEquation(this.gl.FUNC_ADD); + this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); + + // Set up the direct curve VAO. + const directCurveProgram = this.shaderPrograms.directCurve; + this.gl.useProgram(directCurveProgram.program); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.meshes.bVertexPositions); + this.gl.vertexAttribPointer(directCurveProgram.attributes.aPosition, + 2, + this.gl.FLOAT, + false, + 0, + 0); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.meshes.bVertexPathIDs); + this.gl.vertexAttribPointer(directCurveProgram.attributes.aPathID, + 1, + this.gl.UNSIGNED_SHORT, + false, + 0, + 0); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.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, this.meshes.coverCurveIndices); + + // Draw direct curve parts. + this.setTransformUniform(directCurveProgram.uniforms); + this.setFramebufferSizeUniform(directCurveProgram.uniforms); + this.pathColorsBufferTexture.bind(this.gl, directCurveProgram.uniforms, 0); + this.atlasTransformBuffer.bind(this.gl, directCurveProgram.uniforms, 1); + 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); + } + + private finishTiming() { + if (this.timerQueryPollInterval != null) + return; + + this.timerQueryExt.endQueryEXT(this.timerQueryExt.TIME_ELAPSED_EXT); + + this.timerQueryPollInterval = window.setInterval(() => { + for (const queryName of ['atlasRenderingTimerQuery', 'compositingTimerQuery'] as + Array<'atlasRenderingTimerQuery' | 'compositingTimerQuery'>) { + if (this.timerQueryExt.getQueryObjectEXT(this[queryName], + this.timerQueryExt + .QUERY_RESULT_AVAILABLE_EXT) == 0) { + return; + } + } + + const atlasRenderingTime = + this.timerQueryExt.getQueryObjectEXT(this.atlasRenderingTimerQuery, + this.timerQueryExt.QUERY_RESULT_EXT); + const compositingTime = + this.timerQueryExt.getQueryObjectEXT(this.compositingTimerQuery, + this.timerQueryExt.QUERY_RESULT_EXT); + this.updateTimings({ + atlasRendering: atlasRenderingTime / 1000000.0, + compositing: compositingTime / 1000000.0, + }); + + window.clearInterval(this.timerQueryPollInterval!); + this.timerQueryPollInterval = null; + }, TIME_INTERVAL_DELAY); + } + + protected setDirty() { + if (this.dirty) + return; + this.dirty = true; + window.requestAnimationFrame(() => this.redraw()); + } + abstract setTransformAndTexScaleUniformsForDest(uniforms: UniformMap): void; abstract setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap): void; + protected abstract createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number): + AntialiasingStrategy; + + protected abstract compositeIfNecessary(): void; + + protected abstract updateTimings(timings: Timings): void; + abstract get destFramebuffer(): WebGLFramebuffer; abstract get destDepthTexture(): WebGLTexture; abstract get destAllocatedSize(): glmatrix.vec2; abstract get destUsedSize(): glmatrix.vec2; + protected antialiasingStrategy: AntialiasingStrategy; + protected canvas: HTMLCanvasElement; gl: WebGLRenderingContext; @@ -185,6 +405,12 @@ export abstract class PathfinderView { atlasTransformBuffer: PathfinderBufferTexture; protected pathColorsBufferTexture: PathfinderBufferTexture; + + private atlasRenderingTimerQuery: WebGLQuery; + private compositingTimerQuery: WebGLQuery; + private timerQueryPollInterval: number | null; + + protected dirty: boolean; } export abstract class MonochromePathfinderView extends PathfinderView {