Separate the GPU rendering component of views from the view objects

themselves in the demo.

This (mostly) enables rendering outside a view.
This commit is contained in:
Patrick Walton 2017-10-16 19:29:13 -07:00
parent 976b924842
commit 03ee672787
10 changed files with 1231 additions and 1000 deletions

View File

@ -20,6 +20,7 @@ import PathfinderBufferTexture from "./buffer-texture";
import {PerspectiveCamera} from "./camera";
import {UniformMap} from './gl-utils';
import {PathfinderMeshData} from "./meshes";
import {Renderer} from './renderer';
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
import SSAAStrategy from "./ssaa-strategy";
import {BUILTIN_FONT_URI, ExpandedMeshData} from "./text";
@ -252,78 +253,171 @@ class ThreeDController extends DemoAppController<ThreeDView> {
return this.baseMeshes.expand([glyphIndex + 1]);
});
this.view.then(view => {
view.uploadPathColors(this.expandedMeshes.length);
view.uploadPathTransforms(this.expandedMeshes.length);
view.attachMeshes(this.expandedMeshes);
});
this.view.then(view => view.attachMeshes(this.expandedMeshes));
});
}
}
class ThreeDView extends DemoView {
destFramebuffer: WebGLFramebuffer | null = null;
renderer: ThreeDRenderer;
camera: PerspectiveCamera;
appController: ThreeDController;
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.clone([1.0, 1.0]);
protected directCurveProgramName: keyof ShaderMap<void> = 'direct3DCurve';
protected directInteriorProgramName: keyof ShaderMap<void> = 'direct3DInterior';
protected depthFunction: number = this.gl.LESS;
protected get pathIDsAreInstanced(): boolean {
return true;
protected get camera(): PerspectiveCamera {
return this.renderer.camera;
}
private _scale: number;
private appController: ThreeDController;
private cubeVertexPositionBuffer: WebGLBuffer;
private cubeIndexBuffer: WebGLBuffer;
constructor(appController: ThreeDController,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>) {
super(commonShaderSource, shaderSources);
this.appController = appController;
this.renderer = new ThreeDRenderer(this);
this.camera = new PerspectiveCamera(this.canvas, {
this.resizeToFit(true);
}
}
class ThreeDRenderer extends Renderer {
renderContext: ThreeDView;
camera: PerspectiveCamera;
get destFramebuffer(): WebGLFramebuffer | null {
return null;
}
get destAllocatedSize(): glmatrix.vec2 {
return glmatrix.vec2.clone([
this.renderContext.canvas.width,
this.renderContext.canvas.height,
]);
}
get destUsedSize(): glmatrix.vec2 {
return this.destAllocatedSize;
}
protected get directCurveProgramName(): keyof ShaderMap<void> {
return 'direct3DCurve';
}
protected get directInteriorProgramName(): keyof ShaderMap<void> {
return 'direct3DInterior';
}
protected get depthFunction(): number {
return this.renderContext.gl.LESS;
}
protected get pathIDsAreInstanced(): boolean {
return true;
}
protected get worldTransform() {
return this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE);
}
protected get usedSizeFactor(): glmatrix.vec2 {
return glmatrix.vec2.clone([1.0, 1.0]);
}
private cubeVertexPositionBuffer: WebGLBuffer;
private cubeIndexBuffer: WebGLBuffer;
constructor(renderContext: ThreeDView) {
super(renderContext);
this.camera = new PerspectiveCamera(renderContext.canvas, {
innerCollisionExtent: MONUMENT_SCALE[0],
});
this.camera.onChange = () => this.setDirty();
this.camera.onChange = () => renderContext.setDirty();
this.cubeVertexPositionBuffer = unwrapNull(this.gl.createBuffer());
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, CUBE_VERTEX_POSITIONS, this.gl.STATIC_DRAW);
this.cubeVertexPositionBuffer = unwrapNull(renderContext.gl.createBuffer());
renderContext.gl.bindBuffer(renderContext.gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
renderContext.gl.bufferData(renderContext.gl.ARRAY_BUFFER,
CUBE_VERTEX_POSITIONS,
renderContext.gl.STATIC_DRAW);
this.cubeIndexBuffer = unwrapNull(this.gl.createBuffer());
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer);
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, CUBE_INDICES, this.gl.STATIC_DRAW);
this.cubeIndexBuffer = unwrapNull(renderContext.gl.createBuffer());
renderContext.gl.bindBuffer(renderContext.gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer);
renderContext.gl.bufferData(renderContext.gl.ELEMENT_ARRAY_BUFFER,
CUBE_INDICES,
renderContext.gl.STATIC_DRAW);
}
setHintsUniform(uniforms: UniformMap): void {
this.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
}
attachMeshes(expandedMeshes: PathfinderMeshData[]) {
super.attachMeshes(expandedMeshes);
pathBoundingRects(objectIndex: number): Float32Array {
panic("ThreeDView.pathBoundingRects(): TODO");
return glmatrix.vec4.create();
this.uploadPathColors(expandedMeshes.length);
this.uploadPathTransforms(expandedMeshes.length);
}
pathCountForObject(objectIndex: number): number {
return this.appController.meshDescriptors[objectIndex].positions.length;
return this.renderContext.appController.meshDescriptors[objectIndex].positions.length;
}
pathBoundingRects(objectIndex: number): Float32Array {
panic("ThreeDRenderer.pathBoundingRects(): TODO");
return glmatrix.vec4.create();
}
setHintsUniform(uniforms: UniformMap): void {
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
}
protected drawSceneryIfNecessary(): void {
// Set up the cube VBO.
const shaderProgram = this.renderContext.shaderPrograms.demo3DMonument;
this.renderContext.gl.useProgram(shaderProgram.program);
this.renderContext.gl.bindBuffer(this.renderContext.gl.ARRAY_BUFFER,
this.cubeVertexPositionBuffer);
this.renderContext.gl.bindBuffer(this.renderContext.gl.ELEMENT_ARRAY_BUFFER,
this.cubeIndexBuffer);
this.renderContext.gl.vertexAttribPointer(shaderProgram.attributes.aPosition,
3,
this.renderContext.gl.FLOAT,
false,
0,
0);
this.renderContext.gl.enableVertexAttribArray(shaderProgram.attributes.aPosition);
// Set uniforms for the monument.
const transform = this.calculateWorldTransform(MONUMENT_TRANSLATION, MONUMENT_SCALE);
this.renderContext.gl.uniformMatrix4fv(shaderProgram.uniforms.uTransform,
false,
transform);
this.renderContext.gl.uniform4f(shaderProgram.uniforms.uColor,
MONUMENT_COLOR[0],
MONUMENT_COLOR[1],
MONUMENT_COLOR[2],
1.0);
// Set state for the monument.
this.renderContext.gl.enable(this.renderContext.gl.DEPTH_TEST);
this.renderContext.gl.depthMask(true);
this.renderContext.gl.disable(this.renderContext.gl.SCISSOR_TEST);
// Draw the monument!
this.renderContext.gl.drawElements(this.renderContext.gl.TRIANGLES,
CUBE_INDICES.length,
this.renderContext.gl.UNSIGNED_SHORT,
0);
// Clear to avoid Z-fighting.
this.renderContext.gl.clearDepth(1.0);
this.renderContext.gl.clear(this.renderContext.gl.DEPTH_BUFFER_BIT);
}
protected compositeIfNecessary(): void {}
protected pathColorsForObject(objectIndex: number): Uint8Array {
return TEXT_COLOR;
}
protected pathTransformsForObject(objectIndex: number): Float32Array {
const meshDescriptor = this.appController.meshDescriptors[objectIndex];
const meshDescriptor = this.renderContext.appController.meshDescriptors[objectIndex];
const pathCount = this.pathCountForObject(objectIndex);
const pathTransforms = new Float32Array(4 * (pathCount + 1));
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
@ -333,6 +427,10 @@ class ThreeDView extends DemoView {
return pathTransforms;
}
protected meshInstanceCountForObject(objectIndex: number): number {
return this.renderContext.appController.meshDescriptors[objectIndex].positions.length;
}
protected createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType):
@ -342,57 +440,20 @@ class ThreeDView extends DemoView {
throw new PathfinderError("Unsupported antialiasing type!");
}
protected drawSceneryIfNecessary(): void {
// Set up the cube VBO.
const shaderProgram = this.shaderPrograms.demo3DMonument;
this.gl.useProgram(shaderProgram.program);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer);
this.gl.vertexAttribPointer(shaderProgram.attributes.aPosition,
3,
this.gl.FLOAT,
false,
0,
0);
this.gl.enableVertexAttribArray(shaderProgram.attributes.aPosition);
// Set uniforms for the monument.
const transform = this.calculateWorldTransform(MONUMENT_TRANSLATION, MONUMENT_SCALE);
this.gl.uniformMatrix4fv(shaderProgram.uniforms.uTransform, false, transform);
this.gl.uniform4f(shaderProgram.uniforms.uColor,
MONUMENT_COLOR[0],
MONUMENT_COLOR[1],
MONUMENT_COLOR[2],
1.0);
// Set state for the monument.
this.gl.enable(this.gl.DEPTH_TEST);
this.gl.depthMask(true);
this.gl.disable(this.gl.SCISSOR_TEST);
// Draw the monument!
this.gl.drawElements(this.gl.TRIANGLES, CUBE_INDICES.length, this.gl.UNSIGNED_SHORT, 0);
// Clear to avoid Z-fighting.
this.gl.clearDepth(1.0);
this.gl.clear(this.gl.DEPTH_BUFFER_BIT);
}
protected compositeIfNecessary(): void {}
protected newTimingsReceived() {
this.appController.newTimingsReceived(_.pick(this.lastTimings, ['rendering']));
}
protected clearForDirectRendering(): void {
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
this.gl.clearDepth(1.0);
this.gl.depthMask(true);
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
this.renderContext.gl.clearColor(1.0, 1.0, 1.0, 1.0);
this.renderContext.gl.clearDepth(1.0);
this.renderContext.gl.depthMask(true);
this.renderContext.gl.clear(this.renderContext.gl.COLOR_BUFFER_BIT |
this.renderContext.gl.DEPTH_BUFFER_BIT);
}
protected getModelviewTransform(objectIndex: number): glmatrix.mat4 {
const textFrameIndex = this.appController.meshDescriptors[objectIndex].textFrameIndex;
const textFrameIndex = this.renderContext
.appController
.meshDescriptors[objectIndex]
.textFrameIndex;
const transform = glmatrix.mat4.create();
glmatrix.mat4.rotateY(transform, transform, Math.PI / 2.0 * textFrameIndex);
glmatrix.mat4.translate(transform, transform, TEXT_TRANSLATION);
@ -401,7 +462,11 @@ class ThreeDView extends DemoView {
// Cheap but effective backface culling.
protected shouldRenderObject(objectIndex: number): boolean {
const textFrameIndex = this.appController.meshDescriptors[objectIndex].textFrameIndex;
const textFrameIndex = this.renderContext
.appController
.meshDescriptors[objectIndex]
.textFrameIndex;
const translation = this.camera.translation;
const extent = TEXT_TRANSLATION[2] * TEXT_SCALE[2];
switch (textFrameIndex) {
@ -411,26 +476,19 @@ class ThreeDView extends DemoView {
default: return translation[0] > extent;
}
}
protected meshInstanceCountForObject(objectIndex: number): number {
return this.appController.meshDescriptors[objectIndex].positions.length;
}
get destAllocatedSize(): glmatrix.vec2 {
return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height);
}
get destUsedSize(): glmatrix.vec2 {
return this.destAllocatedSize;
protected newTimingsReceived() {
const newTimings: Timings = _.pick(this.lastTimings, ['rendering']);
this.renderContext.appController.newTimingsReceived(newTimings);
}
private calculateWorldTransform(modelviewTranslation: glmatrix.vec3,
modelviewScale: glmatrix.vec3):
glmatrix.mat4 {
const canvas = this.renderContext.canvas;
const projection = glmatrix.mat4.create();
glmatrix.mat4.perspective(projection,
FOV / 180.0 * Math.PI,
this.canvas.width / this.canvas.height,
canvas.width / canvas.height,
NEAR_CLIP_PLANE,
FAR_CLIP_PLANE);
@ -444,10 +502,6 @@ class ThreeDView extends DemoView {
glmatrix.mat4.mul(transform, projection, modelview);
return transform;
}
protected get worldTransform() {
return this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE);
}
}
function main() {

View File

@ -10,7 +10,8 @@
import * as glmatrix from 'gl-matrix';
import {DemoView, Renderer} from './view';
import {Renderer} from './renderer';
import {DemoView} from './view';
export type AntialiasingStrategyName = 'none' | 'ssaa' | 'xcaa';

View File

@ -19,6 +19,7 @@ import PathfinderBufferTexture from './buffer-texture';
import {OrthographicCamera} from './camera';
import {UniformMap} from './gl-utils';
import {PathfinderMeshData} from "./meshes";
import {Renderer} from './renderer';
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
import SSAAStrategy from './ssaa-strategy';
import {BUILTIN_FONT_URI, ExpandedMeshData, GlyphStore, PathfinderFont, TextFrame} from "./text";
@ -127,8 +128,6 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
this.expandedMeshes = expandedMeshes;
this.view.then(view => {
view.uploadPathColors(1);
view.uploadPathTransforms(1);
view.attachMeshes([expandedMeshes.meshes]);
});
});
@ -237,10 +236,44 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
}
class BenchmarkTestView extends DemoView {
destFramebuffer: WebGLFramebuffer | null = null;
readonly renderer: BenchmarkRenderer;
readonly appController: BenchmarkAppController;
get camera(): OrthographicCamera {
return this.renderer.camera;
}
set pixelsPerEm(newPPEM: number) {
this.renderer.pixelsPerEm = newPPEM;
}
set renderingPromiseCallback(newCallback: (time: number) => void) {
this.renderer.renderingPromiseCallback = newCallback;
}
constructor(appController: BenchmarkAppController,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>) {
super(commonShaderSource, shaderSources);
this.appController = appController;
this.renderer = new BenchmarkRenderer(this);
this.resizeToFit(true);
}
}
class BenchmarkRenderer extends Renderer {
renderContext: BenchmarkTestView;
camera: OrthographicCamera;
renderingPromiseCallback: ((time: number) => void) | null;
get destFramebuffer(): WebGLFramebuffer | null {
return null;
}
get bgColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 0.0]);
}
@ -249,50 +282,99 @@ class BenchmarkTestView extends DemoView {
return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
}
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.clone([1.0, 1.0]);
get destAllocatedSize(): glmatrix.vec2 {
const canvas = this.renderContext.canvas;
return glmatrix.vec2.clone([canvas.width, canvas.height]);
}
protected directCurveProgramName: keyof ShaderMap<void> = 'directCurve';
protected directInteriorProgramName: keyof ShaderMap<void> = 'directInterior';
protected depthFunction: number = this.gl.GREATER;
protected camera: OrthographicCamera;
private _pixelsPerEm: number = 32.0;
private readonly appController: BenchmarkAppController;
get destUsedSize(): glmatrix.vec2 {
return this.destAllocatedSize;
}
get emboldenAmount(): glmatrix.vec2 {
return this.stemDarkeningAmount;
}
get pixelsPerEm(): number {
return this._pixelsPerEm;
}
set pixelsPerEm(newPixelsPerEm: number) {
this._pixelsPerEm = newPixelsPerEm;
this.uploadPathTransforms(1);
this.renderContext.setDirty();
}
protected get usedSizeFactor(): glmatrix.vec2 {
return glmatrix.vec2.clone([1.0, 1.0]);
}
protected get directCurveProgramName(): keyof ShaderMap<void> {
return 'directCurve';
}
protected get directInteriorProgramName(): keyof ShaderMap<void> {
return 'directInterior';
}
protected get depthFunction(): number {
return this.renderContext.gl.GREATER;
}
protected get worldTransform() {
const canvas = this.renderContext.canvas;
const transform = glmatrix.mat4.create();
const translation = this.camera.translation;
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [2.0 / canvas.width, 2.0 / canvas.height, 1.0]);
glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
const pixelsPerUnit = this.pixelsPerUnit;
glmatrix.mat4.scale(transform, transform, [pixelsPerUnit, pixelsPerUnit, 1.0]);
return transform;
}
private _pixelsPerEm: number = 32.0;
private get pixelsPerUnit(): number {
const font = unwrapNull(this.renderContext.appController.font);
return this._pixelsPerEm / font.opentypeFont.unitsPerEm;
}
private get stemDarkeningAmount(): glmatrix.vec2 {
return computeStemDarkeningAmount(this._pixelsPerEm, this.pixelsPerUnit);
}
constructor(appController: BenchmarkAppController,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>) {
super(commonShaderSource, shaderSources);
constructor(renderContext: BenchmarkTestView) {
super(renderContext);
this.appController = appController;
this.camera = new OrthographicCamera(this.canvas);
this.camera.onPan = () => this.setDirty();
this.camera.onZoom = () => this.setDirty();
this.camera = new OrthographicCamera(renderContext.canvas);
this.camera.onPan = () => renderContext.setDirty();
this.camera.onZoom = () => renderContext.setDirty();
}
setHintsUniform(uniforms: UniformMap): void {
this.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
attachMeshes(meshes: PathfinderMeshData[]): void {
super.attachMeshes(meshes);
this.uploadPathColors(1);
this.uploadPathTransforms(1);
}
pathCountForObject(objectIndex: number): number {
return STRING.length;
}
pathBoundingRects(objectIndex: number): Float32Array {
const font = unwrapNull(this.appController.font);
const appController = this.renderContext.appController;
const font = unwrapNull(appController.font);
const boundingRects = new Float32Array((STRING.length + 1) * 4);
for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
const glyphID = unwrapNull(this.appController.textRun).glyphIDs[glyphIndex];
const glyphID = unwrapNull(appController.textRun).glyphIDs[glyphIndex];
const metrics = font.metricsForGlyph(glyphID);
if (metrics == null)
@ -307,8 +389,8 @@ class BenchmarkTestView extends DemoView {
return boundingRects;
}
pathCountForObject(objectIndex: number): number {
return STRING.length;
setHintsUniform(uniforms: UniformMap): void {
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
}
protected createAAStrategy(aaType: AntialiasingStrategyName,
@ -324,6 +406,14 @@ class BenchmarkTestView extends DemoView {
// TODO(pcwalton)
}
protected renderingFinished(): void {
if (this.renderingPromiseCallback == null)
return;
const glyphCount = unwrapNull(this.renderContext.appController.textRun).glyphIDs.length;
const usPerGlyph = this.lastTimings.rendering * 1000.0 / glyphCount;
this.renderingPromiseCallback(usPerGlyph);
}
protected pathColorsForObject(objectIndex: number): Uint8Array {
const pathColors = new Uint8Array(4 * (STRING.length + 1));
for (let pathIndex = 0; pathIndex < STRING.length; pathIndex++)
@ -332,20 +422,21 @@ class BenchmarkTestView extends DemoView {
}
protected pathTransformsForObject(objectIndex: number): Float32Array {
const appController = this.renderContext.appController;
const canvas = this.renderContext.canvas;
const font = unwrapNull(appController.font);
const pathTransforms = new Float32Array(4 * (STRING.length + 1));
let currentX = 0, currentY = 0;
const availableWidth = this.canvas.width / this.pixelsPerUnit;
const lineHeight = unwrapNull(this.appController.font).opentypeFont.lineHeight();
const availableWidth = canvas.width / this.pixelsPerUnit;
const lineHeight = font.opentypeFont.lineHeight();
for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
const glyphID = unwrapNull(this.appController.textRun).glyphIDs[glyphIndex];
const glyphID = unwrapNull(appController.textRun).glyphIDs[glyphIndex];
pathTransforms.set([1, 1, currentX, currentY], (glyphIndex + 1) * 4);
currentX += unwrapNull(this.appController.font).opentypeFont
.glyphs
.get(glyphID)
.advanceWidth;
currentX += font.opentypeFont.glyphs.get(glyphID).advanceWidth;
if (currentX > availableWidth) {
currentX = 0;
currentY += lineHeight;
@ -354,52 +445,6 @@ class BenchmarkTestView extends DemoView {
return pathTransforms;
}
protected renderingFinished(): void {
if (this.renderingPromiseCallback != null) {
const glyphCount = unwrapNull(this.appController.textRun).glyphIDs.length;
const usPerGlyph = this.lastTimings.rendering * 1000.0 / glyphCount;
this.renderingPromiseCallback(usPerGlyph);
}
}
get destAllocatedSize(): glmatrix.vec2 {
return glmatrix.vec2.clone([this.canvas.width, this.canvas.height]);
}
get destUsedSize(): glmatrix.vec2 {
return this.destAllocatedSize;
}
protected get worldTransform() {
const transform = glmatrix.mat4.create();
const translation = this.camera.translation;
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform,
transform,
[2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]);
glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
const pixelsPerUnit = this.pixelsPerUnit;
glmatrix.mat4.scale(transform, transform, [pixelsPerUnit, pixelsPerUnit, 1.0]);
return transform;
}
private get pixelsPerUnit(): number {
return this._pixelsPerEm / unwrapNull(this.appController.font).opentypeFont.unitsPerEm;
}
get pixelsPerEm(): number {
return this._pixelsPerEm;
}
set pixelsPerEm(newPixelsPerEm: number) {
this._pixelsPerEm = newPixelsPerEm;
this.uploadPathTransforms(1);
this.setDirty();
}
}
function computeMedian(values: number[]): number | null {

View File

@ -216,6 +216,8 @@ class MeshDebuggerView extends PathfinderView {
this.camera.onPan = () => this.setDirty();
this.camera.onZoom = () => this.setDirty();
this.resizeToFit(true);
}
attachMeshes() {
@ -277,11 +279,6 @@ class MeshDebuggerView extends PathfinderView {
const lowerRightPosition = unwrapNull(getPosition(positions, lowerRightIndex));
const lowerControlPointPosition = getPosition(positions, lowerControlPointIndex);
/*const upperCurve = upperControlPointPosition != null;
const lowerCurve = lowerControlPointPosition != null;
const upperNormals = getNormals(normals, normalIndices, upperCurve, 'upper');
const lowerNormals = getNormals(normals, normalIndices, lowerCurve, 'lower');*/
drawVertexIfNecessary(context,
drawnVertices,
upperLeftIndex,
@ -303,28 +300,6 @@ class MeshDebuggerView extends PathfinderView {
lowerRightPosition,
invScaleFactor);
/*
drawNormalIfNecessary(context,
drawnNormals,
upperLeftPosition,
upperNormals.left,
invScaleFactor);
drawNormalIfNecessary(context,
drawnNormals,
upperRightPosition,
upperNormals.right,
invScaleFactor);
drawNormalIfNecessary(context,
drawnNormals,
lowerLeftPosition,
lowerNormals.left,
invScaleFactor);
drawNormalIfNecessary(context,
drawnNormals,
lowerRightPosition,
lowerNormals.right,
invScaleFactor);*/
context.beginPath();
context.moveTo(upperLeftPosition[0], upperLeftPosition[1]);
if (upperControlPointPosition != null) {

522
demo/client/src/renderer.ts Normal file
View File

@ -0,0 +1,522 @@
// pathfinder/client/src/renderer.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
import * as glmatrix from 'gl-matrix';
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy';
import {StemDarkeningMode, SubpixelAAType} from './aa-strategy';
import PathfinderBufferTexture from "./buffer-texture";
import {UniformMap} from './gl-utils';
import {PathfinderMeshBuffers, PathfinderMeshData} from "./meshes";
import {ShaderMap} from './shader-loader';
import {UINT32_SIZE, unwrapNull} from './utils';
import {RenderContext, Timings} from "./view";
const MAX_PATHS: number = 65535;
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;
export abstract class Renderer {
readonly renderContext: RenderContext;
readonly pathTransformBufferTextures: PathfinderBufferTexture[];
meshes: PathfinderMeshBuffers[];
meshData: PathfinderMeshData[];
get emboldenAmount(): glmatrix.vec2 {
return glmatrix.vec2.create();
}
get bgColor(): glmatrix.vec4 | null {
return null;
}
get fgColor(): glmatrix.vec4 | null {
return null;
}
abstract get destFramebuffer(): WebGLFramebuffer | null;
abstract get destAllocatedSize(): glmatrix.vec2;
abstract get destUsedSize(): glmatrix.vec2;
protected antialiasingStrategy: AntialiasingStrategy | null;
protected lastTimings: Timings;
protected pathColorsBufferTextures: PathfinderBufferTexture[];
protected get pathIDsAreInstanced(): boolean {
return false;
}
protected abstract get depthFunction(): GLenum;
protected abstract get directCurveProgramName(): keyof ShaderMap<void>;
protected abstract get directInteriorProgramName(): keyof ShaderMap<void>;
protected abstract get usedSizeFactor(): glmatrix.vec2;
protected abstract get worldTransform(): glmatrix.mat4;
private instancedPathIDVBO: WebGLBuffer | null;
private timerQueryPollInterval: number | null;
constructor(renderContext: RenderContext) {
this.renderContext = renderContext;
this.lastTimings = { rendering: 0, compositing: 0 };
this.pathTransformBufferTextures = [];
this.pathColorsBufferTextures = [];
if (this.pathIDsAreInstanced)
this.initInstancedPathIDVBO();
this.antialiasingStrategy = new NoAAStrategy(0, 'none');
this.antialiasingStrategy.init(this);
}
attachMeshes(meshes: PathfinderMeshData[]): void {
const renderContext = this.renderContext;
this.meshData = meshes;
this.meshes = meshes.map(meshes => new PathfinderMeshBuffers(renderContext.gl, meshes));
unwrapNull(this.antialiasingStrategy).attachMeshes(this);
}
abstract pathBoundingRects(objectIndex: number): Float32Array;
abstract setHintsUniform(uniforms: UniformMap): void;
redraw(): void {
const renderContext = this.renderContext;
if (this.meshes == null)
return;
// Start timing rendering.
if (this.timerQueryPollInterval == null) {
renderContext.timerQueryExt.beginQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT,
renderContext.atlasRenderingTimerQuery);
}
// Prepare for direct rendering.
const antialiasingStrategy = unwrapNull(this.antialiasingStrategy);
antialiasingStrategy.prepare(this);
// Clear.
this.clearForDirectRendering();
// Draw "scenery" (used in the 3D view).
this.drawSceneryIfNecessary();
// Perform direct rendering (Loop-Blinn).
if (antialiasingStrategy.shouldRenderDirect)
this.renderDirect();
// Antialias.
antialiasingStrategy.antialias(this);
// End the timer, and start a new one.
if (this.timerQueryPollInterval == null) {
renderContext.timerQueryExt.endQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT);
renderContext.timerQueryExt.beginQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT,
renderContext.compositingTimerQuery);
}
antialiasingStrategy.resolve(this);
// Draw the glyphs with the resolved atlas to the default framebuffer.
this.compositeIfNecessary();
// Finish timing.
this.finishTiming();
}
setAntialiasingOptions(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType,
stemDarkening: StemDarkeningMode) {
this.antialiasingStrategy = this.createAAStrategy(aaType,
aaLevel,
subpixelAA,
stemDarkening);
this.antialiasingStrategy.init(this);
if (this.meshData != null)
this.antialiasingStrategy.attachMeshes(this);
this.renderContext.setDirty();
}
canvasResized() {
if (this.antialiasingStrategy != null)
this.antialiasingStrategy.init(this);
}
setFramebufferSizeUniform(uniforms: UniformMap) {
const renderContext = this.renderContext;
const currentViewport = renderContext.gl.getParameter(renderContext.gl.VIEWPORT);
renderContext.gl.uniform2i(uniforms.uFramebufferSize,
currentViewport[2],
currentViewport[3]);
}
setTransformAndTexScaleUniformsForDest(uniforms: UniformMap): void {
const renderContext = this.renderContext;
const usedSize = this.usedSizeFactor;
const transform = glmatrix.mat4.create();
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [2.0 * usedSize[0], 2.0 * usedSize[1], 1.0]);
renderContext.gl.uniformMatrix4fv(uniforms.uTransform, false, transform);
renderContext.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
}
setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap): void {
const renderContext = this.renderContext;
const usedSize = this.usedSizeFactor;
renderContext.gl.uniform4f(uniforms.uTransformST,
2.0 * usedSize[0],
2.0 * usedSize[1],
-1.0,
-1.0);
renderContext.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
}
setTransformSTUniform(uniforms: UniformMap, objectIndex: number) {
// FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an ST matrix is ugly and fragile.
// Refactor.
const renderContext = this.renderContext;
const transform = glmatrix.mat4.clone(this.worldTransform);
glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex));
const translation = glmatrix.vec4.clone([transform[12], transform[13], 0.0, 1.0]);
renderContext.gl.uniform4f(uniforms.uTransformST,
transform[0],
transform[5],
transform[12],
transform[13]);
}
uploadPathColors(objectCount: number) {
const renderContext = this.renderContext;
this.pathColorsBufferTextures = [];
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
const pathColorsBufferTexture = new PathfinderBufferTexture(renderContext.gl,
'uPathColors');
const pathColors = this.pathColorsForObject(objectIndex);
pathColorsBufferTexture.upload(renderContext.gl, pathColors);
this.pathColorsBufferTextures.push(pathColorsBufferTexture);
}
}
uploadPathTransforms(objectCount: number) {
const renderContext = this.renderContext;
this.pathTransformBufferTextures.splice(0);
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
const pathTransformBufferTexture = new PathfinderBufferTexture(renderContext.gl,
'uPathTransform');
const pathTransforms = this.pathTransformsForObject(objectIndex);
pathTransformBufferTexture.upload(renderContext.gl, pathTransforms);
this.pathTransformBufferTextures.push(pathTransformBufferTexture);
}
}
protected abstract createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType,
stemDarkening: StemDarkeningMode):
AntialiasingStrategy;
protected abstract compositeIfNecessary(): void;
protected abstract pathColorsForObject(objectIndex: number): Uint8Array;
protected abstract pathTransformsForObject(objectIndex: number): Float32Array;
protected drawSceneryIfNecessary(): void {}
protected clearForDirectRendering(): void {
const renderContext = this.renderContext;
renderContext.gl.clearColor(1.0, 1.0, 1.0, 1.0);
renderContext.gl.clearDepth(0.0);
renderContext.gl.depthMask(true);
renderContext.gl.clear(renderContext.gl.COLOR_BUFFER_BIT |
renderContext.gl.DEPTH_BUFFER_BIT);
}
protected getModelviewTransform(pathIndex: number): glmatrix.mat4 {
return glmatrix.mat4.create();
}
protected meshInstanceCountForObject(objectIndex: number): number {
return 1;
}
// FIXME(pcwalton): Merge with `meshInstanceCountForObject`?
protected shouldRenderObject(objectIndex: number): boolean {
return true;
}
/// Called whenever new GPU timing statistics are available.
protected newTimingsReceived() {}
private renderDirect() {
const renderContext = this.renderContext;
for (let objectIndex = 0; objectIndex < this.meshes.length; objectIndex++) {
if (!this.shouldRenderObject(objectIndex))
continue;
const meshes = this.meshes[objectIndex];
let instanceCount: number | null;
if (!this.pathIDsAreInstanced)
instanceCount = null;
else
instanceCount = this.meshInstanceCountForObject(objectIndex);
// Set up implicit cover state.
renderContext.gl.depthFunc(this.depthFunction);
renderContext.gl.depthMask(true);
renderContext.gl.enable(renderContext.gl.DEPTH_TEST);
renderContext.gl.disable(renderContext.gl.BLEND);
// Set up the implicit cover interior VAO.
//
// TODO(pcwalton): Cache these.
const directInteriorProgram =
renderContext.shaderPrograms[this.directInteriorProgramName];
const implicitCoverInteriorVAO = renderContext.vertexArrayObjectExt.createVertexArrayOES();
renderContext.vertexArrayObjectExt.bindVertexArrayOES(implicitCoverInteriorVAO);
this.initImplicitCoverInteriorVAO(objectIndex);
// Draw direct interior parts.
this.setTransformUniform(directInteriorProgram.uniforms, objectIndex);
this.setFramebufferSizeUniform(directInteriorProgram.uniforms);
this.setHintsUniform(directInteriorProgram.uniforms);
this.pathColorsBufferTextures[objectIndex].bind(renderContext.gl,
directInteriorProgram.uniforms,
0);
this.pathTransformBufferTextures[objectIndex].bind(renderContext.gl,
directInteriorProgram.uniforms,
1);
let indexCount =
renderContext.gl.getBufferParameter(renderContext.gl.ELEMENT_ARRAY_BUFFER,
renderContext.gl.BUFFER_SIZE) / UINT32_SIZE;
if (instanceCount == null) {
renderContext.gl.drawElements(renderContext.gl.TRIANGLES,
indexCount,
renderContext.gl.UNSIGNED_INT,
0);
} else {
renderContext.instancedArraysExt
.drawElementsInstancedANGLE(renderContext.gl.TRIANGLES,
indexCount,
renderContext.gl.UNSIGNED_INT,
0,
instanceCount);
}
// Set up direct curve state.
renderContext.gl.depthMask(false);
renderContext.gl.enable(renderContext.gl.BLEND);
renderContext.gl.blendEquation(renderContext.gl.FUNC_ADD);
renderContext.gl.blendFuncSeparate(renderContext.gl.SRC_ALPHA,
renderContext.gl.ONE_MINUS_SRC_ALPHA,
renderContext.gl.ONE,
renderContext.gl.ONE);
// Set up the direct curve VAO.
//
// TODO(pcwalton): Cache these.
const directCurveProgram = renderContext.shaderPrograms[this.directCurveProgramName];
const implicitCoverCurveVAO = renderContext.vertexArrayObjectExt.createVertexArrayOES();
renderContext.vertexArrayObjectExt.bindVertexArrayOES(implicitCoverCurveVAO);
this.initImplicitCoverCurveVAO(objectIndex);
// Draw direct curve parts.
this.setTransformUniform(directCurveProgram.uniforms, objectIndex);
this.setFramebufferSizeUniform(directCurveProgram.uniforms);
this.setHintsUniform(directInteriorProgram.uniforms);
this.pathColorsBufferTextures[objectIndex].bind(renderContext.gl,
directCurveProgram.uniforms,
0);
this.pathTransformBufferTextures[objectIndex].bind(renderContext.gl,
directCurveProgram.uniforms,
1);
indexCount =
renderContext.gl.getBufferParameter(renderContext.gl.ELEMENT_ARRAY_BUFFER,
renderContext.gl.BUFFER_SIZE) / UINT32_SIZE;
if (instanceCount == null) {
renderContext.gl.drawElements(renderContext.gl.TRIANGLES,
indexCount,
renderContext.gl.UNSIGNED_INT,
0);
} else {
renderContext.instancedArraysExt
.drawElementsInstancedANGLE(renderContext.gl.TRIANGLES,
indexCount,
renderContext.gl.UNSIGNED_INT,
0,
instanceCount);
}
renderContext.vertexArrayObjectExt.bindVertexArrayOES(null);
}
}
private finishTiming() {
const renderContext = this.renderContext;
if (this.timerQueryPollInterval != null)
return;
renderContext.timerQueryExt.endQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT);
this.timerQueryPollInterval = window.setInterval(() => {
for (const queryName of ['atlasRenderingTimerQuery', 'compositingTimerQuery'] as
Array<'atlasRenderingTimerQuery' | 'compositingTimerQuery'>) {
if (renderContext.timerQueryExt
.getQueryObjectEXT(renderContext[queryName],
renderContext.timerQueryExt
.QUERY_RESULT_AVAILABLE_EXT) ===
0) {
return;
}
}
const atlasRenderingTime =
renderContext.timerQueryExt
.getQueryObjectEXT(renderContext.atlasRenderingTimerQuery,
renderContext.timerQueryExt.QUERY_RESULT_EXT);
const compositingTime =
renderContext.timerQueryExt
.getQueryObjectEXT(renderContext.compositingTimerQuery,
renderContext.timerQueryExt.QUERY_RESULT_EXT);
this.lastTimings = {
compositing: compositingTime / 1000000.0,
rendering: atlasRenderingTime / 1000000.0,
};
this.newTimingsReceived();
window.clearInterval(this.timerQueryPollInterval!);
this.timerQueryPollInterval = null;
}, TIME_INTERVAL_DELAY);
}
private initImplicitCoverCurveVAO(objectIndex: number): void {
const renderContext = this.renderContext;
const meshes = this.meshes[objectIndex];
const directCurveProgram = renderContext.shaderPrograms[this.directCurveProgramName];
renderContext.gl.useProgram(directCurveProgram.program);
renderContext.gl.bindBuffer(renderContext.gl.ARRAY_BUFFER, meshes.bVertexPositions);
renderContext.gl.vertexAttribPointer(directCurveProgram.attributes.aPosition,
2,
renderContext.gl.FLOAT,
false,
0,
0);
if (this.pathIDsAreInstanced)
renderContext.gl.bindBuffer(renderContext.gl.ARRAY_BUFFER, this.instancedPathIDVBO);
else
renderContext.gl.bindBuffer(renderContext.gl.ARRAY_BUFFER, meshes.bVertexPathIDs);
renderContext.gl.vertexAttribPointer(directCurveProgram.attributes.aPathID,
1,
renderContext.gl.UNSIGNED_SHORT,
false,
0,
0);
if (this.pathIDsAreInstanced) {
renderContext.instancedArraysExt
.vertexAttribDivisorANGLE(directCurveProgram.attributes.aPathID, 1);
}
renderContext.gl.bindBuffer(renderContext.gl.ARRAY_BUFFER, meshes.bVertexLoopBlinnData);
renderContext.gl.vertexAttribPointer(directCurveProgram.attributes.aTexCoord,
2,
renderContext.gl.UNSIGNED_BYTE,
false,
B_LOOP_BLINN_DATA_SIZE,
B_LOOP_BLINN_DATA_TEX_COORD_OFFSET);
renderContext.gl.vertexAttribPointer(directCurveProgram.attributes.aSign,
1,
renderContext.gl.BYTE,
false,
B_LOOP_BLINN_DATA_SIZE,
B_LOOP_BLINN_DATA_SIGN_OFFSET);
renderContext.gl.enableVertexAttribArray(directCurveProgram.attributes.aPosition);
renderContext.gl.enableVertexAttribArray(directCurveProgram.attributes.aTexCoord);
renderContext.gl.enableVertexAttribArray(directCurveProgram.attributes.aPathID);
renderContext.gl.enableVertexAttribArray(directCurveProgram.attributes.aSign);
renderContext.gl.bindBuffer(renderContext.gl.ELEMENT_ARRAY_BUFFER,
meshes.coverCurveIndices);
}
private initImplicitCoverInteriorVAO(objectIndex: number): void {
const renderContext = this.renderContext;
const meshes = this.meshes[objectIndex];
const directInteriorProgram = renderContext.shaderPrograms[this.directInteriorProgramName];
renderContext.gl.useProgram(directInteriorProgram.program);
renderContext.gl.bindBuffer(renderContext.gl.ARRAY_BUFFER, meshes.bVertexPositions);
renderContext.gl.vertexAttribPointer(directInteriorProgram.attributes.aPosition,
2,
renderContext.gl.FLOAT,
false,
0,
0);
if (this.pathIDsAreInstanced)
renderContext.gl.bindBuffer(renderContext.gl.ARRAY_BUFFER, this.instancedPathIDVBO);
else
renderContext.gl.bindBuffer(renderContext.gl.ARRAY_BUFFER, meshes.bVertexPathIDs);
renderContext.gl.vertexAttribPointer(directInteriorProgram.attributes.aPathID,
1,
renderContext.gl.UNSIGNED_SHORT,
false,
0,
0);
if (this.pathIDsAreInstanced) {
renderContext.instancedArraysExt
.vertexAttribDivisorANGLE(directInteriorProgram.attributes.aPathID, 1);
}
renderContext.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPosition);
renderContext.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPathID);
renderContext.gl.bindBuffer(renderContext.gl.ELEMENT_ARRAY_BUFFER,
meshes.coverInteriorIndices);
}
private initInstancedPathIDVBO(): void {
const renderContext = this.renderContext;
const pathIDs = new Uint16Array(MAX_PATHS);
for (let pathIndex = 0; pathIndex < MAX_PATHS; pathIndex++)
pathIDs[pathIndex] = pathIndex + 1;
this.instancedPathIDVBO = renderContext.gl.createBuffer();
renderContext.gl.bindBuffer(renderContext.gl.ARRAY_BUFFER, this.instancedPathIDVBO);
renderContext.gl.bufferData(renderContext.gl.ARRAY_BUFFER,
pathIDs,
renderContext.gl.STATIC_DRAW);
renderContext.gl.bindBuffer(renderContext.gl.ARRAY_BUFFER, null);
}
private setTransformUniform(uniforms: UniformMap, objectIndex: number) {
const transform = glmatrix.mat4.clone(this.worldTransform);
glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex));
this.renderContext.gl.uniformMatrix4fv(uniforms.uTransform, false, transform);
}
}

