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:
parent
976b924842
commit
03ee672787
|
@ -20,6 +20,7 @@ import PathfinderBufferTexture from "./buffer-texture";
|
||||||
import {PerspectiveCamera} from "./camera";
|
import {PerspectiveCamera} from "./camera";
|
||||||
import {UniformMap} from './gl-utils';
|
import {UniformMap} from './gl-utils';
|
||||||
import {PathfinderMeshData} from "./meshes";
|
import {PathfinderMeshData} from "./meshes";
|
||||||
|
import {Renderer} from './renderer';
|
||||||
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
|
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
|
||||||
import SSAAStrategy from "./ssaa-strategy";
|
import SSAAStrategy from "./ssaa-strategy";
|
||||||
import {BUILTIN_FONT_URI, ExpandedMeshData} from "./text";
|
import {BUILTIN_FONT_URI, ExpandedMeshData} from "./text";
|
||||||
|
@ -252,78 +253,171 @@ class ThreeDController extends DemoAppController<ThreeDView> {
|
||||||
return this.baseMeshes.expand([glyphIndex + 1]);
|
return this.baseMeshes.expand([glyphIndex + 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.view.then(view => {
|
this.view.then(view => view.attachMeshes(this.expandedMeshes));
|
||||||
view.uploadPathColors(this.expandedMeshes.length);
|
|
||||||
view.uploadPathTransforms(this.expandedMeshes.length);
|
|
||||||
view.attachMeshes(this.expandedMeshes);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThreeDView extends DemoView {
|
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 get camera(): PerspectiveCamera {
|
||||||
|
return this.renderer.camera;
|
||||||
protected directCurveProgramName: keyof ShaderMap<void> = 'direct3DCurve';
|
|
||||||
protected directInteriorProgramName: keyof ShaderMap<void> = 'direct3DInterior';
|
|
||||||
|
|
||||||
protected depthFunction: number = this.gl.LESS;
|
|
||||||
|
|
||||||
protected get pathIDsAreInstanced(): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _scale: number;
|
|
||||||
|
|
||||||
private appController: ThreeDController;
|
|
||||||
|
|
||||||
private cubeVertexPositionBuffer: WebGLBuffer;
|
|
||||||
private cubeIndexBuffer: WebGLBuffer;
|
|
||||||
|
|
||||||
constructor(appController: ThreeDController,
|
constructor(appController: ThreeDController,
|
||||||
commonShaderSource: string,
|
commonShaderSource: string,
|
||||||
shaderSources: ShaderMap<ShaderProgramSource>) {
|
shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||||
super(commonShaderSource, shaderSources);
|
super(commonShaderSource, shaderSources);
|
||||||
|
|
||||||
this.appController = appController;
|
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],
|
innerCollisionExtent: MONUMENT_SCALE[0],
|
||||||
});
|
});
|
||||||
this.camera.onChange = () => this.setDirty();
|
this.camera.onChange = () => renderContext.setDirty();
|
||||||
|
|
||||||
this.cubeVertexPositionBuffer = unwrapNull(this.gl.createBuffer());
|
this.cubeVertexPositionBuffer = unwrapNull(renderContext.gl.createBuffer());
|
||||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
|
renderContext.gl.bindBuffer(renderContext.gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
|
||||||
this.gl.bufferData(this.gl.ARRAY_BUFFER, CUBE_VERTEX_POSITIONS, this.gl.STATIC_DRAW);
|
renderContext.gl.bufferData(renderContext.gl.ARRAY_BUFFER,
|
||||||
|
CUBE_VERTEX_POSITIONS,
|
||||||
|
renderContext.gl.STATIC_DRAW);
|
||||||
|
|
||||||
this.cubeIndexBuffer = unwrapNull(this.gl.createBuffer());
|
this.cubeIndexBuffer = unwrapNull(renderContext.gl.createBuffer());
|
||||||
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer);
|
renderContext.gl.bindBuffer(renderContext.gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer);
|
||||||
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, CUBE_INDICES, this.gl.STATIC_DRAW);
|
renderContext.gl.bufferData(renderContext.gl.ELEMENT_ARRAY_BUFFER,
|
||||||
|
CUBE_INDICES,
|
||||||
|
renderContext.gl.STATIC_DRAW);
|
||||||
}
|
}
|
||||||
|
|
||||||
setHintsUniform(uniforms: UniformMap): void {
|
attachMeshes(expandedMeshes: PathfinderMeshData[]) {
|
||||||
this.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
|
super.attachMeshes(expandedMeshes);
|
||||||
}
|
|
||||||
|
|
||||||
pathBoundingRects(objectIndex: number): Float32Array {
|
this.uploadPathColors(expandedMeshes.length);
|
||||||
panic("ThreeDView.pathBoundingRects(): TODO");
|
this.uploadPathTransforms(expandedMeshes.length);
|
||||||
return glmatrix.vec4.create();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pathCountForObject(objectIndex: number): number {
|
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 {
|
protected pathColorsForObject(objectIndex: number): Uint8Array {
|
||||||
return TEXT_COLOR;
|
return TEXT_COLOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
||||||
const meshDescriptor = this.appController.meshDescriptors[objectIndex];
|
const meshDescriptor = this.renderContext.appController.meshDescriptors[objectIndex];
|
||||||
const pathCount = this.pathCountForObject(objectIndex);
|
const pathCount = this.pathCountForObject(objectIndex);
|
||||||
const pathTransforms = new Float32Array(4 * (pathCount + 1));
|
const pathTransforms = new Float32Array(4 * (pathCount + 1));
|
||||||
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
|
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
|
||||||
|
@ -333,6 +427,10 @@ class ThreeDView extends DemoView {
|
||||||
return pathTransforms;
|
return pathTransforms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected meshInstanceCountForObject(objectIndex: number): number {
|
||||||
|
return this.renderContext.appController.meshDescriptors[objectIndex].positions.length;
|
||||||
|
}
|
||||||
|
|
||||||
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
||||||
aaLevel: number,
|
aaLevel: number,
|
||||||
subpixelAA: SubpixelAAType):
|
subpixelAA: SubpixelAAType):
|
||||||
|
@ -342,57 +440,20 @@ class ThreeDView extends DemoView {
|
||||||
throw new PathfinderError("Unsupported antialiasing type!");
|
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 {
|
protected clearForDirectRendering(): void {
|
||||||
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
this.renderContext.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
||||||
this.gl.clearDepth(1.0);
|
this.renderContext.gl.clearDepth(1.0);
|
||||||
this.gl.depthMask(true);
|
this.renderContext.gl.depthMask(true);
|
||||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
this.renderContext.gl.clear(this.renderContext.gl.COLOR_BUFFER_BIT |
|
||||||
|
this.renderContext.gl.DEPTH_BUFFER_BIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getModelviewTransform(objectIndex: number): glmatrix.mat4 {
|
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();
|
const transform = glmatrix.mat4.create();
|
||||||
glmatrix.mat4.rotateY(transform, transform, Math.PI / 2.0 * textFrameIndex);
|
glmatrix.mat4.rotateY(transform, transform, Math.PI / 2.0 * textFrameIndex);
|
||||||
glmatrix.mat4.translate(transform, transform, TEXT_TRANSLATION);
|
glmatrix.mat4.translate(transform, transform, TEXT_TRANSLATION);
|
||||||
|
@ -401,7 +462,11 @@ class ThreeDView extends DemoView {
|
||||||
|
|
||||||
// Cheap but effective backface culling.
|
// Cheap but effective backface culling.
|
||||||
protected shouldRenderObject(objectIndex: number): boolean {
|
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 translation = this.camera.translation;
|
||||||
const extent = TEXT_TRANSLATION[2] * TEXT_SCALE[2];
|
const extent = TEXT_TRANSLATION[2] * TEXT_SCALE[2];
|
||||||
switch (textFrameIndex) {
|
switch (textFrameIndex) {
|
||||||
|
@ -411,26 +476,19 @@ class ThreeDView extends DemoView {
|
||||||
default: return translation[0] > extent;
|
default: return translation[0] > extent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
protected newTimingsReceived() {
|
||||||
protected meshInstanceCountForObject(objectIndex: number): number {
|
const newTimings: Timings = _.pick(this.lastTimings, ['rendering']);
|
||||||
return this.appController.meshDescriptors[objectIndex].positions.length;
|
this.renderContext.appController.newTimingsReceived(newTimings);
|
||||||
}
|
|
||||||
|
|
||||||
get destAllocatedSize(): glmatrix.vec2 {
|
|
||||||
return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
get destUsedSize(): glmatrix.vec2 {
|
|
||||||
return this.destAllocatedSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateWorldTransform(modelviewTranslation: glmatrix.vec3,
|
private calculateWorldTransform(modelviewTranslation: glmatrix.vec3,
|
||||||
modelviewScale: glmatrix.vec3):
|
modelviewScale: glmatrix.vec3):
|
||||||
glmatrix.mat4 {
|
glmatrix.mat4 {
|
||||||
|
const canvas = this.renderContext.canvas;
|
||||||
const projection = glmatrix.mat4.create();
|
const projection = glmatrix.mat4.create();
|
||||||
glmatrix.mat4.perspective(projection,
|
glmatrix.mat4.perspective(projection,
|
||||||
FOV / 180.0 * Math.PI,
|
FOV / 180.0 * Math.PI,
|
||||||
this.canvas.width / this.canvas.height,
|
canvas.width / canvas.height,
|
||||||
NEAR_CLIP_PLANE,
|
NEAR_CLIP_PLANE,
|
||||||
FAR_CLIP_PLANE);
|
FAR_CLIP_PLANE);
|
||||||
|
|
||||||
|
@ -444,10 +502,6 @@ class ThreeDView extends DemoView {
|
||||||
glmatrix.mat4.mul(transform, projection, modelview);
|
glmatrix.mat4.mul(transform, projection, modelview);
|
||||||
return transform;
|
return transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get worldTransform() {
|
|
||||||
return this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
|
|
||||||
import * as glmatrix from 'gl-matrix';
|
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';
|
export type AntialiasingStrategyName = 'none' | 'ssaa' | 'xcaa';
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import PathfinderBufferTexture from './buffer-texture';
|
||||||
import {OrthographicCamera} from './camera';
|
import {OrthographicCamera} from './camera';
|
||||||
import {UniformMap} from './gl-utils';
|
import {UniformMap} from './gl-utils';
|
||||||
import {PathfinderMeshData} from "./meshes";
|
import {PathfinderMeshData} from "./meshes";
|
||||||
|
import {Renderer} from './renderer';
|
||||||
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
|
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
|
||||||
import SSAAStrategy from './ssaa-strategy';
|
import SSAAStrategy from './ssaa-strategy';
|
||||||
import {BUILTIN_FONT_URI, ExpandedMeshData, GlyphStore, PathfinderFont, TextFrame} from "./text";
|
import {BUILTIN_FONT_URI, ExpandedMeshData, GlyphStore, PathfinderFont, TextFrame} from "./text";
|
||||||
|
@ -127,8 +128,6 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
||||||
this.expandedMeshes = expandedMeshes;
|
this.expandedMeshes = expandedMeshes;
|
||||||
|
|
||||||
this.view.then(view => {
|
this.view.then(view => {
|
||||||
view.uploadPathColors(1);
|
|
||||||
view.uploadPathTransforms(1);
|
|
||||||
view.attachMeshes([expandedMeshes.meshes]);
|
view.attachMeshes([expandedMeshes.meshes]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -237,10 +236,44 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class BenchmarkTestView extends DemoView {
|
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;
|
renderingPromiseCallback: ((time: number) => void) | null;
|
||||||
|
|
||||||
|
get destFramebuffer(): WebGLFramebuffer | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
get bgColor(): glmatrix.vec4 {
|
get bgColor(): glmatrix.vec4 {
|
||||||
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 0.0]);
|
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]);
|
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';
|
get destUsedSize(): glmatrix.vec2 {
|
||||||
protected directInteriorProgramName: keyof ShaderMap<void> = 'directInterior';
|
return this.destAllocatedSize;
|
||||||
|
}
|
||||||
protected depthFunction: number = this.gl.GREATER;
|
|
||||||
|
|
||||||
protected camera: OrthographicCamera;
|
|
||||||
|
|
||||||
private _pixelsPerEm: number = 32.0;
|
|
||||||
|
|
||||||
private readonly appController: BenchmarkAppController;
|
|
||||||
|
|
||||||
get emboldenAmount(): glmatrix.vec2 {
|
get emboldenAmount(): glmatrix.vec2 {
|
||||||
return this.stemDarkeningAmount;
|
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 {
|
private get stemDarkeningAmount(): glmatrix.vec2 {
|
||||||
return computeStemDarkeningAmount(this._pixelsPerEm, this.pixelsPerUnit);
|
return computeStemDarkeningAmount(this._pixelsPerEm, this.pixelsPerUnit);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(appController: BenchmarkAppController,
|
constructor(renderContext: BenchmarkTestView) {
|
||||||
commonShaderSource: string,
|
super(renderContext);
|
||||||
shaderSources: ShaderMap<ShaderProgramSource>) {
|
|
||||||
super(commonShaderSource, shaderSources);
|
|
||||||
|
|
||||||
this.appController = appController;
|
this.camera = new OrthographicCamera(renderContext.canvas);
|
||||||
|
this.camera.onPan = () => renderContext.setDirty();
|
||||||
this.camera = new OrthographicCamera(this.canvas);
|
this.camera.onZoom = () => renderContext.setDirty();
|
||||||
this.camera.onPan = () => this.setDirty();
|
|
||||||
this.camera.onZoom = () => this.setDirty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setHintsUniform(uniforms: UniformMap): void {
|
attachMeshes(meshes: PathfinderMeshData[]): void {
|
||||||
this.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
|
super.attachMeshes(meshes);
|
||||||
|
|
||||||
|
this.uploadPathColors(1);
|
||||||
|
this.uploadPathTransforms(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pathCountForObject(objectIndex: number): number {
|
||||||
|
return STRING.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
pathBoundingRects(objectIndex: number): Float32Array {
|
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);
|
const boundingRects = new Float32Array((STRING.length + 1) * 4);
|
||||||
|
|
||||||
for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
|
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);
|
const metrics = font.metricsForGlyph(glyphID);
|
||||||
if (metrics == null)
|
if (metrics == null)
|
||||||
|
@ -307,8 +389,8 @@ class BenchmarkTestView extends DemoView {
|
||||||
return boundingRects;
|
return boundingRects;
|
||||||
}
|
}
|
||||||
|
|
||||||
pathCountForObject(objectIndex: number): number {
|
setHintsUniform(uniforms: UniformMap): void {
|
||||||
return STRING.length;
|
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
||||||
|
@ -324,6 +406,14 @@ class BenchmarkTestView extends DemoView {
|
||||||
// TODO(pcwalton)
|
// 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 {
|
protected pathColorsForObject(objectIndex: number): Uint8Array {
|
||||||
const pathColors = new Uint8Array(4 * (STRING.length + 1));
|
const pathColors = new Uint8Array(4 * (STRING.length + 1));
|
||||||
for (let pathIndex = 0; pathIndex < STRING.length; pathIndex++)
|
for (let pathIndex = 0; pathIndex < STRING.length; pathIndex++)
|
||||||
|
@ -332,20 +422,21 @@ class BenchmarkTestView extends DemoView {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
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));
|
const pathTransforms = new Float32Array(4 * (STRING.length + 1));
|
||||||
|
|
||||||
let currentX = 0, currentY = 0;
|
let currentX = 0, currentY = 0;
|
||||||
const availableWidth = this.canvas.width / this.pixelsPerUnit;
|
const availableWidth = canvas.width / this.pixelsPerUnit;
|
||||||
const lineHeight = unwrapNull(this.appController.font).opentypeFont.lineHeight();
|
const lineHeight = font.opentypeFont.lineHeight();
|
||||||
|
|
||||||
for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
|
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);
|
pathTransforms.set([1, 1, currentX, currentY], (glyphIndex + 1) * 4);
|
||||||
|
|
||||||
currentX += unwrapNull(this.appController.font).opentypeFont
|
currentX += font.opentypeFont.glyphs.get(glyphID).advanceWidth;
|
||||||
.glyphs
|
|
||||||
.get(glyphID)
|
|
||||||
.advanceWidth;
|
|
||||||
if (currentX > availableWidth) {
|
if (currentX > availableWidth) {
|
||||||
currentX = 0;
|
currentX = 0;
|
||||||
currentY += lineHeight;
|
currentY += lineHeight;
|
||||||
|
@ -354,52 +445,6 @@ class BenchmarkTestView extends DemoView {
|
||||||
|
|
||||||
return pathTransforms;
|
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 {
|
function computeMedian(values: number[]): number | null {
|
||||||
|
|
|
@ -216,6 +216,8 @@ class MeshDebuggerView extends PathfinderView {
|
||||||
|
|
||||||
this.camera.onPan = () => this.setDirty();
|
this.camera.onPan = () => this.setDirty();
|
||||||
this.camera.onZoom = () => this.setDirty();
|
this.camera.onZoom = () => this.setDirty();
|
||||||
|
|
||||||
|
this.resizeToFit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
attachMeshes() {
|
attachMeshes() {
|
||||||
|
@ -277,11 +279,6 @@ class MeshDebuggerView extends PathfinderView {
|
||||||
const lowerRightPosition = unwrapNull(getPosition(positions, lowerRightIndex));
|
const lowerRightPosition = unwrapNull(getPosition(positions, lowerRightIndex));
|
||||||
const lowerControlPointPosition = getPosition(positions, lowerControlPointIndex);
|
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,
|
drawVertexIfNecessary(context,
|
||||||
drawnVertices,
|
drawnVertices,
|
||||||
upperLeftIndex,
|
upperLeftIndex,
|
||||||
|
@ -303,28 +300,6 @@ class MeshDebuggerView extends PathfinderView {
|
||||||
lowerRightPosition,
|
lowerRightPosition,
|
||||||
invScaleFactor);
|
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.beginPath();
|
||||||
context.moveTo(upperLeftPosition[0], upperLeftPosition[1]);
|
context.moveTo(upperLeftPosition[0], upperLeftPosition[1]);
|
||||||
if (upperControlPointPosition != null) {
|
if (upperControlPointPosition != null) {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,8 +12,9 @@ import * as glmatrix from 'gl-matrix';
|
||||||
|
|
||||||
import {AntialiasingStrategy, SubpixelAAType} from './aa-strategy';
|
import {AntialiasingStrategy, SubpixelAAType} from './aa-strategy';
|
||||||
import {createFramebuffer, createFramebufferDepthTexture, setTextureParameters} from './gl-utils';
|
import {createFramebuffer, createFramebufferDepthTexture, setTextureParameters} from './gl-utils';
|
||||||
|
import {Renderer} from './renderer';
|
||||||
import {unwrapNull} from './utils';
|
import {unwrapNull} from './utils';
|
||||||
import {DemoView, Renderer} from './view';
|
import {DemoView} from './view';
|
||||||
|
|
||||||
export default class SSAAStrategy extends AntialiasingStrategy {
|
export default class SSAAStrategy extends AntialiasingStrategy {
|
||||||
private level: number;
|
private level: number;
|
||||||
|
@ -50,11 +51,11 @@ export default class SSAAStrategy extends AntialiasingStrategy {
|
||||||
renderContext.gl.bindTexture(renderContext.gl.TEXTURE_2D, this.supersampledColorTexture);
|
renderContext.gl.bindTexture(renderContext.gl.TEXTURE_2D, this.supersampledColorTexture);
|
||||||
renderContext.gl.texImage2D(renderContext.gl.TEXTURE_2D,
|
renderContext.gl.texImage2D(renderContext.gl.TEXTURE_2D,
|
||||||
0,
|
0,
|
||||||
renderer.colorAlphaFormat,
|
renderContext.colorAlphaFormat,
|
||||||
this.supersampledFramebufferSize[0],
|
this.supersampledFramebufferSize[0],
|
||||||
this.supersampledFramebufferSize[1],
|
this.supersampledFramebufferSize[1],
|
||||||
0,
|
0,
|
||||||
renderer.colorAlphaFormat,
|
renderContext.colorAlphaFormat,
|
||||||
renderContext.gl.UNSIGNED_BYTE,
|
renderContext.gl.UNSIGNED_BYTE,
|
||||||
null);
|
null);
|
||||||
setTextureParameters(renderContext.gl, renderContext.gl.LINEAR);
|
setTextureParameters(renderContext.gl, renderContext.gl.LINEAR);
|
||||||
|
|
|
@ -18,6 +18,7 @@ import PathfinderBufferTexture from "./buffer-texture";
|
||||||
import {OrthographicCamera} from "./camera";
|
import {OrthographicCamera} from "./camera";
|
||||||
import {UniformMap} from './gl-utils';
|
import {UniformMap} from './gl-utils';
|
||||||
import {PathfinderMeshData} from "./meshes";
|
import {PathfinderMeshData} from "./meshes";
|
||||||
|
import {Renderer} from './renderer';
|
||||||
import {ShaderMap, ShaderProgramSource} from './shader-loader';
|
import {ShaderMap, ShaderProgramSource} from './shader-loader';
|
||||||
import SSAAStrategy from "./ssaa-strategy";
|
import SSAAStrategy from "./ssaa-strategy";
|
||||||
import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
|
import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
|
||||||
|
@ -78,24 +79,19 @@ class SVGDemoController extends DemoAppController<SVGDemoView> {
|
||||||
|
|
||||||
private meshesReceived(): void {
|
private meshesReceived(): void {
|
||||||
this.view.then(view => {
|
this.view.then(view => {
|
||||||
view.uploadPathColors(1);
|
|
||||||
view.uploadPathTransforms(1);
|
|
||||||
view.attachMeshes([this.meshes]);
|
view.attachMeshes([this.meshes]);
|
||||||
|
view.initCameraBounds(this.loader.bounds);
|
||||||
view.camera.bounds = this.loader.bounds;
|
|
||||||
view.camera.zoomToFit();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SVGDemoView extends DemoView {
|
class SVGDemoView extends DemoView {
|
||||||
camera: OrthographicCamera;
|
renderer: SVGDemoRenderer;
|
||||||
|
appController: SVGDemoController;
|
||||||
|
|
||||||
protected depthFunction: number = this.gl.GREATER;
|
get camera(): OrthographicCamera {
|
||||||
|
return this.renderer.camera;
|
||||||
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.fromValues(1.0, 1.0);
|
}
|
||||||
|
|
||||||
private appController: SVGDemoController;
|
|
||||||
|
|
||||||
constructor(appController: SVGDemoController,
|
constructor(appController: SVGDemoController,
|
||||||
commonShaderSource: string,
|
commonShaderSource: string,
|
||||||
|
@ -103,14 +99,26 @@ class SVGDemoView extends DemoView {
|
||||||
super(commonShaderSource, shaderSources);
|
super(commonShaderSource, shaderSources);
|
||||||
|
|
||||||
this.appController = appController;
|
this.appController = appController;
|
||||||
|
this.renderer = new SVGDemoRenderer(this);
|
||||||
|
|
||||||
this.camera = new OrthographicCamera(this.canvas, { scaleBounds: true });
|
this.resizeToFit(true);
|
||||||
this.camera.onPan = () => this.setDirty();
|
|
||||||
this.camera.onZoom = () => this.setDirty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initCameraBounds(bounds: glmatrix.vec4): void {
|
||||||
|
this.renderer.initCameraBounds(bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SVGDemoRenderer extends Renderer {
|
||||||
|
renderContext: SVGDemoView;
|
||||||
|
|
||||||
|
camera: OrthographicCamera;
|
||||||
|
|
||||||
get destAllocatedSize(): glmatrix.vec2 {
|
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 {
|
get destFramebuffer(): WebGLFramebuffer | null {
|
||||||
|
@ -121,21 +129,70 @@ class SVGDemoView extends DemoView {
|
||||||
return this.destAllocatedSize;
|
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 {
|
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 {
|
pathBoundingRects(objectIndex: number): Float32Array {
|
||||||
panic("SVGDemoView.pathBoundingRects(): TODO");
|
panic("SVGDemoRenderer.pathBoundingRects(): TODO");
|
||||||
return glmatrix.vec4.create();
|
return glmatrix.vec4.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
pathCountForObject(objectIndex: number): number {
|
attachMeshes(meshes: PathfinderMeshData[]): void {
|
||||||
return this.appController.loader.pathInstances.length;
|
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 {
|
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));
|
const pathColors = new Uint8Array(4 * (instances.length + 1));
|
||||||
|
|
||||||
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
|
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
|
||||||
|
@ -154,7 +211,7 @@ class SVGDemoView extends DemoView {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
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));
|
const pathTransforms = new Float32Array(4 * (instances.length + 1));
|
||||||
|
|
||||||
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
|
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
|
||||||
|
@ -174,30 +231,6 @@ class SVGDemoView extends DemoView {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected compositeIfNecessary(): void {}
|
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() {
|
function main() {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
|
||||||
import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from './gl-utils';
|
import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from './gl-utils';
|
||||||
import {UniformMap} from './gl-utils';
|
import {UniformMap} from './gl-utils';
|
||||||
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
||||||
|
import {Renderer} from './renderer';
|
||||||
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
|
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
|
||||||
import SSAAStrategy from './ssaa-strategy';
|
import SSAAStrategy from './ssaa-strategy';
|
||||||
import {calculatePixelDescent, calculatePixelRectForGlyph, PathfinderFont} from "./text";
|
import {calculatePixelDescent, calculatePixelRectForGlyph, PathfinderFont} from "./text";
|
||||||
|
@ -186,7 +187,7 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private hintingChanged(): void {
|
private hintingChanged(): void {
|
||||||
this.view.then(view => view.updateHinting());
|
this.view.then(view => view.renderer.updateHinting());
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateText(): void {
|
private updateText(): void {
|
||||||
|
@ -213,7 +214,7 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
this.meshes = meshes;
|
this.meshes = meshes;
|
||||||
|
|
||||||
view.attachText();
|
view.attachText();
|
||||||
view.uploadPathColors(1);
|
view.renderer.uploadPathColors(1);
|
||||||
view.attachMeshes([this.meshes]);
|
view.attachMeshes([this.meshes]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -240,7 +241,7 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
/// The font size in pixels per em.
|
/// The font size in pixels per em.
|
||||||
set fontSize(newFontSize: number) {
|
set fontSize(newFontSize: number) {
|
||||||
this._fontSize = newFontSize;
|
this._fontSize = newFontSize;
|
||||||
this.view.then(view => view.relayoutText());
|
this.view.then(view => view.renderer.relayoutText());
|
||||||
}
|
}
|
||||||
|
|
||||||
get layoutPixelsPerUnit(): number {
|
get layoutPixelsPerUnit(): number {
|
||||||
|
@ -265,6 +266,62 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextDemoView extends DemoView {
|
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;
|
atlasFramebuffer: WebGLFramebuffer;
|
||||||
atlasDepthTexture: WebGLTexture;
|
atlasDepthTexture: WebGLTexture;
|
||||||
|
|
||||||
|
@ -272,22 +329,22 @@ class TextDemoView extends DemoView {
|
||||||
glyphTexCoordsBuffer: WebGLBuffer;
|
glyphTexCoordsBuffer: WebGLBuffer;
|
||||||
glyphElementsBuffer: 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 {
|
get emboldenAmount(): glmatrix.vec2 {
|
||||||
return this.stemDarkeningAmount;
|
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 {
|
get bgColor(): glmatrix.vec4 {
|
||||||
return glmatrix.vec4.fromValues(1.0, 1.0, 1.0, 0.0);
|
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);
|
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;
|
protected get directCurveProgramName(): keyof ShaderMap<void> {
|
||||||
private stemDarkening: StemDarkeningMode;
|
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 glyphBounds: Float32Array;
|
||||||
|
private stemDarkening: StemDarkeningMode;
|
||||||
|
private subpixelAA: SubpixelAAType;
|
||||||
|
|
||||||
constructor(appController: TextDemoController,
|
private get displayPixelsPerUnit(): number {
|
||||||
commonShaderSource: string,
|
return this.renderContext.appController.layoutPixelsPerUnit;
|
||||||
shaderSources: ShaderMap<ShaderProgramSource>) {
|
}
|
||||||
super(commonShaderSource, shaderSources);
|
|
||||||
|
|
||||||
this.appController = appController;
|
constructor(renderContext: TextDemoView) {
|
||||||
|
super(renderContext);
|
||||||
|
|
||||||
this.camera = new OrthographicCamera(this.canvas, {
|
this.camera = new OrthographicCamera(this.renderContext.canvas, {
|
||||||
maxScale: MAX_SCALE,
|
maxScale: MAX_SCALE,
|
||||||
minScale: MIN_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,
|
setAntialiasingOptions(aaType: AntialiasingStrategyName,
|
||||||
|
@ -356,23 +413,53 @@ class TextDemoView extends DemoView {
|
||||||
// Need to relayout because changing AA options can cause font dilation to change...
|
// Need to relayout because changing AA options can cause font dilation to change...
|
||||||
this.layoutText();
|
this.layoutText();
|
||||||
this.buildAtlasGlyphs();
|
this.buildAtlasGlyphs();
|
||||||
this.setDirty();
|
this.renderContext.setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
setHintsUniform(uniforms: UniformMap): void {
|
setHintsUniform(uniforms: UniformMap): void {
|
||||||
const hint = this.createHint();
|
const hint = this.createHint();
|
||||||
this.gl.uniform4f(uniforms.uHints,
|
this.renderContext.gl.uniform4f(uniforms.uHints,
|
||||||
hint.xHeight,
|
hint.xHeight,
|
||||||
hint.hintedXHeight,
|
hint.hintedXHeight,
|
||||||
hint.stemHeight,
|
hint.stemHeight,
|
||||||
hint.hintedStemHeight);
|
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 {
|
pathBoundingRects(objectIndex: number): Float32Array {
|
||||||
const pathCount = this.appController.pathCount;
|
const pathCount = this.renderContext.appController.pathCount;
|
||||||
const atlasGlyphs = this.appController.atlasGlyphs;
|
const atlasGlyphs = this.renderContext.appController.atlasGlyphs;
|
||||||
const pixelsPerUnit = this.displayPixelsPerUnit;
|
const pixelsPerUnit = this.displayPixelsPerUnit;
|
||||||
const font = this.appController.font;
|
const font = this.renderContext.appController.font;
|
||||||
const hint = this.createHint();
|
const hint = this.createHint();
|
||||||
|
|
||||||
const boundingRects = new Float32Array((pathCount + 1) * 4);
|
const boundingRects = new Float32Array((pathCount + 1) * 4);
|
||||||
|
@ -393,16 +480,97 @@ class TextDemoView extends DemoView {
|
||||||
return boundingRects;
|
return boundingRects;
|
||||||
}
|
}
|
||||||
|
|
||||||
pathCountForObject(objectIndex: number): number {
|
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
||||||
return this.appController.pathCount;
|
aaLevel: number,
|
||||||
|
subpixelAA: SubpixelAAType,
|
||||||
|
stemDarkening: StemDarkeningMode):
|
||||||
|
AntialiasingStrategy {
|
||||||
|
this.subpixelAA = subpixelAA;
|
||||||
|
this.stemDarkening = stemDarkening;
|
||||||
|
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initContext() {
|
protected clearForDirectRendering(): void {
|
||||||
super.initContext();
|
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 {
|
protected pathColorsForObject(objectIndex: number): Uint8Array {
|
||||||
const pathCount = this.appController.pathCount;
|
const pathCount = this.renderContext.appController.pathCount;
|
||||||
|
|
||||||
const pathColors = new Uint8Array(4 * (pathCount + 1));
|
const pathColors = new Uint8Array(4 * (pathCount + 1));
|
||||||
|
|
||||||
|
@ -416,8 +584,8 @@ class TextDemoView extends DemoView {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
||||||
const pathCount = this.appController.pathCount;
|
const pathCount = this.renderContext.appController.pathCount;
|
||||||
const atlasGlyphs = this.appController.atlasGlyphs;
|
const atlasGlyphs = this.renderContext.appController.atlasGlyphs;
|
||||||
const pixelsPerUnit = this.displayPixelsPerUnit;
|
const pixelsPerUnit = this.displayPixelsPerUnit;
|
||||||
|
|
||||||
const transforms = new Float32Array((pathCount + 1) * 4);
|
const transforms = new Float32Array((pathCount + 1) * 4);
|
||||||
|
@ -435,97 +603,33 @@ class TextDemoView extends DemoView {
|
||||||
return transforms;
|
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() {
|
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 {
|
private createHint(): Hint {
|
||||||
return new Hint(this.appController.font,
|
return new Hint(this.renderContext.appController.font,
|
||||||
this.displayPixelsPerUnit,
|
this.displayPixelsPerUnit,
|
||||||
this.appController.useHinting);
|
this.renderContext.appController.useHinting);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lays out glyphs on the canvas.
|
|
||||||
private layoutText() {
|
private layoutText() {
|
||||||
const layout = this.appController.layout;
|
const layout = this.renderContext.appController.layout;
|
||||||
layout.layoutRuns();
|
layout.layoutRuns();
|
||||||
|
|
||||||
const textBounds = layout.textFrame.bounds;
|
const textBounds = layout.textFrame.bounds;
|
||||||
|
@ -537,7 +641,7 @@ class TextDemoView extends DemoView {
|
||||||
|
|
||||||
const hint = this.createHint();
|
const hint = this.createHint();
|
||||||
const displayPixelsPerUnit = this.displayPixelsPerUnit;
|
const displayPixelsPerUnit = this.displayPixelsPerUnit;
|
||||||
const layoutPixelsPerUnit = this.appController.layoutPixelsPerUnit;
|
const layoutPixelsPerUnit = this.renderContext.appController.layoutPixelsPerUnit;
|
||||||
|
|
||||||
let globalGlyphIndex = 0;
|
let globalGlyphIndex = 0;
|
||||||
for (const run of layout.textFrame.runs) {
|
for (const run of layout.textFrame.runs) {
|
||||||
|
@ -566,29 +670,38 @@ class TextDemoView extends DemoView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.glyphPositionsBuffer = unwrapNull(this.gl.createBuffer());
|
this.glyphPositionsBuffer = unwrapNull(this.renderContext.gl.createBuffer());
|
||||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
|
this.renderContext.gl.bindBuffer(this.renderContext.gl.ARRAY_BUFFER,
|
||||||
this.gl.bufferData(this.gl.ARRAY_BUFFER, glyphPositions, this.gl.STATIC_DRAW);
|
this.glyphPositionsBuffer);
|
||||||
this.glyphElementsBuffer = unwrapNull(this.gl.createBuffer());
|
this.renderContext.gl.bufferData(this.renderContext.gl.ARRAY_BUFFER,
|
||||||
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.glyphElementsBuffer);
|
glyphPositions,
|
||||||
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, glyphIndices, this.gl.STATIC_DRAW);
|
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() {
|
private buildAtlasGlyphs() {
|
||||||
const font = this.appController.font;
|
const appController = this.renderContext.appController;
|
||||||
const glyphStore = this.appController.glyphStore;
|
const font = appController.font;
|
||||||
const layoutPixelsPerUnit = this.appController.layoutPixelsPerUnit;
|
const glyphStore = appController.glyphStore;
|
||||||
|
const layoutPixelsPerUnit = appController.layoutPixelsPerUnit;
|
||||||
const displayPixelsPerUnit = this.displayPixelsPerUnit;
|
const displayPixelsPerUnit = this.displayPixelsPerUnit;
|
||||||
|
|
||||||
const textFrame = this.appController.layout.textFrame;
|
const textFrame = appController.layout.textFrame;
|
||||||
const hint = this.createHint();
|
const hint = this.createHint();
|
||||||
|
|
||||||
// Only build glyphs in view.
|
// Only build glyphs in view.
|
||||||
const translation = this.camera.translation;
|
const translation = this.camera.translation;
|
||||||
const canvasRect = glmatrix.vec4.fromValues(-translation[0],
|
const canvasRect = glmatrix.vec4.clone([
|
||||||
-translation[1],
|
-translation[0],
|
||||||
-translation[0] + this.canvas.width,
|
-translation[1],
|
||||||
-translation[1] + this.canvas.height);
|
-translation[0] + this.renderContext.canvas.width,
|
||||||
|
-translation[1] + this.renderContext.canvas.height,
|
||||||
|
]);
|
||||||
|
|
||||||
let atlasGlyphs = [];
|
let atlasGlyphs = [];
|
||||||
for (const run of textFrame.runs) {
|
for (const run of textFrame.runs) {
|
||||||
|
@ -621,12 +734,12 @@ class TextDemoView extends DemoView {
|
||||||
if (atlasGlyphs.length === 0)
|
if (atlasGlyphs.length === 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.appController.atlasGlyphs = atlasGlyphs;
|
appController.atlasGlyphs = atlasGlyphs;
|
||||||
this.appController.atlas.layoutGlyphs(atlasGlyphs,
|
appController.atlas.layoutGlyphs(atlasGlyphs,
|
||||||
font,
|
font,
|
||||||
displayPixelsPerUnit,
|
displayPixelsPerUnit,
|
||||||
hint,
|
hint,
|
||||||
this.stemDarkeningAmount);
|
this.stemDarkeningAmount);
|
||||||
|
|
||||||
this.uploadPathTransforms(1);
|
this.uploadPathTransforms(1);
|
||||||
|
|
||||||
|
@ -635,26 +748,14 @@ class TextDemoView extends DemoView {
|
||||||
this.setGlyphTexCoords();
|
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() {
|
private setGlyphTexCoords() {
|
||||||
const textFrame = this.appController.layout.textFrame;
|
const appController = this.renderContext.appController;
|
||||||
const font = this.appController.font;
|
const textFrame = appController.layout.textFrame;
|
||||||
const atlasGlyphs = this.appController.atlasGlyphs;
|
const font = appController.font;
|
||||||
|
const atlasGlyphs = appController.atlasGlyphs;
|
||||||
|
|
||||||
const hint = this.createHint();
|
const hint = this.createHint();
|
||||||
const layoutPixelsPerUnit = this.appController.layoutPixelsPerUnit;
|
const layoutPixelsPerUnit = appController.layoutPixelsPerUnit;
|
||||||
const displayPixelsPerUnit = this.displayPixelsPerUnit;
|
const displayPixelsPerUnit = this.displayPixelsPerUnit;
|
||||||
|
|
||||||
const atlasGlyphKeys = atlasGlyphs.map(atlasGlyph => atlasGlyph.glyphKey.sortKey);
|
const atlasGlyphKeys = atlasGlyphs.map(atlasGlyph => atlasGlyph.glyphKey.sortKey);
|
||||||
|
@ -707,61 +808,16 @@ class TextDemoView extends DemoView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.glyphTexCoordsBuffer = unwrapNull(this.gl.createBuffer());
|
this.glyphTexCoordsBuffer = unwrapNull(this.renderContext.gl.createBuffer());
|
||||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
|
this.renderContext.gl.bindBuffer(this.renderContext.gl.ARRAY_BUFFER,
|
||||||
this.gl.bufferData(this.gl.ARRAY_BUFFER, this.glyphBounds, this.gl.STATIC_DRAW);
|
this.glyphTexCoordsBuffer);
|
||||||
|
this.renderContext.gl.bufferData(this.renderContext.gl.ARRAY_BUFFER,
|
||||||
|
this.glyphBounds,
|
||||||
|
this.renderContext.gl.STATIC_DRAW);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setIdentityTexScaleUniform(uniforms: UniformMap) {
|
private setIdentityTexScaleUniform(uniforms: UniformMap) {
|
||||||
this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
|
this.renderContext.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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,6 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// 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 * as glmatrix from 'gl-matrix';
|
||||||
|
|
||||||
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
||||||
|
@ -19,18 +16,11 @@ import PathfinderBufferTexture from './buffer-texture';
|
||||||
import {Camera} from "./camera";
|
import {Camera} from "./camera";
|
||||||
import {EXTDisjointTimerQuery, QUAD_ELEMENTS, UniformMap} from './gl-utils';
|
import {EXTDisjointTimerQuery, QUAD_ELEMENTS, UniformMap} from './gl-utils';
|
||||||
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
||||||
|
import {Renderer} from './renderer';
|
||||||
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 {expectNotNull, PathfinderError, UINT32_SIZE, unwrapNull} from './utils';
|
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([
|
const QUAD_POSITIONS: Float32Array = new Float32Array([
|
||||||
0.0, 1.0,
|
0.0, 1.0,
|
||||||
1.0, 1.0,
|
1.0, 1.0,
|
||||||
|
@ -58,19 +48,23 @@ export interface Timings {
|
||||||
declare class WebGLQuery {}
|
declare class WebGLQuery {}
|
||||||
|
|
||||||
export abstract class PathfinderView {
|
export abstract class PathfinderView {
|
||||||
protected canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
|
|
||||||
protected camera: Camera;
|
protected abstract get camera(): Camera;
|
||||||
|
|
||||||
private dirty: boolean;
|
private dirty: boolean;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
|
|
||||||
this.canvas = unwrapNull(document.getElementById('pf-canvas')) as HTMLCanvasElement;
|
this.canvas = unwrapNull(document.getElementById('pf-canvas')) as HTMLCanvasElement;
|
||||||
|
|
||||||
window.addEventListener('resize', () => this.resizeToFit(false), false);
|
window.addEventListener('resize', () => this.resizeToFit(false), false);
|
||||||
this.resizeToFit(true);
|
}
|
||||||
|
|
||||||
|
setDirty() {
|
||||||
|
if (this.dirty)
|
||||||
|
return;
|
||||||
|
this.dirty = true;
|
||||||
|
window.requestAnimationFrame(() => this.redraw());
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomIn(): void {
|
zoomIn(): void {
|
||||||
|
@ -85,18 +79,11 @@ export abstract class PathfinderView {
|
||||||
this.setDirty();
|
this.setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected setDirty() {
|
|
||||||
if (this.dirty)
|
|
||||||
return;
|
|
||||||
this.dirty = true;
|
|
||||||
window.requestAnimationFrame(() => this.redraw());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected redraw() {
|
protected redraw() {
|
||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private resizeToFit(initialSize: boolean) {
|
protected resizeToFit(initialSize: boolean) {
|
||||||
const width = window.innerWidth;
|
const width = window.innerWidth;
|
||||||
|
|
||||||
let height = window.scrollY + window.innerHeight - this.canvas.getBoundingClientRect().top;
|
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;
|
gl: WebGLRenderingContext;
|
||||||
|
|
||||||
shaderPrograms: ShaderMap<PathfinderShaderProgram>;
|
shaderPrograms: ShaderMap<PathfinderShaderProgram>;
|
||||||
|
@ -129,29 +118,19 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
|
||||||
drawBuffersExt: WebGLDrawBuffers;
|
drawBuffersExt: WebGLDrawBuffers;
|
||||||
instancedArraysExt: ANGLEInstancedArrays;
|
instancedArraysExt: ANGLEInstancedArrays;
|
||||||
textureHalfFloatExt: OESTextureHalfFloat;
|
textureHalfFloatExt: OESTextureHalfFloat;
|
||||||
|
timerQueryExt: EXTDisjointTimerQuery;
|
||||||
vertexArrayObjectExt: OESVertexArrayObject;
|
vertexArrayObjectExt: OESVertexArrayObject;
|
||||||
|
|
||||||
quadPositionsBuffer: WebGLBuffer;
|
quadPositionsBuffer: WebGLBuffer;
|
||||||
quadTexCoordsBuffer: WebGLBuffer;
|
quadTexCoordsBuffer: WebGLBuffer;
|
||||||
quadElementsBuffer: WebGLBuffer;
|
quadElementsBuffer: WebGLBuffer;
|
||||||
|
|
||||||
|
atlasRenderingTimerQuery: WebGLQuery;
|
||||||
|
compositingTimerQuery: WebGLQuery;
|
||||||
|
|
||||||
meshes: PathfinderMeshBuffers[];
|
meshes: PathfinderMeshBuffers[];
|
||||||
meshData: PathfinderMeshData[];
|
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 {
|
get colorAlphaFormat(): GLenum {
|
||||||
return this.sRGBExt == null ? this.gl.RGBA : this.sRGBExt.SRGB_ALPHA_EXT;
|
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 sRGBExt: EXTsRGB;
|
||||||
protected timerQueryExt: EXTDisjointTimerQuery;
|
|
||||||
|
|
||||||
protected antialiasingStrategy: AntialiasingStrategy | null;
|
|
||||||
protected colorBufferHalfFloatExt: any;
|
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;
|
private wantsScreenshot: boolean;
|
||||||
|
|
||||||
|
/// NB: All subclasses are responsible for creating a renderer in their constructors.
|
||||||
constructor(commonShaderSource: string, shaderSources: ShaderMap<ShaderProgramSource>) {
|
constructor(commonShaderSource: string, shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.initContext();
|
this.initContext();
|
||||||
|
|
||||||
this.lastTimings = { rendering: 0, compositing: 0 };
|
|
||||||
|
|
||||||
const shaderSource = this.compileShaders(commonShaderSource, shaderSources);
|
const shaderSource = this.compileShaders(commonShaderSource, shaderSources);
|
||||||
this.shaderPrograms = this.linkShaders(shaderSource);
|
this.shaderPrograms = this.linkShaders(shaderSource);
|
||||||
|
|
||||||
this.pathTransformBufferTextures = [];
|
|
||||||
this.pathColorsBufferTextures = [];
|
|
||||||
|
|
||||||
if (this.pathIDsAreInstanced)
|
|
||||||
this.initInstancedPathIDVBO();
|
|
||||||
|
|
||||||
this.wantsScreenshot = false;
|
this.wantsScreenshot = false;
|
||||||
|
|
||||||
this.antialiasingStrategy = new NoAAStrategy(0, 'none');
|
|
||||||
this.antialiasingStrategy.init(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setAntialiasingOptions(aaType: AntialiasingStrategyName,
|
attachMeshes(meshes: PathfinderMeshData[]): void {
|
||||||
aaLevel: number,
|
this.renderer.attachMeshes(meshes);
|
||||||
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);
|
|
||||||
|
|
||||||
this.setDirty();
|
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);
|
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() {
|
queueScreenshot() {
|
||||||
this.wantsScreenshot = true;
|
this.wantsScreenshot = true;
|
||||||
this.setDirty();
|
this.setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadPathColors(objectCount: number) {
|
setAntialiasingOptions(aaType: AntialiasingStrategyName,
|
||||||
this.pathColorsBufferTextures = [];
|
aaLevel: number,
|
||||||
|
subpixelAA: SubpixelAAType,
|
||||||
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
|
stemDarkening: StemDarkeningMode) {
|
||||||
const pathColorsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathColors');
|
this.renderer.setAntialiasingOptions(aaType, aaLevel, subpixelAA, stemDarkening);
|
||||||
const pathColors = this.pathColorsForObject(objectIndex);
|
|
||||||
pathColorsBufferTexture.upload(this.gl, pathColors);
|
|
||||||
this.pathColorsBufferTextures.push(pathColorsBufferTexture);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
protected resized(): void {
|
||||||
super.resized();
|
super.resized();
|
||||||
|
this.renderer.canvasResized();
|
||||||
if (this.antialiasingStrategy != null)
|
|
||||||
this.antialiasingStrategy.init(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initContext() {
|
protected initContext() {
|
||||||
|
@ -350,46 +223,7 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
|
||||||
protected redraw() {
|
protected redraw() {
|
||||||
super.redraw();
|
super.redraw();
|
||||||
|
|
||||||
if (this.meshes == null)
|
this.renderer.redraw();
|
||||||
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();
|
|
||||||
|
|
||||||
// Invoke the post-render hook.
|
// Invoke the post-render hook.
|
||||||
this.renderingFinished();
|
this.renderingFinished();
|
||||||
|
@ -403,42 +237,6 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
|
||||||
|
|
||||||
protected renderingFinished(): void {}
|
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>):
|
private compileShaders(commonSource: string, shaderSources: ShaderMap<ShaderProgramSource>):
|
||||||
ShaderMap<UnlinkedShaderProgram> {
|
ShaderMap<UnlinkedShaderProgram> {
|
||||||
const shaders: Partial<ShaderMap<Partial<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>;
|
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() {
|
private takeScreenshot() {
|
||||||
const width = this.canvas.width, height = this.canvas.height;
|
const width = this.canvas.width, height = this.canvas.height;
|
||||||
const scratchCanvas = document.createElement('canvas');
|
const scratchCanvas = document.createElement('canvas');
|
||||||
|
@ -721,18 +297,6 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
|
||||||
scratchLink.click();
|
scratchLink.click();
|
||||||
document.body.removeChild(scratchLink);
|
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 {
|
export interface RenderContext {
|
||||||
|
@ -743,40 +307,19 @@ export interface RenderContext {
|
||||||
readonly drawBuffersExt: WebGLDrawBuffers;
|
readonly drawBuffersExt: WebGLDrawBuffers;
|
||||||
readonly instancedArraysExt: ANGLEInstancedArrays;
|
readonly instancedArraysExt: ANGLEInstancedArrays;
|
||||||
readonly textureHalfFloatExt: OESTextureHalfFloat;
|
readonly textureHalfFloatExt: OESTextureHalfFloat;
|
||||||
|
readonly timerQueryExt: EXTDisjointTimerQuery;
|
||||||
readonly vertexArrayObjectExt: OESVertexArrayObject;
|
readonly vertexArrayObjectExt: OESVertexArrayObject;
|
||||||
|
|
||||||
|
readonly colorAlphaFormat: GLenum;
|
||||||
|
|
||||||
readonly shaderPrograms: ShaderMap<PathfinderShaderProgram>;
|
readonly shaderPrograms: ShaderMap<PathfinderShaderProgram>;
|
||||||
|
|
||||||
readonly quadPositionsBuffer: WebGLBuffer;
|
readonly quadPositionsBuffer: WebGLBuffer;
|
||||||
readonly quadElementsBuffer: WebGLBuffer;
|
readonly quadElementsBuffer: WebGLBuffer;
|
||||||
|
|
||||||
|
readonly atlasRenderingTimerQuery: WebGLQuery;
|
||||||
|
readonly compositingTimerQuery: WebGLQuery;
|
||||||
|
|
||||||
initQuadVAO(attributes: any): void;
|
initQuadVAO(attributes: any): void;
|
||||||
}
|
setDirty(): 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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,11 @@ import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
|
||||||
import {createFramebufferDepthTexture, setTextureParameters, UniformMap} from './gl-utils';
|
import {createFramebufferDepthTexture, setTextureParameters, UniformMap} from './gl-utils';
|
||||||
import {WebGLVertexArrayObject} 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 {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 {PathfinderShaderProgram} from './shader-loader';
|
||||||
import {computeStemDarkeningAmount} from './text';
|
import {computeStemDarkeningAmount} from './text';
|
||||||
import {FLOAT32_SIZE, lerp, UINT32_SIZE, unwrapNull} from './utils';
|
import {FLOAT32_SIZE, lerp, UINT32_SIZE, unwrapNull} from './utils';
|
||||||
import {RenderContext, Renderer} from './view';
|
import {RenderContext} from './view';
|
||||||
|
|
||||||
interface FastEdgeVAOs {
|
interface FastEdgeVAOs {
|
||||||
upper: WebGLVertexArrayObject;
|
upper: WebGLVertexArrayObject;
|
||||||
|
@ -145,10 +146,10 @@ export abstract class XCAAStrategy extends AntialiasingStrategy {
|
||||||
const renderContext = renderer.renderContext;
|
const renderContext = renderer.renderContext;
|
||||||
this.directColorTexture = createFramebufferColorTexture(renderContext.gl,
|
this.directColorTexture = createFramebufferColorTexture(renderContext.gl,
|
||||||
this.destFramebufferSize,
|
this.destFramebufferSize,
|
||||||
renderer.colorAlphaFormat);
|
renderContext.colorAlphaFormat);
|
||||||
this.directPathIDTexture = createFramebufferColorTexture(renderContext.gl,
|
this.directPathIDTexture = createFramebufferColorTexture(renderContext.gl,
|
||||||
this.destFramebufferSize,
|
this.destFramebufferSize,
|
||||||
renderer.colorAlphaFormat);
|
renderContext.colorAlphaFormat);
|
||||||
this.directFramebuffer =
|
this.directFramebuffer =
|
||||||
createFramebuffer(renderContext.gl,
|
createFramebuffer(renderContext.gl,
|
||||||
renderContext.drawBuffersExt,
|
renderContext.drawBuffersExt,
|
||||||
|
@ -1049,10 +1050,10 @@ export class MCAAMulticolorStrategy extends MCAAStrategy {
|
||||||
const renderContext = renderer.renderContext;
|
const renderContext = renderer.renderContext;
|
||||||
this.bgColorTexture = createFramebufferColorTexture(renderContext.gl,
|
this.bgColorTexture = createFramebufferColorTexture(renderContext.gl,
|
||||||
this.supersampledFramebufferSize,
|
this.supersampledFramebufferSize,
|
||||||
renderer.colorAlphaFormat);
|
renderContext.colorAlphaFormat);
|
||||||
this.fgColorTexture = createFramebufferColorTexture(renderContext.gl,
|
this.fgColorTexture = createFramebufferColorTexture(renderContext.gl,
|
||||||
this.supersampledFramebufferSize,
|
this.supersampledFramebufferSize,
|
||||||
renderer.colorAlphaFormat);
|
renderContext.colorAlphaFormat);
|
||||||
this.edgeDetectFramebuffer = createFramebuffer(renderContext.gl,
|
this.edgeDetectFramebuffer = createFramebuffer(renderContext.gl,
|
||||||
renderContext.drawBuffersExt,
|
renderContext.drawBuffersExt,
|
||||||
[this.bgColorTexture, this.fgColorTexture],
|
[this.bgColorTexture, this.fgColorTexture],
|
||||||
|
|
Loading…
Reference in New Issue