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';
|
import {PathfinderView} from './view';
|
||||||
|
|
||||||
|
export type AntialiasingStrategyName = 'none' | 'ssaa' | 'ecaa';
|
||||||
|
|
||||||
export interface AntialiasingStrategy {
|
export interface AntialiasingStrategy {
|
||||||
// Prepares any OpenGL data. This is only called on startup and canvas resize.
|
// Prepares any OpenGL data. This is only called on startup and canvas resize.
|
||||||
init(view: PathfinderView): void;
|
init(view: PathfinderView): void;
|
||||||
|
|
|
@ -11,15 +11,25 @@
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
import 'path-data-polyfill.js';
|
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 {ShaderMap, ShaderProgramSource} from './shader-loader';
|
||||||
import {PathfinderView} from './view';
|
|
||||||
import {panic} from './utils';
|
import {panic} from './utils';
|
||||||
|
import {PathfinderView, Timings} from './view';
|
||||||
import AppController from './app-controller';
|
import AppController from './app-controller';
|
||||||
|
import SSAAStrategy from "./ssaa-strategy";
|
||||||
|
|
||||||
const SVG_NS: string = "http://www.w3.org/2000/svg";
|
const SVG_NS: string = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths";
|
const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths";
|
||||||
|
|
||||||
|
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
||||||
|
none: NoAAStrategy,
|
||||||
|
ssaa: SSAAStrategy,
|
||||||
|
ecaa: ECAAMulticolorStrategy,
|
||||||
|
};
|
||||||
|
|
||||||
declare class SVGPathSegment {
|
declare class SVGPathSegment {
|
||||||
type: string;
|
type: string;
|
||||||
values: number[];
|
values: number[];
|
||||||
|
@ -29,6 +39,12 @@ declare class SVGPathElement {
|
||||||
getPathData(settings: any): SVGPathSegment[];
|
getPathData(settings: any): SVGPathSegment[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AntialiasingStrategyTable {
|
||||||
|
none: typeof NoAAStrategy;
|
||||||
|
ssaa: typeof SSAAStrategy;
|
||||||
|
ecaa: typeof ECAAStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
class SVGDemoController extends AppController<SVGDemoView> {
|
class SVGDemoController extends AppController<SVGDemoView> {
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
@ -83,7 +99,6 @@ class SVGDemoController extends AppController<SVGDemoView> {
|
||||||
|
|
||||||
// Build the partitioning request to the server.
|
// Build the partitioning request to the server.
|
||||||
const request = {paths: pathData.map(segments => ({segments: segments}))};
|
const request = {paths: pathData.map(segments => ({segments: segments}))};
|
||||||
console.log(JSON.stringify(request));
|
|
||||||
|
|
||||||
// Make the request.
|
// Make the request.
|
||||||
window.fetch(PARTITION_SVG_PATHS_ENDPOINT_URL, {
|
window.fetch(PARTITION_SVG_PATHS_ENDPOINT_URL, {
|
||||||
|
@ -91,12 +106,25 @@ class SVGDemoController extends AppController<SVGDemoView> {
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify(request),
|
body: JSON.stringify(request),
|
||||||
}).then(response => response.text()).then(responseText => {
|
}).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 svg: SVGSVGElement;
|
||||||
private pathElements: Array<SVGPathElement>;
|
private pathElements: Array<SVGPathElement>;
|
||||||
|
private meshes: PathfinderMeshData;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SVGDemoView extends PathfinderView {
|
class SVGDemoView extends PathfinderView {
|
||||||
|
@ -137,6 +165,17 @@ class SVGDemoView extends PathfinderView {
|
||||||
panic("TODO");
|
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;
|
private appController: SVGDemoController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import * as base64js from 'base64-js';
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
import * as opentype from 'opentype.js';
|
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 {ECAAMonochromeStrategy, ECAAStrategy} from './ecaa-strategy';
|
||||||
import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
|
import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
|
||||||
import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} 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 {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
||||||
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
|
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
|
||||||
import {PathfinderError, assert, expectNotNull, UINT32_SIZE, unwrapNull, panic} from './utils';
|
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 AppController from './app-controller';
|
||||||
import PathfinderBufferTexture from './buffer-texture';
|
import PathfinderBufferTexture from './buffer-texture';
|
||||||
import SSAAStrategy from './ssaa-strategy';
|
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 SCALE_FACTOR: number = 1.0 / 100.0;
|
||||||
|
|
||||||
const TIME_INTERVAL_DELAY: number = 32;
|
|
||||||
|
|
||||||
const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font";
|
const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font";
|
||||||
|
|
||||||
const B_POSITION_SIZE: number = 8;
|
const B_POSITION_SIZE: number = 8;
|
||||||
|
|
||||||
const B_PATH_INDEX_SIZE: number = 2;
|
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);
|
const ATLAS_SIZE: glmatrix.vec2 = glmatrix.vec2.fromValues(3072, 3072);
|
||||||
|
|
||||||
type Matrix4D = Float32Array;
|
type Matrix4D = Float32Array;
|
||||||
|
@ -93,8 +87,6 @@ type Size2D = glmatrix.vec2;
|
||||||
|
|
||||||
type ShaderType = number;
|
type ShaderType = number;
|
||||||
|
|
||||||
type WebGLQuery = any;
|
|
||||||
|
|
||||||
// `opentype.js` monkey patches
|
// `opentype.js` monkey patches
|
||||||
|
|
||||||
declare module 'opentype.js' {
|
declare module 'opentype.js' {
|
||||||
|
@ -224,7 +216,7 @@ class TextDemoController extends AppController<TextDemoView> {
|
||||||
this.view.then(view => view.attachText());
|
this.view.then(view => view.attachText());
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTiming(newTimes: {atlasRendering: number, compositing: number}) {
|
updateTimings(newTimes: Timings) {
|
||||||
this.fpsLabel.innerHTML =
|
this.fpsLabel.innerHTML =
|
||||||
`${newTimes.atlasRendering} ms atlas, ${newTimes.compositing} ms compositing`;
|
`${newTimes.atlasRendering} ms atlas, ${newTimes.compositing} ms compositing`;
|
||||||
}
|
}
|
||||||
|
@ -262,29 +254,10 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
this.translation = glmatrix.vec2.create();
|
this.translation = glmatrix.vec2.create();
|
||||||
|
|
||||||
this.canvas.addEventListener('wheel', event => this.onWheel(event), false);
|
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() {
|
protected initContext() {
|
||||||
super.initContext();
|
super.initContext();
|
||||||
|
|
||||||
// Set up our timer queries for profiling.
|
|
||||||
this.atlasRenderingTimerQuery = this.timerQueryExt.createQueryEXT();
|
|
||||||
this.compositingTimerQuery = this.timerQueryExt.createQueryEXT();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadPathData(pathCount: number) {
|
uploadPathData(pathCount: number) {
|
||||||
|
@ -298,14 +271,6 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
this.pathColorsBufferTexture.upload(this.gl, pathColors);
|
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.
|
/// Lays out glyphs on the canvas.
|
||||||
private layoutGlyphs() {
|
private layoutGlyphs() {
|
||||||
const lineGlyphs = this.appController.lineGlyphs;
|
const lineGlyphs = this.appController.lineGlyphs;
|
||||||
|
@ -488,13 +453,6 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
this.setDirty();
|
this.setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private setDirty() {
|
|
||||||
if (this.dirty)
|
|
||||||
return;
|
|
||||||
this.dirty = true;
|
|
||||||
window.requestAnimationFrame(() => this.redraw());
|
|
||||||
}
|
|
||||||
|
|
||||||
private onWheel(event: WheelEvent) {
|
private onWheel(event: WheelEvent) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
@ -536,80 +494,6 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
this.setDirty();
|
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) {
|
setIdentityTexScaleUniform(uniforms: UniformMap) {
|
||||||
this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
|
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]);
|
this.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderDirect() {
|
protected compositeIfNecessary() {
|
||||||
// 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() {
|
|
||||||
// Set up composite state.
|
// Set up composite state.
|
||||||
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
|
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
|
||||||
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
||||||
|
@ -796,11 +591,14 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
return this.appController.atlas.usedSize;
|
return this.appController.atlas.usedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
private antialiasingStrategy: AntialiasingStrategy;
|
protected createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number):
|
||||||
|
AntialiasingStrategy {
|
||||||
|
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel);
|
||||||
|
}
|
||||||
|
|
||||||
private atlasRenderingTimerQuery: WebGLQuery;
|
protected updateTimings(timings: Timings) {
|
||||||
private compositingTimerQuery: WebGLQuery;
|
this.appController.updateTimings(timings);
|
||||||
private timerQueryPollInterval: number | null;
|
}
|
||||||
|
|
||||||
private translation: glmatrix.vec2;
|
private translation: glmatrix.vec2;
|
||||||
|
|
||||||
|
@ -816,8 +614,6 @@ class TextDemoView extends MonochromePathfinderView {
|
||||||
atlasTransformBuffer: PathfinderBufferTexture;
|
atlasTransformBuffer: PathfinderBufferTexture;
|
||||||
|
|
||||||
appController: TextDemoController;
|
appController: TextDemoController;
|
||||||
|
|
||||||
private dirty: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AntialiasingStrategyTable {
|
interface AntialiasingStrategyTable {
|
||||||
|
|
|
@ -10,13 +10,20 @@
|
||||||
|
|
||||||
import * as glmatrix from 'gl-matrix';
|
import * as glmatrix from 'gl-matrix';
|
||||||
|
|
||||||
|
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
||||||
import {QUAD_ELEMENTS, UniformMap} from './gl-utils';
|
import {QUAD_ELEMENTS, UniformMap} from './gl-utils';
|
||||||
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
||||||
import {PathfinderShaderProgram, SHADER_NAMES, ShaderMap} from './shader-loader';
|
import {PathfinderShaderProgram, SHADER_NAMES, ShaderMap} from './shader-loader';
|
||||||
import {ShaderProgramSource, UnlinkedShaderProgram} 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';
|
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([
|
const QUAD_POSITIONS: Float32Array = new Float32Array([
|
||||||
0.0, 1.0,
|
0.0, 1.0,
|
||||||
1.0, 1.0,
|
1.0, 1.0,
|
||||||
|
@ -31,6 +38,13 @@ const QUAD_TEX_COORDS: Float32Array = new Float32Array([
|
||||||
1.0, 0.0,
|
1.0, 0.0,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export interface Timings {
|
||||||
|
atlasRendering: number;
|
||||||
|
compositing: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class WebGLQuery {}
|
||||||
|
|
||||||
export abstract class PathfinderView {
|
export abstract class PathfinderView {
|
||||||
constructor(canvas: HTMLCanvasElement,
|
constructor(canvas: HTMLCanvasElement,
|
||||||
commonShaderSource: string,
|
commonShaderSource: string,
|
||||||
|
@ -45,10 +59,33 @@ export abstract class PathfinderView {
|
||||||
this.atlasTransformBuffer = new PathfinderBufferTexture(this.gl, 'uPathTransform');
|
this.atlasTransformBuffer = new PathfinderBufferTexture(this.gl, 'uPathTransform');
|
||||||
this.pathColorsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathColors');
|
this.pathColorsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathColors');
|
||||||
|
|
||||||
|
this.antialiasingStrategy = new NoAAStrategy(0);
|
||||||
|
this.antialiasingStrategy.init(this);
|
||||||
|
|
||||||
window.addEventListener('resize', () => this.resizeToFit(false), false);
|
window.addEventListener('resize', () => this.resizeToFit(false), false);
|
||||||
this.resizeToFit(true);
|
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) {
|
private resizeToFit(initialSize: boolean) {
|
||||||
const width = window.innerWidth;
|
const width = window.innerWidth;
|
||||||
const height = window.scrollY + window.innerHeight -
|
const height = window.scrollY + window.innerHeight -
|
||||||
|
@ -93,6 +130,10 @@ export abstract class PathfinderView {
|
||||||
this.quadElementsBuffer = unwrapNull(this.gl.createBuffer());
|
this.quadElementsBuffer = unwrapNull(this.gl.createBuffer());
|
||||||
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.quadElementsBuffer);
|
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.quadElementsBuffer);
|
||||||
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, QUAD_ELEMENTS, this.gl.STATIC_DRAW);
|
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>):
|
private compileShaders(commonSource: string, shaderSources: ShaderMap<ShaderProgramSource>):
|
||||||
|
@ -154,15 +195,194 @@ export abstract class PathfinderView {
|
||||||
this.gl.uniform2i(uniforms.uFramebufferSize, currentViewport[2], currentViewport[3]);
|
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 setTransformAndTexScaleUniformsForDest(uniforms: UniformMap): void;
|
||||||
abstract setTransformSTAndTexScaleUniformsForDest(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 destFramebuffer(): WebGLFramebuffer;
|
||||||
abstract get destDepthTexture(): WebGLTexture;
|
abstract get destDepthTexture(): WebGLTexture;
|
||||||
|
|
||||||
abstract get destAllocatedSize(): glmatrix.vec2;
|
abstract get destAllocatedSize(): glmatrix.vec2;
|
||||||
abstract get destUsedSize(): glmatrix.vec2;
|
abstract get destUsedSize(): glmatrix.vec2;
|
||||||
|
|
||||||
|
protected antialiasingStrategy: AntialiasingStrategy;
|
||||||
|
|
||||||
protected canvas: HTMLCanvasElement;
|
protected canvas: HTMLCanvasElement;
|
||||||
|
|
||||||
gl: WebGLRenderingContext;
|
gl: WebGLRenderingContext;
|
||||||
|
@ -185,6 +405,12 @@ export abstract class PathfinderView {
|
||||||
|
|
||||||
atlasTransformBuffer: PathfinderBufferTexture;
|
atlasTransformBuffer: PathfinderBufferTexture;
|
||||||
protected pathColorsBufferTexture: PathfinderBufferTexture;
|
protected pathColorsBufferTexture: PathfinderBufferTexture;
|
||||||
|
|
||||||
|
private atlasRenderingTimerQuery: WebGLQuery;
|
||||||
|
private compositingTimerQuery: WebGLQuery;
|
||||||
|
private timerQueryPollInterval: number | null;
|
||||||
|
|
||||||
|
protected dirty: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class MonochromePathfinderView extends PathfinderView {
|
export abstract class MonochromePathfinderView extends PathfinderView {
|
||||||
|
|
Loading…
Reference in New Issue