View File

@ -12,8 +12,9 @@ import * as glmatrix from 'gl-matrix';
import {AntialiasingStrategy, SubpixelAAType} from './aa-strategy';
import {createFramebuffer, createFramebufferDepthTexture, setTextureParameters} from './gl-utils';
import {Renderer} from './renderer';
import {unwrapNull} from './utils';
import {DemoView, Renderer} from './view';
import {DemoView} from './view';
export default class SSAAStrategy extends AntialiasingStrategy {
private level: number;
@ -50,11 +51,11 @@ export default class SSAAStrategy extends AntialiasingStrategy {
renderContext.gl.bindTexture(renderContext.gl.TEXTURE_2D, this.supersampledColorTexture);
renderContext.gl.texImage2D(renderContext.gl.TEXTURE_2D,
0,
renderer.colorAlphaFormat,
renderContext.colorAlphaFormat,
this.supersampledFramebufferSize[0],
this.supersampledFramebufferSize[1],
0,
renderer.colorAlphaFormat,
renderContext.colorAlphaFormat,
renderContext.gl.UNSIGNED_BYTE,
null);
setTextureParameters(renderContext.gl, renderContext.gl.LINEAR);

View File

@ -18,6 +18,7 @@ import PathfinderBufferTexture from "./buffer-texture";
import {OrthographicCamera} from "./camera";
import {UniformMap} from './gl-utils';
import {PathfinderMeshData} from "./meshes";
import {Renderer} from './renderer';
import {ShaderMap, ShaderProgramSource} from './shader-loader';
import SSAAStrategy from "./ssaa-strategy";
import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
@ -78,24 +79,19 @@ class SVGDemoController extends DemoAppController<SVGDemoView> {
private meshesReceived(): void {
this.view.then(view => {
view.uploadPathColors(1);
view.uploadPathTransforms(1);
view.attachMeshes([this.meshes]);
view.camera.bounds = this.loader.bounds;
view.camera.zoomToFit();
view.initCameraBounds(this.loader.bounds);
});
}
}
class SVGDemoView extends DemoView {
camera: OrthographicCamera;
renderer: SVGDemoRenderer;
appController: SVGDemoController;
protected depthFunction: number = this.gl.GREATER;
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.fromValues(1.0, 1.0);
private appController: SVGDemoController;
get camera(): OrthographicCamera {
return this.renderer.camera;
}
constructor(appController: SVGDemoController,
commonShaderSource: string,
@ -103,14 +99,26 @@ class SVGDemoView extends DemoView {
super(commonShaderSource, shaderSources);
this.appController = appController;
this.renderer = new SVGDemoRenderer(this);
this.camera = new OrthographicCamera(this.canvas, { scaleBounds: true });
this.camera.onPan = () => this.setDirty();
this.camera.onZoom = () => this.setDirty();
this.resizeToFit(true);
}
initCameraBounds(bounds: glmatrix.vec4): void {
this.renderer.initCameraBounds(bounds);
}
}
class SVGDemoRenderer extends Renderer {
renderContext: SVGDemoView;
camera: OrthographicCamera;
get destAllocatedSize(): glmatrix.vec2 {
return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height);
return glmatrix.vec2.clone([
this.renderContext.canvas.width,
this.renderContext.canvas.height,
]);
}
get destFramebuffer(): WebGLFramebuffer | null {
@ -121,21 +129,70 @@ class SVGDemoView extends DemoView {
return this.destAllocatedSize;
}
constructor(renderContext: SVGDemoView) {
super(renderContext);
this.camera = new OrthographicCamera(renderContext.canvas, { scaleBounds: true });
this.camera.onPan = () => this.renderContext.setDirty();
this.camera.onZoom = () => this.renderContext.setDirty();
}
setHintsUniform(uniforms: UniformMap): void {
this.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
}
pathBoundingRects(objectIndex: number): Float32Array {
panic("SVGDemoView.pathBoundingRects(): TODO");
panic("SVGDemoRenderer.pathBoundingRects(): TODO");
return glmatrix.vec4.create();
}
pathCountForObject(objectIndex: number): number {
return this.appController.loader.pathInstances.length;
attachMeshes(meshes: PathfinderMeshData[]): void {
super.attachMeshes(meshes);
this.uploadPathColors(1);
this.uploadPathTransforms(1);
}
initCameraBounds(bounds: glmatrix.vec4): void {
this.camera.bounds = bounds;
this.camera.zoomToFit();
}
protected get depthFunction(): number {
return this.renderContext.gl.GREATER;
}
protected get usedSizeFactor(): glmatrix.vec2 {
return glmatrix.vec2.clone([1.0, 1.0]);
}
protected get worldTransform(): glmatrix.mat4 {
const transform = glmatrix.mat4.create();
const translation = this.camera.translation;
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [
2.0 / this.renderContext.canvas.width,
2.0 / this.renderContext.canvas.height,
1.0,
]);
glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
return transform;
}
protected get directCurveProgramName(): keyof ShaderMap<void> {
return 'directCurve';
}
protected get directInteriorProgramName(): keyof ShaderMap<void> {
return 'directInterior';
}
protected newTimingsReceived(): void {
this.renderContext.appController.newTimingsReceived(_.pick(this.lastTimings, ['rendering']));
}
protected pathColorsForObject(objectIndex: number): Uint8Array {
const instances = this.appController.loader.pathInstances;
const instances = this.renderContext.appController.loader.pathInstances;
const pathColors = new Uint8Array(4 * (instances.length + 1));
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
@ -154,7 +211,7 @@ class SVGDemoView extends DemoView {
}
protected pathTransformsForObject(objectIndex: number): Float32Array {
const instances = this.appController.loader.pathInstances;
const instances = this.renderContext.appController.loader.pathInstances;
const pathTransforms = new Float32Array(4 * (instances.length + 1));
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
@ -174,30 +231,6 @@ class SVGDemoView extends DemoView {
}
protected compositeIfNecessary(): void {}
protected newTimingsReceived() {
this.appController.newTimingsReceived(_.pick(this.lastTimings, ['rendering']));
}
protected get worldTransform() {
const transform = glmatrix.mat4.create();
const translation = this.camera.translation;
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform,
transform,
[2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]);
glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
return transform;
}
protected get directCurveProgramName(): keyof ShaderMap<void> {
return 'directCurve';
}
protected get directInteriorProgramName(): keyof ShaderMap<void> {
return 'directInterior';
}
}
function main() {

View File

@ -23,6 +23,7 @@ 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 {Renderer} from './renderer';
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
import SSAAStrategy from './ssaa-strategy';
import {calculatePixelDescent, calculatePixelRectForGlyph, PathfinderFont} from "./text";
@ -186,7 +187,7 @@ class TextDemoController extends DemoAppController<TextDemoView> {
}
private hintingChanged(): void {
this.view.then(view => view.updateHinting());
this.view.then(view => view.renderer.updateHinting());
}
private updateText(): void {
@ -213,7 +214,7 @@ class TextDemoController extends DemoAppController<TextDemoView> {
this.meshes = meshes;
view.attachText();
view.uploadPathColors(1);
view.renderer.uploadPathColors(1);
view.attachMeshes([this.meshes]);
});
});
@ -240,7 +241,7 @@ class TextDemoController extends DemoAppController<TextDemoView> {
/// The font size in pixels per em.
set fontSize(newFontSize: number) {
this._fontSize = newFontSize;
this.view.then(view => view.relayoutText());
this.view.then(view => view.renderer.relayoutText());
}
get layoutPixelsPerUnit(): number {
@ -265,6 +266,62 @@ class TextDemoController extends DemoAppController<TextDemoView> {
}
class TextDemoView extends DemoView {
renderer: TextDemoRenderer;
appController: TextDemoController;
protected get camera(): OrthographicCamera {
return this.renderer.camera;
}
constructor(appController: TextDemoController,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>) {
super(commonShaderSource, shaderSources);
this.appController = appController;
this.renderer = new TextDemoRenderer(this);
this.canvas.addEventListener('dblclick', () => this.appController.showTextEditor(), false);
this.resizeToFit(true);
}
attachText() {
this.panZoomEventsEnabled = false;
this.renderer.prepareToAttachText();
this.renderer.camera.zoomToFit();
this.appController.fontSize = this.renderer.camera.scale *
this.appController.font.opentypeFont.unitsPerEm;
this.renderer.finishAttachingText();
this.panZoomEventsEnabled = true;
}
protected onPan() {
this.renderer.viewPanned();
}
protected onZoom() {
this.appController.fontSize = this.renderer.camera.scale *
this.appController.font.opentypeFont.unitsPerEm;
}
private set panZoomEventsEnabled(flag: boolean) {
if (flag) {
this.renderer.camera.onPan = () => this.onPan();
this.renderer.camera.onZoom = () => this.onZoom();
} else {
this.renderer.camera.onPan = null;
this.renderer.camera.onZoom = null;
}
}
}
class TextDemoRenderer extends Renderer {
renderContext: TextDemoView;
camera: OrthographicCamera;
atlasFramebuffer: WebGLFramebuffer;
atlasDepthTexture: WebGLTexture;
@ -272,22 +329,22 @@ class TextDemoView extends DemoView {
glyphTexCoordsBuffer: WebGLBuffer;
glyphElementsBuffer: WebGLBuffer;
appController: TextDemoController;
get destFramebuffer(): WebGLFramebuffer {
return this.atlasFramebuffer;
}
camera: OrthographicCamera;
get destAllocatedSize(): glmatrix.vec2 {
return ATLAS_SIZE;
}
get destUsedSize(): glmatrix.vec2 {
return this.renderContext.appController.atlas.usedSize;
}
get emboldenAmount(): glmatrix.vec2 {
return this.stemDarkeningAmount;
}
private get stemDarkeningAmount(): glmatrix.vec2 {
if (this.stemDarkening === 'dark') {
return computeStemDarkeningAmount(this.appController.fontSize,
this.appController.layoutPixelsPerUnit);
}
return glmatrix.vec2.create();
}
get bgColor(): glmatrix.vec4 {
return glmatrix.vec4.fromValues(1.0, 1.0, 1.0, 0.0);
}
@ -296,55 +353,55 @@ class TextDemoView extends DemoView {
return glmatrix.vec4.fromValues(0.0, 0.0, 0.0, 1.0);
}
protected depthFunction: number = this.gl.GREATER;
protected get worldTransform(): glmatrix.mat4 {
const transform = glmatrix.mat4.create();
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [2.0 / ATLAS_SIZE[0], 2.0 / ATLAS_SIZE[1], 1.0]);
return transform;
}
private subpixelAA: SubpixelAAType;
private stemDarkening: StemDarkeningMode;
protected get directCurveProgramName(): keyof ShaderMap<void> {
return 'directCurve';
}
protected get directInteriorProgramName(): keyof ShaderMap<void> {
return 'directInterior';
}
protected get depthFunction(): number {
return this.renderContext.gl.GREATER;
}
protected get usedSizeFactor(): glmatrix.vec2 {
const usedSize = glmatrix.vec2.create();
glmatrix.vec2.div(usedSize, this.renderContext.appController.atlas.usedSize, ATLAS_SIZE);
return usedSize;
}
private get stemDarkeningAmount(): glmatrix.vec2 {
const appController = this.renderContext.appController;
if (this.stemDarkening === 'dark') {
return computeStemDarkeningAmount(appController.fontSize,
appController.layoutPixelsPerUnit);
}
return glmatrix.vec2.create();
}
private glyphBounds: Float32Array;
private stemDarkening: StemDarkeningMode;
private subpixelAA: SubpixelAAType;
constructor(appController: TextDemoController,
commonShaderSource: string,
shaderSources: ShaderMap<ShaderProgramSource>) {
super(commonShaderSource, shaderSources);
private get displayPixelsPerUnit(): number {
return this.renderContext.appController.layoutPixelsPerUnit;
}
this.appController = appController;
constructor(renderContext: TextDemoView) {
super(renderContext);
this.camera = new OrthographicCamera(this.canvas, {
this.camera = new OrthographicCamera(this.renderContext.canvas, {
maxScale: MAX_SCALE,
minScale: MIN_SCALE,
});
this.canvas.addEventListener('dblclick', () => this.appController.showTextEditor(), false);
}
attachText() {
this.panZoomEventsEnabled = false;
if (this.atlasFramebuffer == null)
this.createAtlasFramebuffer();
this.layoutText();
this.camera.zoomToFit();
this.appController.fontSize = this.camera.scale *
this.appController.font.opentypeFont.unitsPerEm;
this.buildAtlasGlyphs();
this.setDirty();
this.panZoomEventsEnabled = true;
}
relayoutText() {
this.layoutText();
this.buildAtlasGlyphs();
this.setDirty();
}
updateHinting(): void {
// Need to relayout the text because the pixel bounds of the glyphs can change from this...
this.layoutText();
this.buildAtlasGlyphs();
this.setDirty();
}
setAntialiasingOptions(aaType: AntialiasingStrategyName,
@ -356,23 +413,53 @@ class TextDemoView extends DemoView {
// Need to relayout because changing AA options can cause font dilation to change...
this.layoutText();
this.buildAtlasGlyphs();
this.setDirty();
this.renderContext.setDirty();
}
setHintsUniform(uniforms: UniformMap): void {
const hint = this.createHint();
this.gl.uniform4f(uniforms.uHints,
hint.xHeight,
hint.hintedXHeight,
hint.stemHeight,
hint.hintedStemHeight);
this.renderContext.gl.uniform4f(uniforms.uHints,
hint.xHeight,
hint.hintedXHeight,
hint.stemHeight,
hint.hintedStemHeight);
}
prepareToAttachText(): void {
if (this.atlasFramebuffer == null)
this.createAtlasFramebuffer();
this.layoutText();
}
finishAttachingText(): void {
this.buildAtlasGlyphs();
this.renderContext.setDirty();
}
relayoutText(): void {
this.layoutText();
this.buildAtlasGlyphs();
this.renderContext.setDirty();
}
updateHinting(): void {
// Need to relayout the text because the pixel bounds of the glyphs can change from this...
this.layoutText();
this.buildAtlasGlyphs();
this.renderContext.setDirty();
}
viewPanned(): void {
this.buildAtlasGlyphs();
this.renderContext.setDirty();
}
pathBoundingRects(objectIndex: number): Float32Array {
const pathCount = this.appController.pathCount;
const atlasGlyphs = this.appController.atlasGlyphs;
const pathCount = this.renderContext.appController.pathCount;
const atlasGlyphs = this.renderContext.appController.atlasGlyphs;
const pixelsPerUnit = this.displayPixelsPerUnit;
const font = this.appController.font;
const font = this.renderContext.appController.font;
const hint = this.createHint();
const boundingRects = new Float32Array((pathCount + 1) * 4);
@ -393,16 +480,97 @@ class TextDemoView extends DemoView {
return boundingRects;
}
pathCountForObject(objectIndex: number): number {
return this.appController.pathCount;
protected createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType,
stemDarkening: StemDarkeningMode):
AntialiasingStrategy {
this.subpixelAA = subpixelAA;
this.stemDarkening = stemDarkening;
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
}
protected initContext() {
super.initContext();
protected clearForDirectRendering(): void {
this.renderContext.gl.clearColor(0.0, 0.0, 0.0, 0.0);
this.renderContext.gl.clearDepth(0.0);
this.renderContext.gl.depthMask(true);
this.renderContext.gl.clear(this.renderContext.gl.COLOR_BUFFER_BIT |
this.renderContext.gl.DEPTH_BUFFER_BIT);
}
protected compositeIfNecessary() {
// Set up composite state.
this.renderContext.gl.bindFramebuffer(this.renderContext.gl.FRAMEBUFFER, null);
this.renderContext.gl.viewport(0, 0, this.renderContext.canvas.width, this.renderContext.canvas.height);
this.renderContext.gl.disable(this.renderContext.gl.DEPTH_TEST);
this.renderContext.gl.disable(this.renderContext.gl.SCISSOR_TEST);
this.renderContext.gl.blendEquation(this.renderContext.gl.FUNC_ADD);
this.renderContext.gl.blendFuncSeparate(this.renderContext.gl.SRC_ALPHA,
this.renderContext.gl.ONE_MINUS_SRC_ALPHA,
this.renderContext.gl.ONE,
this.renderContext.gl.ONE);
this.renderContext.gl.enable(this.renderContext.gl.BLEND);
// Clear.
this.renderContext.gl.clearColor(1.0, 1.0, 1.0, 1.0);
this.renderContext.gl.clear(this.renderContext.gl.COLOR_BUFFER_BIT);
// Set up the composite VAO.
const blitProgram = this.renderContext.shaderPrograms.blit;
const attributes = blitProgram.attributes;
this.renderContext.gl.useProgram(blitProgram.program);
this.renderContext.gl.bindBuffer(this.renderContext.gl.ARRAY_BUFFER,
this.glyphPositionsBuffer);
this.renderContext.gl.vertexAttribPointer(attributes.aPosition,
2,
this.renderContext.gl.FLOAT,
false,
0,
0);
this.renderContext.gl.bindBuffer(this.renderContext.gl.ARRAY_BUFFER,
this.glyphTexCoordsBuffer);
this.renderContext.gl.vertexAttribPointer(attributes.aTexCoord,
2,
this.renderContext.gl.FLOAT,
false,
0,
0);
this.renderContext.gl.enableVertexAttribArray(attributes.aPosition);
this.renderContext.gl.enableVertexAttribArray(attributes.aTexCoord);
this.renderContext.gl.bindBuffer(this.renderContext.gl.ELEMENT_ARRAY_BUFFER,
this.glyphElementsBuffer);
// Create the transform.
const transform = glmatrix.mat4.create();
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [
2.0 / this.renderContext.canvas.width,
2.0 / this.renderContext.canvas.height,
1.0,
]);
glmatrix.mat4.translate(transform,
transform,
[this.camera.translation[0], this.camera.translation[1], 0.0]);
// Blit.
this.renderContext.gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform);
this.renderContext.gl.activeTexture(this.renderContext.gl.TEXTURE0);
const destTexture = this.renderContext
.appController
.atlas
.ensureTexture(this.renderContext);
this.renderContext.gl.bindTexture(this.renderContext.gl.TEXTURE_2D, destTexture);
this.renderContext.gl.uniform1i(blitProgram.uniforms.uSource, 0);
this.setIdentityTexScaleUniform(blitProgram.uniforms);
const totalGlyphCount = this.renderContext.appController.layout.textFrame.totalGlyphCount;
this.renderContext.gl.drawElements(this.renderContext.gl.TRIANGLES,
totalGlyphCount * 6,
this.renderContext.gl.UNSIGNED_INT,
0);
}
protected pathColorsForObject(objectIndex: number): Uint8Array {
const pathCount = this.appController.pathCount;
const pathCount = this.renderContext.appController.pathCount;
const pathColors = new Uint8Array(4 * (pathCount + 1));
@ -416,8 +584,8 @@ class TextDemoView extends DemoView {
}
protected pathTransformsForObject(objectIndex: number): Float32Array {
const pathCount = this.appController.pathCount;
const atlasGlyphs = this.appController.atlasGlyphs;
const pathCount = this.renderContext.appController.pathCount;
const atlasGlyphs = this.renderContext.appController.atlasGlyphs;
const pixelsPerUnit = this.displayPixelsPerUnit;
const transforms = new Float32Array((pathCount + 1) * 4);
@ -435,97 +603,33 @@ class TextDemoView extends DemoView {
return transforms;
}
protected onPan() {
this.buildAtlasGlyphs();
this.setDirty();
}
protected onZoom() {
this.appController.fontSize = this.camera.scale *
this.appController.font.opentypeFont.unitsPerEm;
}
protected compositeIfNecessary() {
// Set up composite state.
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
this.gl.disable(this.gl.DEPTH_TEST);
this.gl.disable(this.gl.SCISSOR_TEST);
this.gl.blendEquation(this.gl.FUNC_ADD);
this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA,
this.gl.ONE, this.gl.ONE);
this.gl.enable(this.gl.BLEND);
// Clear.
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
// Set up the composite VAO.
const blitProgram = this.shaderPrograms.blit;
const attributes = blitProgram.attributes;
this.gl.useProgram(blitProgram.program);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
this.gl.vertexAttribPointer(attributes.aPosition, 2, this.gl.FLOAT, false, 0, 0);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
this.gl.vertexAttribPointer(attributes.aTexCoord, 2, this.gl.FLOAT, false, 0, 0);
this.gl.enableVertexAttribArray(attributes.aPosition);
this.gl.enableVertexAttribArray(attributes.aTexCoord);
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.glyphElementsBuffer);
// Create the transform.
const transform = glmatrix.mat4.create();
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform,
transform,
[2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]);
glmatrix.mat4.translate(transform,
transform,
[this.camera.translation[0],
this.camera.translation[1],
0.0]);
// Blit.
this.gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform);
this.gl.activeTexture(this.gl.TEXTURE0);
this.gl.bindTexture(this.gl.TEXTURE_2D, this.appController.atlas.ensureTexture(this));
this.gl.uniform1i(blitProgram.uniforms.uSource, 0);
this.setIdentityTexScaleUniform(blitProgram.uniforms);
this.gl.drawElements(this.gl.TRIANGLES,
this.appController.layout.textFrame.totalGlyphCount * 6,
this.gl.UNSIGNED_INT,
0);
}
protected clearForDirectRendering(): void {
this.gl.clearColor(0.0, 0.0, 0.0, 0.0);
this.gl.clearDepth(0.0);
this.gl.depthMask(true);
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
}
protected createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType,
stemDarkening: StemDarkeningMode):
AntialiasingStrategy {
this.subpixelAA = subpixelAA;
this.stemDarkening = stemDarkening;
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
}
protected newTimingsReceived() {
this.appController.newTimingsReceived(this.lastTimings);
this.renderContext.appController.newTimingsReceived(this.lastTimings);
}
private createAtlasFramebuffer() {
const appController = this.renderContext.appController;
const atlasColorTexture = appController.atlas.ensureTexture(this.renderContext);
this.atlasDepthTexture = createFramebufferDepthTexture(this.renderContext.gl, ATLAS_SIZE);
this.atlasFramebuffer = createFramebuffer(this.renderContext.gl,
this.renderContext.drawBuffersExt,
[atlasColorTexture],
this.atlasDepthTexture);
// Allow the antialiasing strategy to set up framebuffers as necessary.
if (this.antialiasingStrategy != null)
this.antialiasingStrategy.setFramebufferSize(this);
}
private createHint(): Hint {
return new Hint(this.appController.font,
return new Hint(this.renderContext.appController.font,
this.displayPixelsPerUnit,
this.appController.useHinting);
this.renderContext.appController.useHinting);
}
/// Lays out glyphs on the canvas.
private layoutText() {
const layout = this.appController.layout;
const layout = this.renderContext.appController.layout;
layout.layoutRuns();
const textBounds = layout.textFrame.bounds;
@ -537,7 +641,7 @@ class TextDemoView extends DemoView {
const hint = this.createHint();
const displayPixelsPerUnit = this.displayPixelsPerUnit;
const layoutPixelsPerUnit = this.appController.layoutPixelsPerUnit;
const layoutPixelsPerUnit = this.renderContext.appController.layoutPixelsPerUnit;
let globalGlyphIndex = 0;
for (const run of layout.textFrame.runs) {
@ -566,29 +670,38 @@ class TextDemoView extends DemoView {
}
}
this.glyphPositionsBuffer = unwrapNull(this.gl.createBuffer());
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, glyphPositions, this.gl.STATIC_DRAW);
this.glyphElementsBuffer = unwrapNull(this.gl.createBuffer());
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.glyphElementsBuffer);
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, glyphIndices, this.gl.STATIC_DRAW);
this.glyphPositionsBuffer = unwrapNull(this.renderContext.gl.createBuffer());
this.renderContext.gl.bindBuffer(this.renderContext.gl.ARRAY_BUFFER,
this.glyphPositionsBuffer);
this.renderContext.gl.bufferData(this.renderContext.gl.ARRAY_BUFFER,
glyphPositions,
this.renderContext.gl.STATIC_DRAW);
this.glyphElementsBuffer = unwrapNull(this.renderContext.gl.createBuffer());
this.renderContext.gl.bindBuffer(this.renderContext.gl.ELEMENT_ARRAY_BUFFER,
this.glyphElementsBuffer);
this.renderContext.gl.bufferData(this.renderContext.gl.ELEMENT_ARRAY_BUFFER,
glyphIndices,
this.renderContext.gl.STATIC_DRAW);
}
private buildAtlasGlyphs() {
const font = this.appController.font;
const glyphStore = this.appController.glyphStore;
const layoutPixelsPerUnit = this.appController.layoutPixelsPerUnit;
const appController = this.renderContext.appController;
const font = appController.font;
const glyphStore = appController.glyphStore;
const layoutPixelsPerUnit = appController.layoutPixelsPerUnit;
const displayPixelsPerUnit = this.displayPixelsPerUnit;
const textFrame = this.appController.layout.textFrame;
const textFrame = appController.layout.textFrame;
const hint = this.createHint();
// Only build glyphs in view.
const translation = this.camera.translation;
const canvasRect = glmatrix.vec4.fromValues(-translation[0],
-translation[1],
-translation[0] + this.canvas.width,
-translation[1] + this.canvas.height);
const canvasRect = glmatrix.vec4.clone([
-translation[0],
-translation[1],
-translation[0] + this.renderContext.canvas.width,
-translation[1] + this.renderContext.canvas.height,
]);
let atlasGlyphs = [];
for (const run of textFrame.runs) {
@ -621,12 +734,12 @@ class TextDemoView extends DemoView {
if (atlasGlyphs.length === 0)
return;
this.appController.atlasGlyphs = atlasGlyphs;
this.appController.atlas.layoutGlyphs(atlasGlyphs,
font,
displayPixelsPerUnit,
hint,
this.stemDarkeningAmount);
appController.atlasGlyphs = atlasGlyphs;
appController.atlas.layoutGlyphs(atlasGlyphs,
font,
displayPixelsPerUnit,
hint,
this.stemDarkeningAmount);
this.uploadPathTransforms(1);
@ -635,26 +748,14 @@ class TextDemoView extends DemoView {
this.setGlyphTexCoords();
}
private createAtlasFramebuffer() {
const atlasColorTexture = this.appController.atlas.ensureTexture(this);
this.atlasDepthTexture = createFramebufferDepthTexture(this.gl, ATLAS_SIZE);
this.atlasFramebuffer = createFramebuffer(this.gl,
this.drawBuffersExt,
[atlasColorTexture],
this.atlasDepthTexture);
// Allow the antialiasing strategy to set up framebuffers as necessary.
if (this.antialiasingStrategy != null)
this.antialiasingStrategy.setFramebufferSize(this);
}
private setGlyphTexCoords() {
const textFrame = this.appController.layout.textFrame;
const font = this.appController.font;
const atlasGlyphs = this.appController.atlasGlyphs;
const appController = this.renderContext.appController;
const textFrame = appController.layout.textFrame;
const font = appController.font;
const atlasGlyphs = appController.atlasGlyphs;
const hint = this.createHint();
const layoutPixelsPerUnit = this.appController.layoutPixelsPerUnit;
const layoutPixelsPerUnit = appController.layoutPixelsPerUnit;
const displayPixelsPerUnit = this.displayPixelsPerUnit;
const atlasGlyphKeys = atlasGlyphs.map(atlasGlyph => atlasGlyph.glyphKey.sortKey);
@ -707,61 +808,16 @@ class TextDemoView extends DemoView {
}
}
this.glyphTexCoordsBuffer = unwrapNull(this.gl.createBuffer());
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, this.glyphBounds, this.gl.STATIC_DRAW);
this.glyphTexCoordsBuffer = unwrapNull(this.renderContext.gl.createBuffer());
this.renderContext.gl.bindBuffer(this.renderContext.gl.ARRAY_BUFFER,
this.glyphTexCoordsBuffer);
this.renderContext.gl.bufferData(this.renderContext.gl.ARRAY_BUFFER,
this.glyphBounds,
this.renderContext.gl.STATIC_DRAW);
}
private setIdentityTexScaleUniform(uniforms: UniformMap) {
this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
}
protected get usedSizeFactor(): glmatrix.vec2 {
const usedSize = glmatrix.vec2.create();
glmatrix.vec2.div(usedSize, this.appController.atlas.usedSize, ATLAS_SIZE);
return usedSize;
}
private set panZoomEventsEnabled(flag: boolean) {
if (flag) {
this.camera.onPan = () => this.onPan();
this.camera.onZoom = () => this.onZoom();
} else {
this.camera.onPan = null;
this.camera.onZoom = null;
}
}
get destFramebuffer(): WebGLFramebuffer {
return this.atlasFramebuffer;
}
get destAllocatedSize(): glmatrix.vec2 {
return ATLAS_SIZE;
}
get destUsedSize(): glmatrix.vec2 {
return this.appController.atlas.usedSize;
}
protected get worldTransform(): glmatrix.mat4 {
const transform = glmatrix.mat4.create();
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [2.0 / ATLAS_SIZE[0], 2.0 / ATLAS_SIZE[1], 1.0]);
return transform;
}
protected get directCurveProgramName(): keyof ShaderMap<void> {
return 'directCurve';
}
protected get directInteriorProgramName(): keyof ShaderMap<void> {
return 'directInterior';
}
/// Pixels per unit, including dilation.
private get displayPixelsPerUnit(): number {
return this.appController.layoutPixelsPerUnit;
this.renderContext.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
}
}

View File

@ -8,9 +8,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// FIXME(pcwalton): This is turning into a fragile inheritance hierarchy. See if we can refactor to
// use composition more.
import * as glmatrix from 'gl-matrix';
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
@ -19,18 +16,11 @@ import PathfinderBufferTexture from './buffer-texture';
import {Camera} from "./camera";
import {EXTDisjointTimerQuery, QUAD_ELEMENTS, UniformMap} from './gl-utils';
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
import {Renderer} from './renderer';
import {PathfinderShaderProgram, SHADER_NAMES, ShaderMap} from './shader-loader';
import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader';
import {expectNotNull, PathfinderError, UINT32_SIZE, unwrapNull} from './utils';
const MAX_PATHS: number = 65535;
const TIME_INTERVAL_DELAY: number = 32;
const B_LOOP_BLINN_DATA_SIZE: number = 4;
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,
@ -58,19 +48,23 @@ export interface Timings {
declare class WebGLQuery {}
export abstract class PathfinderView {
protected canvas: HTMLCanvasElement;
canvas: HTMLCanvasElement;
protected camera: Camera;
protected abstract get camera(): Camera;
private dirty: boolean;
constructor() {
this.dirty = false;
this.canvas = unwrapNull(document.getElementById('pf-canvas')) as HTMLCanvasElement;
window.addEventListener('resize', () => this.resizeToFit(false), false);
this.resizeToFit(true);
}
setDirty() {
if (this.dirty)
return;
this.dirty = true;
window.requestAnimationFrame(() => this.redraw());
}
zoomIn(): void {
@ -85,18 +79,11 @@ export abstract class PathfinderView {
this.setDirty();
}
protected setDirty() {
if (this.dirty)
return;
this.dirty = true;
window.requestAnimationFrame(() => this.redraw());
}
protected redraw() {
this.dirty = false;
}
private resizeToFit(initialSize: boolean) {
protected resizeToFit(initialSize: boolean) {
const width = window.innerWidth;
let height = window.scrollY + window.innerHeight - this.canvas.getBoundingClientRect().top;
@ -121,7 +108,9 @@ export abstract class PathfinderView {
}
}
export abstract class DemoView extends PathfinderView implements Renderer, RenderContext {
export abstract class DemoView extends PathfinderView implements RenderContext {
readonly renderer: Renderer;
gl: WebGLRenderingContext;
shaderPrograms: ShaderMap<PathfinderShaderProgram>;
@ -129,29 +118,19 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
drawBuffersExt: WebGLDrawBuffers;
instancedArraysExt: ANGLEInstancedArrays;
textureHalfFloatExt: OESTextureHalfFloat;
timerQueryExt: EXTDisjointTimerQuery;
vertexArrayObjectExt: OESVertexArrayObject;
quadPositionsBuffer: WebGLBuffer;
quadTexCoordsBuffer: WebGLBuffer;
quadElementsBuffer: WebGLBuffer;
atlasRenderingTimerQuery: WebGLQuery;
compositingTimerQuery: WebGLQuery;
meshes: PathfinderMeshBuffers[];
meshData: PathfinderMeshData[];
pathTransformBufferTextures: PathfinderBufferTexture[];
get bgColor(): glmatrix.vec4 | null {
return null;
}
get fgColor(): glmatrix.vec4 | null {
return null;
}
get emboldenAmount(): glmatrix.vec2 {
return glmatrix.vec2.create();
}
get colorAlphaFormat(): GLenum {
return this.sRGBExt == null ? this.gl.RGBA : this.sRGBExt.SRGB_ALPHA_EXT;
}
@ -161,70 +140,24 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
}
protected sRGBExt: EXTsRGB;
protected timerQueryExt: EXTDisjointTimerQuery;
protected antialiasingStrategy: AntialiasingStrategy | null;
protected colorBufferHalfFloatExt: any;
protected pathColorsBufferTextures: PathfinderBufferTexture[];
protected lastTimings: Timings;
protected get pathIDsAreInstanced(): boolean {
return false;
}
private instancedPathIDVBO: WebGLBuffer | null;
private atlasRenderingTimerQuery: WebGLQuery;
private compositingTimerQuery: WebGLQuery;
private timerQueryPollInterval: number | null;
private wantsScreenshot: boolean;
/// NB: All subclasses are responsible for creating a renderer in their constructors.
constructor(commonShaderSource: string, shaderSources: ShaderMap<ShaderProgramSource>) {
super();
this.initContext();
this.lastTimings = { rendering: 0, compositing: 0 };
const shaderSource = this.compileShaders(commonShaderSource, shaderSources);
this.shaderPrograms = this.linkShaders(shaderSource);
this.pathTransformBufferTextures = [];
this.pathColorsBufferTextures = [];
if (this.pathIDsAreInstanced)
this.initInstancedPathIDVBO();
this.wantsScreenshot = false;
this.antialiasingStrategy = new NoAAStrategy(0, 'none');
this.antialiasingStrategy.init(this);
}
setAntialiasingOptions(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType,
stemDarkening: StemDarkeningMode) {
this.antialiasingStrategy = this.createAAStrategy(aaType,
aaLevel,
subpixelAA,
stemDarkening);
const canvas = this.canvas;
this.antialiasingStrategy.init(this);
if (this.meshData != null)
this.antialiasingStrategy.attachMeshes(this);
this.setDirty();
}
attachMeshes(meshes: PathfinderMeshData[]) {
this.meshData = meshes;
this.meshes = meshes.map(meshes => new PathfinderMeshBuffers(this.gl, meshes));
unwrapNull(this.antialiasingStrategy).attachMeshes(this);
attachMeshes(meshes: PathfinderMeshData[]): void {
this.renderer.attachMeshes(meshes);
this.setDirty();
}
@ -238,81 +171,21 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.quadElementsBuffer);
}
setFramebufferSizeUniform(uniforms: UniformMap) {
const currentViewport = this.gl.getParameter(this.gl.VIEWPORT);
this.gl.uniform2i(uniforms.uFramebufferSize, currentViewport[2], currentViewport[3]);
}
setTransformSTUniform(uniforms: UniformMap, objectIndex: number) {
// FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an ST matrix is ugly and fragile.
// Refactor.
const transform = glmatrix.mat4.clone(this.worldTransform);
glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex));
const translation = glmatrix.vec4.clone([transform[12], transform[13], 0.0, 1.0]);
this.gl.uniform4f(uniforms.uTransformST,
transform[0],
transform[5],
transform[12],
transform[13]);
}
setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap): void {
const usedSize = this.usedSizeFactor;
this.gl.uniform4f(uniforms.uTransformST, 2.0 * usedSize[0], 2.0 * usedSize[1], -1.0, -1.0);
this.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
}
setTransformAndTexScaleUniformsForDest(uniforms: UniformMap): void {
const usedSize = this.usedSizeFactor;
const transform = glmatrix.mat4.create();
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
glmatrix.mat4.scale(transform, transform, [2.0 * usedSize[0], 2.0 * usedSize[1], 1.0]);
this.gl.uniformMatrix4fv(uniforms.uTransform, false, transform);
this.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
}
queueScreenshot() {
this.wantsScreenshot = true;
this.setDirty();
}
uploadPathColors(objectCount: number) {
this.pathColorsBufferTextures = [];
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
const pathColorsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathColors');
const pathColors = this.pathColorsForObject(objectIndex);
pathColorsBufferTexture.upload(this.gl, pathColors);
this.pathColorsBufferTextures.push(pathColorsBufferTexture);
}
setAntialiasingOptions(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType,
stemDarkening: StemDarkeningMode) {
this.renderer.setAntialiasingOptions(aaType, aaLevel, subpixelAA, stemDarkening);
}
uploadPathTransforms(objectCount: number) {
this.pathTransformBufferTextures = [];
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
const pathTransformBufferTexture = new PathfinderBufferTexture(this.gl,
'uPathTransform');
const pathTransforms = this.pathTransformsForObject(objectIndex);
pathTransformBufferTexture.upload(this.gl, pathTransforms);
this.pathTransformBufferTextures.push(pathTransformBufferTexture);
}
}
abstract setHintsUniform(uniforms: UniformMap): void;
abstract pathBoundingRects(objectIndex: number): Float32Array;
abstract pathCountForObject(objectIndex: number): number;
protected resized(): void {
super.resized();
if (this.antialiasingStrategy != null)
this.antialiasingStrategy.init(this);
this.renderer.canvasResized();
}
protected initContext() {
@ -350,46 +223,7 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
protected redraw() {
super.redraw();
if (this.meshes == null)
return;
// Start timing rendering.
if (this.timerQueryPollInterval == null) {
this.timerQueryExt.beginQueryEXT(this.timerQueryExt.TIME_ELAPSED_EXT,
this.atlasRenderingTimerQuery);
}
// Prepare for direct rendering.
const antialiasingStrategy = unwrapNull(this.antialiasingStrategy);
antialiasingStrategy.prepare(this);
// Clear.
this.clearForDirectRendering();
// Draw "scenery" (used in the 3D view).
this.drawSceneryIfNecessary();
// Perform direct rendering (Loop-Blinn).
if (antialiasingStrategy.shouldRenderDirect)
this.renderDirect();
// Antialias.
antialiasingStrategy.antialias(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);
}
antialiasingStrategy.resolve(this);
// Draw the glyphs with the resolved atlas to the default framebuffer.
this.compositeIfNecessary();
// Finish timing.
this.finishTiming();
this.renderer.redraw();
// Invoke the post-render hook.
this.renderingFinished();
@ -403,42 +237,6 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
protected renderingFinished(): void {}
protected getModelviewTransform(pathIndex: number): glmatrix.mat4 {
return glmatrix.mat4.create();
}
protected drawSceneryIfNecessary(): void {}
protected clearForDirectRendering(): void {
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
this.gl.clearDepth(0.0);
this.gl.depthMask(true);
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
}
protected shouldRenderObject(objectIndex: number): boolean {
return true;
}
protected newTimingsReceived() {}
protected abstract pathColorsForObject(objectIndex: number): Uint8Array;
protected abstract pathTransformsForObject(objectIndex: number): Float32Array;
protected abstract get depthFunction(): number;
protected abstract createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType,
stemDarkening: StemDarkeningMode):
AntialiasingStrategy;
protected abstract compositeIfNecessary(): void;
protected meshInstanceCountForObject(objectIndex: number): number {
return 1;
}
private compileShaders(commonSource: string, shaderSources: ShaderMap<ShaderProgramSource>):
ShaderMap<UnlinkedShaderProgram> {
const shaders: Partial<ShaderMap<Partial<UnlinkedShaderProgram>>> = {};
@ -483,228 +281,6 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
return shaderProgramMap as ShaderMap<PathfinderShaderProgram>;
}
private initInstancedPathIDVBO(): void {
const pathIDs = new Uint16Array(MAX_PATHS);
for (let pathIndex = 0; pathIndex < MAX_PATHS; pathIndex++)
pathIDs[pathIndex] = pathIndex + 1;
this.instancedPathIDVBO = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instancedPathIDVBO);
this.gl.bufferData(this.gl.ARRAY_BUFFER, pathIDs, this.gl.STATIC_DRAW);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
}
private setTransformUniform(uniforms: UniformMap, objectIndex: number) {
const transform = glmatrix.mat4.clone(this.worldTransform);
glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex));
this.gl.uniformMatrix4fv(uniforms.uTransform, false, transform);
}
private renderDirect() {
for (let objectIndex = 0; objectIndex < this.meshes.length; objectIndex++) {
if (!this.shouldRenderObject(objectIndex))
continue;
const meshes = this.meshes[objectIndex];
let instanceCount: number | null;
if (!this.pathIDsAreInstanced)
instanceCount = null;
else
instanceCount = this.meshInstanceCountForObject(objectIndex);
// Set up implicit cover state.
this.gl.depthFunc(this.depthFunction);
this.gl.depthMask(true);
this.gl.enable(this.gl.DEPTH_TEST);
this.gl.disable(this.gl.BLEND);
// Set up the implicit cover interior VAO.
//
// TODO(pcwalton): Cache these.
const directInteriorProgram = this.shaderPrograms[this.directInteriorProgramName];
const implicitCoverInteriorVAO = this.vertexArrayObjectExt.createVertexArrayOES();
this.vertexArrayObjectExt.bindVertexArrayOES(implicitCoverInteriorVAO);
this.initImplicitCoverInteriorVAO(objectIndex);
// Draw direct interior parts.
this.setTransformUniform(directInteriorProgram.uniforms, objectIndex);
this.setFramebufferSizeUniform(directInteriorProgram.uniforms);
this.setHintsUniform(directInteriorProgram.uniforms);
this.pathColorsBufferTextures[objectIndex].bind(this.gl,
directInteriorProgram.uniforms,
0);
this.pathTransformBufferTextures[objectIndex].bind(this.gl,
directInteriorProgram.uniforms,
1);
let indexCount = this.gl.getBufferParameter(this.gl.ELEMENT_ARRAY_BUFFER,
this.gl.BUFFER_SIZE) / UINT32_SIZE;
if (instanceCount == null) {
this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0);
} else {
this.instancedArraysExt.drawElementsInstancedANGLE(this.gl.TRIANGLES,
indexCount,
this.gl.UNSIGNED_INT,
0,
instanceCount);
}
// Set up direct curve state.
this.gl.depthMask(false);
this.gl.enable(this.gl.BLEND);
this.gl.blendEquation(this.gl.FUNC_ADD);
this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA,
this.gl.ONE, this.gl.ONE);
// Set up the direct curve VAO.
//
// TODO(pcwalton): Cache these.
const directCurveProgram = this.shaderPrograms[this.directCurveProgramName];
const implicitCoverCurveVAO = this.vertexArrayObjectExt.createVertexArrayOES();
this.vertexArrayObjectExt.bindVertexArrayOES(implicitCoverCurveVAO);
this.initImplicitCoverCurveVAO(objectIndex);
// Draw direct curve parts.
this.setTransformUniform(directCurveProgram.uniforms, objectIndex);
this.setFramebufferSizeUniform(directCurveProgram.uniforms);
this.setHintsUniform(directInteriorProgram.uniforms);
this.pathColorsBufferTextures[objectIndex].bind(this.gl,
directCurveProgram.uniforms,
0);
this.pathTransformBufferTextures[objectIndex].bind(this.gl,
directCurveProgram.uniforms,
1);
indexCount = this.gl.getBufferParameter(this.gl.ELEMENT_ARRAY_BUFFER,
this.gl.BUFFER_SIZE) / UINT32_SIZE;
if (instanceCount == null) {
this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0);
} else {
this.instancedArraysExt.drawElementsInstancedANGLE(this.gl.TRIANGLES,
indexCount,
this.gl.UNSIGNED_INT,
0,
instanceCount);
}
this.vertexArrayObjectExt.bindVertexArrayOES(null);
}
}
private initImplicitCoverInteriorVAO(objectIndex: number): void {
const meshes = this.meshes[objectIndex];
const directInteriorProgram = this.shaderPrograms[this.directInteriorProgramName];
this.gl.useProgram(directInteriorProgram.program);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPositions);
this.gl.vertexAttribPointer(directInteriorProgram.attributes.aPosition,
2,
this.gl.FLOAT,
false,
0,
0);
if (this.pathIDsAreInstanced)
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instancedPathIDVBO);
else
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPathIDs);
this.gl.vertexAttribPointer(directInteriorProgram.attributes.aPathID,
1,
this.gl.UNSIGNED_SHORT,
false,
0,
0);
if (this.pathIDsAreInstanced) {
this.instancedArraysExt
.vertexAttribDivisorANGLE(directInteriorProgram.attributes.aPathID, 1);
}
this.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPosition);
this.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPathID);
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, meshes.coverInteriorIndices);
}
private initImplicitCoverCurveVAO(objectIndex: number): void {
const meshes = this.meshes[objectIndex];
const directCurveProgram = this.shaderPrograms[this.directCurveProgramName];
this.gl.useProgram(directCurveProgram.program);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPositions);
this.gl.vertexAttribPointer(directCurveProgram.attributes.aPosition,
2,
this.gl.FLOAT,
false,
0,
0);
if (this.pathIDsAreInstanced)
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instancedPathIDVBO);
else
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPathIDs);
this.gl.vertexAttribPointer(directCurveProgram.attributes.aPathID,
1,
this.gl.UNSIGNED_SHORT,
false,
0,
0);
if (this.pathIDsAreInstanced) {
this.instancedArraysExt
.vertexAttribDivisorANGLE(directCurveProgram.attributes.aPathID, 1);
}
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexLoopBlinnData);
this.gl.vertexAttribPointer(directCurveProgram.attributes.aTexCoord,
2,
this.gl.UNSIGNED_BYTE,
false,
B_LOOP_BLINN_DATA_SIZE,
B_LOOP_BLINN_DATA_TEX_COORD_OFFSET);
this.gl.vertexAttribPointer(directCurveProgram.attributes.aSign,
1,
this.gl.BYTE,
false,
B_LOOP_BLINN_DATA_SIZE,
B_LOOP_BLINN_DATA_SIGN_OFFSET);
this.gl.enableVertexAttribArray(directCurveProgram.attributes.aPosition);
this.gl.enableVertexAttribArray(directCurveProgram.attributes.aTexCoord);
this.gl.enableVertexAttribArray(directCurveProgram.attributes.aPathID);
this.gl.enableVertexAttribArray(directCurveProgram.attributes.aSign);
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, meshes.coverCurveIndices);
}
private finishTiming() {
if (this.timerQueryPollInterval != null)
return;
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.lastTimings = {
compositing: compositingTime / 1000000.0,
rendering: atlasRenderingTime / 1000000.0,
};
this.newTimingsReceived();
window.clearInterval(this.timerQueryPollInterval!);
this.timerQueryPollInterval = null;
}, TIME_INTERVAL_DELAY);
}
private takeScreenshot() {
const width = this.canvas.width, height = this.canvas.height;
const scratchCanvas = document.createElement('canvas');
@ -721,18 +297,6 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
scratchLink.click();
document.body.removeChild(scratchLink);
}
abstract get destFramebuffer(): WebGLFramebuffer | null;
abstract get destAllocatedSize(): glmatrix.vec2;
abstract get destUsedSize(): glmatrix.vec2;
protected abstract get usedSizeFactor(): glmatrix.vec2;
protected abstract get worldTransform(): glmatrix.mat4;
protected abstract get directCurveProgramName(): keyof ShaderMap<void>;
protected abstract get directInteriorProgramName(): keyof ShaderMap<void>;
}
export interface RenderContext {
@ -743,40 +307,19 @@ export interface RenderContext {
readonly drawBuffersExt: WebGLDrawBuffers;
readonly instancedArraysExt: ANGLEInstancedArrays;
readonly textureHalfFloatExt: OESTextureHalfFloat;
readonly timerQueryExt: EXTDisjointTimerQuery;
readonly vertexArrayObjectExt: OESVertexArrayObject;
readonly colorAlphaFormat: GLenum;
readonly shaderPrograms: ShaderMap<PathfinderShaderProgram>;
readonly quadPositionsBuffer: WebGLBuffer;
readonly quadElementsBuffer: WebGLBuffer;
readonly atlasRenderingTimerQuery: WebGLQuery;
readonly compositingTimerQuery: WebGLQuery;
initQuadVAO(attributes: any): void;
}
export interface Renderer {
readonly renderContext: RenderContext;
readonly destFramebuffer: WebGLFramebuffer | null;
readonly pathTransformBufferTextures: PathfinderBufferTexture[];
readonly meshes: PathfinderMeshBuffers[];
readonly meshData: PathfinderMeshData[];
readonly colorAlphaFormat: GLenum;
readonly destAllocatedSize: glmatrix.vec2;
readonly destUsedSize: glmatrix.vec2;
readonly emboldenAmount: glmatrix.vec2;
readonly bgColor: glmatrix.vec4 | null;
readonly fgColor: glmatrix.vec4 | null;
pathBoundingRects(objectIndex: number): Float32Array;
setFramebufferSizeUniform(uniforms: UniformMap): void;
setHintsUniform(uniforms: UniformMap): void;
setTransformAndTexScaleUniformsForDest(uniforms: UniformMap): void;
setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap): void;
setTransformSTUniform(uniforms: UniformMap, objectIndex: number): void;
setDirty(): void;
}

View File

@ -17,10 +17,11 @@ import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
import {createFramebufferDepthTexture, setTextureParameters, UniformMap} from './gl-utils';
import {WebGLVertexArrayObject} from './gl-utils';
import {B_QUAD_LOWER_INDICES_OFFSET, B_QUAD_SIZE, B_QUAD_UPPER_INDICES_OFFSET} from './meshes';
import {Renderer} from './renderer';
import {PathfinderShaderProgram} from './shader-loader';
import {computeStemDarkeningAmount} from './text';
import {FLOAT32_SIZE, lerp, UINT32_SIZE, unwrapNull} from './utils';
import {RenderContext, Renderer} from './view';
import {RenderContext} from './view';
interface FastEdgeVAOs {
upper: WebGLVertexArrayObject;
@ -145,10 +146,10 @@ export abstract class XCAAStrategy extends AntialiasingStrategy {
const renderContext = renderer.renderContext;
this.directColorTexture = createFramebufferColorTexture(renderContext.gl,
this.destFramebufferSize,
renderer.colorAlphaFormat);
renderContext.colorAlphaFormat);
this.directPathIDTexture = createFramebufferColorTexture(renderContext.gl,
this.destFramebufferSize,
renderer.colorAlphaFormat);
renderContext.colorAlphaFormat);
this.directFramebuffer =
createFramebuffer(renderContext.gl,
renderContext.drawBuffersExt,
@ -1049,10 +1050,10 @@ export class MCAAMulticolorStrategy extends MCAAStrategy {
const renderContext = renderer.renderContext;
this.bgColorTexture = createFramebufferColorTexture(renderContext.gl,
this.supersampledFramebufferSize,
renderer.colorAlphaFormat);
renderContext.colorAlphaFormat);
this.fgColorTexture = createFramebufferColorTexture(renderContext.gl,
this.supersampledFramebufferSize,
renderer.colorAlphaFormat);
renderContext.colorAlphaFormat);
this.edgeDetectFramebuffer = createFramebuffer(renderContext.gl,
renderContext.drawBuffersExt,
[this.bgColorTexture, this.fgColorTexture],