Render SVG paths to an off screen framebuffer

This commit is contained in:
Patrick Walton 2017-08-28 17:18:44 -07:00
parent 4d16df17db
commit 0bae14b326
4 changed files with 283 additions and 220 deletions

View File

@ -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;

View File

@ -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<SVGDemoView> {
start() {
super.start();
@ -83,7 +99,6 @@ class SVGDemoController extends AppController<SVGDemoView> {
// 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<SVGDemoView> {
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<SVGPathElement>;
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;
}

View File

@ -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<TextDemoView> {
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 {

View File

@ -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<ShaderProgramSource>):
@ -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 {