Render SVG paths to an off screen framebuffer
This commit is contained in:
parent
4d16df17db
commit
0bae14b326
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ 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';
|
||||
|
@ -21,7 +21,7 @@ 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 {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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue