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 {UniformMap} from './gl-utils';
|
||||
import {PathfinderMeshData} from "./meshes";
|
||||
import {Renderer} from './renderer';
|
||||
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
|
||||
import SSAAStrategy from "./ssaa-strategy";
|
||||
import {BUILTIN_FONT_URI, ExpandedMeshData} from "./text";
|
||||
|
@ -252,78 +253,171 @@ class ThreeDController extends DemoAppController<ThreeDView> {
|
|||
return this.baseMeshes.expand([glyphIndex + 1]);
|
||||
});
|
||||
|
||||
this.view.then(view => {
|
||||
view.uploadPathColors(this.expandedMeshes.length);
|
||||
view.uploadPathTransforms(this.expandedMeshes.length);
|
||||
view.attachMeshes(this.expandedMeshes);
|
||||
});
|
||||
this.view.then(view => view.attachMeshes(this.expandedMeshes));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ThreeDView extends DemoView {
|
||||
destFramebuffer: WebGLFramebuffer | null = null;
|
||||
renderer: ThreeDRenderer;
|
||||
|
||||
camera: PerspectiveCamera;
|
||||
appController: ThreeDController;
|
||||
|
||||
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.clone([1.0, 1.0]);
|
||||
|
||||
protected directCurveProgramName: keyof ShaderMap<void> = 'direct3DCurve';
|
||||
protected directInteriorProgramName: keyof ShaderMap<void> = 'direct3DInterior';
|
||||
|
||||
protected depthFunction: number = this.gl.LESS;
|
||||
|
||||
protected get pathIDsAreInstanced(): boolean {
|
||||
return true;
|
||||
protected get camera(): PerspectiveCamera {
|
||||
return this.renderer.camera;
|
||||
}
|
||||
|
||||
private _scale: number;
|
||||
|
||||
private appController: ThreeDController;
|
||||
|
||||
private cubeVertexPositionBuffer: WebGLBuffer;
|
||||
private cubeIndexBuffer: WebGLBuffer;
|
||||
|
||||
constructor(appController: ThreeDController,
|
||||
commonShaderSource: string,
|
||||
shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||
super(commonShaderSource, shaderSources);
|
||||
|
||||
this.appController = appController;
|
||||
this.renderer = new ThreeDRenderer(this);
|
||||
|
||||
this.camera = new PerspectiveCamera(this.canvas, {
|
||||
this.resizeToFit(true);
|
||||
}
|
||||
}
|
||||
|
||||
class ThreeDRenderer extends Renderer {
|
||||
renderContext: ThreeDView;
|
||||
|
||||
camera: PerspectiveCamera;
|
||||
|
||||
get destFramebuffer(): WebGLFramebuffer | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
get destAllocatedSize(): glmatrix.vec2 {
|
||||
return glmatrix.vec2.clone([
|
||||
this.renderContext.canvas.width,
|
||||
this.renderContext.canvas.height,
|
||||
]);
|
||||
}
|
||||
|
||||
get destUsedSize(): glmatrix.vec2 {
|
||||
return this.destAllocatedSize;
|
||||
}
|
||||
|
||||
protected get directCurveProgramName(): keyof ShaderMap<void> {
|
||||
return 'direct3DCurve';
|
||||
}
|
||||
|
||||
protected get directInteriorProgramName(): keyof ShaderMap<void> {
|
||||
return 'direct3DInterior';
|
||||
}
|
||||
|
||||
protected get depthFunction(): number {
|
||||
return this.renderContext.gl.LESS;
|
||||
}
|
||||
|
||||
protected get pathIDsAreInstanced(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected get worldTransform() {
|
||||
return this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE);
|
||||
}
|
||||
|
||||
protected get usedSizeFactor(): glmatrix.vec2 {
|
||||
return glmatrix.vec2.clone([1.0, 1.0]);
|
||||
}
|
||||
|
||||
private cubeVertexPositionBuffer: WebGLBuffer;
|
||||
private cubeIndexBuffer: WebGLBuffer;
|
||||
|
||||
constructor(renderContext: ThreeDView) {
|
||||
super(renderContext);
|
||||
|
||||
this.camera = new PerspectiveCamera(renderContext.canvas, {
|
||||
innerCollisionExtent: MONUMENT_SCALE[0],
|
||||
});
|
||||
this.camera.onChange = () => this.setDirty();
|
||||
this.camera.onChange = () => renderContext.setDirty();
|
||||
|
||||
this.cubeVertexPositionBuffer = unwrapNull(this.gl.createBuffer());
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
|
||||
this.gl.bufferData(this.gl.ARRAY_BUFFER, CUBE_VERTEX_POSITIONS, this.gl.STATIC_DRAW);
|
||||
this.cubeVertexPositionBuffer = unwrapNull(renderContext.gl.createBuffer());
|
||||
renderContext.gl.bindBuffer(renderContext.gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
|
||||
renderContext.gl.bufferData(renderContext.gl.ARRAY_BUFFER,
|
||||
CUBE_VERTEX_POSITIONS,
|
||||
renderContext.gl.STATIC_DRAW);
|
||||
|
||||
this.cubeIndexBuffer = unwrapNull(this.gl.createBuffer());
|
||||
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer);
|
||||
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, CUBE_INDICES, this.gl.STATIC_DRAW);
|
||||
this.cubeIndexBuffer = unwrapNull(renderContext.gl.createBuffer());
|
||||
renderContext.gl.bindBuffer(renderContext.gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer);
|
||||
renderContext.gl.bufferData(renderContext.gl.ELEMENT_ARRAY_BUFFER,
|
||||
CUBE_INDICES,
|
||||
renderContext.gl.STATIC_DRAW);
|
||||
}
|
||||
|
||||
setHintsUniform(uniforms: UniformMap): void {
|
||||
this.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
|
||||
}
|
||||
attachMeshes(expandedMeshes: PathfinderMeshData[]) {
|
||||
super.attachMeshes(expandedMeshes);
|
||||
|
||||
pathBoundingRects(objectIndex: number): Float32Array {
|
||||
panic("ThreeDView.pathBoundingRects(): TODO");
|
||||
return glmatrix.vec4.create();
|
||||
this.uploadPathColors(expandedMeshes.length);
|
||||
this.uploadPathTransforms(expandedMeshes.length);
|
||||
}
|
||||
|
||||
pathCountForObject(objectIndex: number): number {
|
||||
return this.appController.meshDescriptors[objectIndex].positions.length;
|
||||
return this.renderContext.appController.meshDescriptors[objectIndex].positions.length;
|
||||
}
|
||||
|
||||
pathBoundingRects(objectIndex: number): Float32Array {
|
||||
panic("ThreeDRenderer.pathBoundingRects(): TODO");
|
||||
return glmatrix.vec4.create();
|
||||
}
|
||||
|
||||
setHintsUniform(uniforms: UniformMap): void {
|
||||
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
protected drawSceneryIfNecessary(): void {
|
||||
// Set up the cube VBO.
|
||||
const shaderProgram = this.renderContext.shaderPrograms.demo3DMonument;
|
||||
this.renderContext.gl.useProgram(shaderProgram.program);
|
||||
this.renderContext.gl.bindBuffer(this.renderContext.gl.ARRAY_BUFFER,
|
||||
this.cubeVertexPositionBuffer);
|
||||
this.renderContext.gl.bindBuffer(this.renderContext.gl.ELEMENT_ARRAY_BUFFER,
|
||||
this.cubeIndexBuffer);
|
||||
this.renderContext.gl.vertexAttribPointer(shaderProgram.attributes.aPosition,
|
||||
3,
|
||||
this.renderContext.gl.FLOAT,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
this.renderContext.gl.enableVertexAttribArray(shaderProgram.attributes.aPosition);
|
||||
|
||||
// Set uniforms for the monument.
|
||||
const transform = this.calculateWorldTransform(MONUMENT_TRANSLATION, MONUMENT_SCALE);
|
||||
this.renderContext.gl.uniformMatrix4fv(shaderProgram.uniforms.uTransform,
|
||||
false,
|
||||
transform);
|
||||
this.renderContext.gl.uniform4f(shaderProgram.uniforms.uColor,
|
||||
MONUMENT_COLOR[0],
|
||||
MONUMENT_COLOR[1],
|
||||
MONUMENT_COLOR[2],
|
||||
1.0);
|
||||
|
||||
// Set state for the monument.
|
||||
this.renderContext.gl.enable(this.renderContext.gl.DEPTH_TEST);
|
||||
this.renderContext.gl.depthMask(true);
|
||||
this.renderContext.gl.disable(this.renderContext.gl.SCISSOR_TEST);
|
||||
|
||||
// Draw the monument!
|
||||
this.renderContext.gl.drawElements(this.renderContext.gl.TRIANGLES,
|
||||
CUBE_INDICES.length,
|
||||
this.renderContext.gl.UNSIGNED_SHORT,
|
||||
0);
|
||||
|
||||
// Clear to avoid Z-fighting.
|
||||
this.renderContext.gl.clearDepth(1.0);
|
||||
this.renderContext.gl.clear(this.renderContext.gl.DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
protected compositeIfNecessary(): void {}
|
||||
|
||||
protected pathColorsForObject(objectIndex: number): Uint8Array {
|
||||
return TEXT_COLOR;
|
||||
}
|
||||
|
||||
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
||||
const meshDescriptor = this.appController.meshDescriptors[objectIndex];
|
||||
const meshDescriptor = this.renderContext.appController.meshDescriptors[objectIndex];
|
||||
const pathCount = this.pathCountForObject(objectIndex);
|
||||
const pathTransforms = new Float32Array(4 * (pathCount + 1));
|
||||
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
|
||||
|
@ -333,6 +427,10 @@ class ThreeDView extends DemoView {
|
|||
return pathTransforms;
|
||||
}
|
||||
|
||||
protected meshInstanceCountForObject(objectIndex: number): number {
|
||||
return this.renderContext.appController.meshDescriptors[objectIndex].positions.length;
|
||||
}
|
||||
|
||||
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
||||
aaLevel: number,
|
||||
subpixelAA: SubpixelAAType):
|
||||
|
@ -342,57 +440,20 @@ class ThreeDView extends DemoView {
|
|||
throw new PathfinderError("Unsupported antialiasing type!");
|
||||
}
|
||||
|
||||
protected drawSceneryIfNecessary(): void {
|
||||
// Set up the cube VBO.
|
||||
const shaderProgram = this.shaderPrograms.demo3DMonument;
|
||||
this.gl.useProgram(shaderProgram.program);
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer);
|
||||
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer);
|
||||
this.gl.vertexAttribPointer(shaderProgram.attributes.aPosition,
|
||||
3,
|
||||
this.gl.FLOAT,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
this.gl.enableVertexAttribArray(shaderProgram.attributes.aPosition);
|
||||
|
||||
// Set uniforms for the monument.
|
||||
const transform = this.calculateWorldTransform(MONUMENT_TRANSLATION, MONUMENT_SCALE);
|
||||
this.gl.uniformMatrix4fv(shaderProgram.uniforms.uTransform, false, transform);
|
||||
this.gl.uniform4f(shaderProgram.uniforms.uColor,
|
||||
MONUMENT_COLOR[0],
|
||||
MONUMENT_COLOR[1],
|
||||
MONUMENT_COLOR[2],
|
||||
1.0);
|
||||
|
||||
// Set state for the monument.
|
||||
this.gl.enable(this.gl.DEPTH_TEST);
|
||||
this.gl.depthMask(true);
|
||||
this.gl.disable(this.gl.SCISSOR_TEST);
|
||||
|
||||
// Draw the monument!
|
||||
this.gl.drawElements(this.gl.TRIANGLES, CUBE_INDICES.length, this.gl.UNSIGNED_SHORT, 0);
|
||||
|
||||
// Clear to avoid Z-fighting.
|
||||
this.gl.clearDepth(1.0);
|
||||
this.gl.clear(this.gl.DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
protected compositeIfNecessary(): void {}
|
||||
|
||||
protected newTimingsReceived() {
|
||||
this.appController.newTimingsReceived(_.pick(this.lastTimings, ['rendering']));
|
||||
}
|
||||
|
||||
protected clearForDirectRendering(): void {
|
||||
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
||||
this.gl.clearDepth(1.0);
|
||||
this.gl.depthMask(true);
|
||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
||||
this.renderContext.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
||||
this.renderContext.gl.clearDepth(1.0);
|
||||
this.renderContext.gl.depthMask(true);
|
||||
this.renderContext.gl.clear(this.renderContext.gl.COLOR_BUFFER_BIT |
|
||||
this.renderContext.gl.DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
protected getModelviewTransform(objectIndex: number): glmatrix.mat4 {
|
||||
const textFrameIndex = this.appController.meshDescriptors[objectIndex].textFrameIndex;
|
||||
const textFrameIndex = this.renderContext
|
||||
.appController
|
||||
.meshDescriptors[objectIndex]
|
||||
.textFrameIndex;
|
||||
|
||||
const transform = glmatrix.mat4.create();
|
||||
glmatrix.mat4.rotateY(transform, transform, Math.PI / 2.0 * textFrameIndex);
|
||||
glmatrix.mat4.translate(transform, transform, TEXT_TRANSLATION);
|
||||
|
@ -401,7 +462,11 @@ class ThreeDView extends DemoView {
|
|||
|
||||
// Cheap but effective backface culling.
|
||||
protected shouldRenderObject(objectIndex: number): boolean {
|
||||
const textFrameIndex = this.appController.meshDescriptors[objectIndex].textFrameIndex;
|
||||
const textFrameIndex = this.renderContext
|
||||
.appController
|
||||
.meshDescriptors[objectIndex]
|
||||
.textFrameIndex;
|
||||
|
||||
const translation = this.camera.translation;
|
||||
const extent = TEXT_TRANSLATION[2] * TEXT_SCALE[2];
|
||||
switch (textFrameIndex) {
|
||||
|
@ -411,26 +476,19 @@ class ThreeDView extends DemoView {
|
|||
default: return translation[0] > extent;
|
||||
}
|
||||
}
|
||||
|
||||
protected meshInstanceCountForObject(objectIndex: number): number {
|
||||
return this.appController.meshDescriptors[objectIndex].positions.length;
|
||||
}
|
||||
|
||||
get destAllocatedSize(): glmatrix.vec2 {
|
||||
return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
get destUsedSize(): glmatrix.vec2 {
|
||||
return this.destAllocatedSize;
|
||||
protected newTimingsReceived() {
|
||||
const newTimings: Timings = _.pick(this.lastTimings, ['rendering']);
|
||||
this.renderContext.appController.newTimingsReceived(newTimings);
|
||||
}
|
||||
|
||||
private calculateWorldTransform(modelviewTranslation: glmatrix.vec3,
|
||||
modelviewScale: glmatrix.vec3):
|
||||
glmatrix.mat4 {
|
||||
const canvas = this.renderContext.canvas;
|
||||
const projection = glmatrix.mat4.create();
|
||||
glmatrix.mat4.perspective(projection,
|
||||
FOV / 180.0 * Math.PI,
|
||||
this.canvas.width / this.canvas.height,
|
||||
canvas.width / canvas.height,
|
||||
NEAR_CLIP_PLANE,
|
||||
FAR_CLIP_PLANE);
|
||||
|
||||
|
@ -444,10 +502,6 @@ class ThreeDView extends DemoView {
|
|||
glmatrix.mat4.mul(transform, projection, modelview);
|
||||
return transform;
|
||||
}
|
||||
|
||||
protected get worldTransform() {
|
||||
return this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE);
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
|
||||
import * as glmatrix from 'gl-matrix';
|
||||
|
||||
import {DemoView, Renderer} from './view';
|
||||
import {Renderer} from './renderer';
|
||||
import {DemoView} from './view';
|
||||
|
||||
export type AntialiasingStrategyName = 'none' | 'ssaa' | 'xcaa';
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import PathfinderBufferTexture from './buffer-texture';
|
|||
import {OrthographicCamera} from './camera';
|
||||
import {UniformMap} from './gl-utils';
|
||||
import {PathfinderMeshData} from "./meshes";
|
||||
import {Renderer} from './renderer';
|
||||
import {ShaderMap, ShaderProgramSource} from "./shader-loader";
|
||||
import SSAAStrategy from './ssaa-strategy';
|
||||
import {BUILTIN_FONT_URI, ExpandedMeshData, GlyphStore, PathfinderFont, TextFrame} from "./text";
|
||||
|
@ -127,8 +128,6 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
|||
this.expandedMeshes = expandedMeshes;
|
||||
|
||||
this.view.then(view => {
|
||||
view.uploadPathColors(1);
|
||||
view.uploadPathTransforms(1);
|
||||
view.attachMeshes([expandedMeshes.meshes]);
|
||||
});
|
||||
});
|
||||
|
@ -237,10 +236,44 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
|
|||
}
|
||||
|
||||
class BenchmarkTestView extends DemoView {
|
||||
destFramebuffer: WebGLFramebuffer | null = null;
|
||||
readonly renderer: BenchmarkRenderer;
|
||||
readonly appController: BenchmarkAppController;
|
||||
|
||||
get camera(): OrthographicCamera {
|
||||
return this.renderer.camera;
|
||||
}
|
||||
|
||||
set pixelsPerEm(newPPEM: number) {
|
||||
this.renderer.pixelsPerEm = newPPEM;
|
||||
}
|
||||
|
||||
set renderingPromiseCallback(newCallback: (time: number) => void) {
|
||||
this.renderer.renderingPromiseCallback = newCallback;
|
||||
}
|
||||
|
||||
constructor(appController: BenchmarkAppController,
|
||||
commonShaderSource: string,
|
||||
shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||
super(commonShaderSource, shaderSources);
|
||||
|
||||
this.appController = appController;
|
||||
this.renderer = new BenchmarkRenderer(this);
|
||||
|
||||
this.resizeToFit(true);
|
||||
}
|
||||
}
|
||||
|
||||
class BenchmarkRenderer extends Renderer {
|
||||
renderContext: BenchmarkTestView;
|
||||
|
||||
camera: OrthographicCamera;
|
||||
|
||||
renderingPromiseCallback: ((time: number) => void) | null;
|
||||
|
||||
get destFramebuffer(): WebGLFramebuffer | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
get bgColor(): glmatrix.vec4 {
|
||||
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 0.0]);
|
||||
}
|
||||
|
@ -249,50 +282,99 @@ class BenchmarkTestView extends DemoView {
|
|||
return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]);
|
||||
}
|
||||
|
||||
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.clone([1.0, 1.0]);
|
||||
get destAllocatedSize(): glmatrix.vec2 {
|
||||
const canvas = this.renderContext.canvas;
|
||||
return glmatrix.vec2.clone([canvas.width, canvas.height]);
|
||||
}
|
||||
|
||||
protected directCurveProgramName: keyof ShaderMap<void> = 'directCurve';
|
||||
protected directInteriorProgramName: keyof ShaderMap<void> = 'directInterior';
|
||||
|
||||
protected depthFunction: number = this.gl.GREATER;
|
||||
|
||||
protected camera: OrthographicCamera;
|
||||
|
||||
private _pixelsPerEm: number = 32.0;
|
||||
|
||||
private readonly appController: BenchmarkAppController;
|
||||
get destUsedSize(): glmatrix.vec2 {
|
||||
return this.destAllocatedSize;
|
||||
}
|
||||
|
||||
get emboldenAmount(): glmatrix.vec2 {
|
||||
return this.stemDarkeningAmount;
|
||||
}
|
||||
|
||||
get pixelsPerEm(): number {
|
||||
return this._pixelsPerEm;
|
||||
}
|
||||
|
||||
set pixelsPerEm(newPixelsPerEm: number) {
|
||||
this._pixelsPerEm = newPixelsPerEm;
|
||||
this.uploadPathTransforms(1);
|
||||
this.renderContext.setDirty();
|
||||
}
|
||||
|
||||
protected get usedSizeFactor(): glmatrix.vec2 {
|
||||
return glmatrix.vec2.clone([1.0, 1.0]);
|
||||
}
|
||||
|
||||
protected get directCurveProgramName(): keyof ShaderMap<void> {
|
||||
return 'directCurve';
|
||||
}
|
||||
|
||||
protected get directInteriorProgramName(): keyof ShaderMap<void> {
|
||||
return 'directInterior';
|
||||
}
|
||||
|
||||
protected get depthFunction(): number {
|
||||
return this.renderContext.gl.GREATER;
|
||||
}
|
||||
|
||||
protected get worldTransform() {
|
||||
const canvas = this.renderContext.canvas;
|
||||
|
||||
const transform = glmatrix.mat4.create();
|
||||
const translation = this.camera.translation;
|
||||
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
|
||||
glmatrix.mat4.scale(transform, transform, [2.0 / canvas.width, 2.0 / canvas.height, 1.0]);
|
||||
glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
|
||||
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
|
||||
|
||||
const pixelsPerUnit = this.pixelsPerUnit;
|
||||
glmatrix.mat4.scale(transform, transform, [pixelsPerUnit, pixelsPerUnit, 1.0]);
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
private _pixelsPerEm: number = 32.0;
|
||||
|
||||
private get pixelsPerUnit(): number {
|
||||
const font = unwrapNull(this.renderContext.appController.font);
|
||||
return this._pixelsPerEm / font.opentypeFont.unitsPerEm;
|
||||
}
|
||||
|
||||
private get stemDarkeningAmount(): glmatrix.vec2 {
|
||||
return computeStemDarkeningAmount(this._pixelsPerEm, this.pixelsPerUnit);
|
||||
}
|
||||
|
||||
constructor(appController: BenchmarkAppController,
|
||||
commonShaderSource: string,
|
||||
shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||
super(commonShaderSource, shaderSources);
|
||||
constructor(renderContext: BenchmarkTestView) {
|
||||
super(renderContext);
|
||||
|
||||
this.appController = appController;
|
||||
|
||||
this.camera = new OrthographicCamera(this.canvas);
|
||||
this.camera.onPan = () => this.setDirty();
|
||||
this.camera.onZoom = () => this.setDirty();
|
||||
this.camera = new OrthographicCamera(renderContext.canvas);
|
||||
this.camera.onPan = () => renderContext.setDirty();
|
||||
this.camera.onZoom = () => renderContext.setDirty();
|
||||
}
|
||||
|
||||
setHintsUniform(uniforms: UniformMap): void {
|
||||
this.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
|
||||
attachMeshes(meshes: PathfinderMeshData[]): void {
|
||||
super.attachMeshes(meshes);
|
||||
|
||||
this.uploadPathColors(1);
|
||||
this.uploadPathTransforms(1);
|
||||
}
|
||||
|
||||
pathCountForObject(objectIndex: number): number {
|
||||
return STRING.length;
|
||||
}
|
||||
|
||||
pathBoundingRects(objectIndex: number): Float32Array {
|
||||
const font = unwrapNull(this.appController.font);
|
||||
const appController = this.renderContext.appController;
|
||||
const font = unwrapNull(appController.font);
|
||||
|
||||
const boundingRects = new Float32Array((STRING.length + 1) * 4);
|
||||
|
||||
for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
|
||||
const glyphID = unwrapNull(this.appController.textRun).glyphIDs[glyphIndex];
|
||||
const glyphID = unwrapNull(appController.textRun).glyphIDs[glyphIndex];
|
||||
|
||||
const metrics = font.metricsForGlyph(glyphID);
|
||||
if (metrics == null)
|
||||
|
@ -307,8 +389,8 @@ class BenchmarkTestView extends DemoView {
|
|||
return boundingRects;
|
||||
}
|
||||
|
||||
pathCountForObject(objectIndex: number): number {
|
||||
return STRING.length;
|
||||
setHintsUniform(uniforms: UniformMap): void {
|
||||
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
||||
|
@ -324,6 +406,14 @@ class BenchmarkTestView extends DemoView {
|
|||
// TODO(pcwalton)
|
||||
}
|
||||
|
||||
protected renderingFinished(): void {
|
||||
if (this.renderingPromiseCallback == null)
|
||||
return;
|
||||
const glyphCount = unwrapNull(this.renderContext.appController.textRun).glyphIDs.length;
|
||||
const usPerGlyph = this.lastTimings.rendering * 1000.0 / glyphCount;
|
||||
this.renderingPromiseCallback(usPerGlyph);
|
||||
}
|
||||
|
||||
protected pathColorsForObject(objectIndex: number): Uint8Array {
|
||||
const pathColors = new Uint8Array(4 * (STRING.length + 1));
|
||||
for (let pathIndex = 0; pathIndex < STRING.length; pathIndex++)
|
||||
|
@ -332,20 +422,21 @@ class BenchmarkTestView extends DemoView {
|
|||
}
|
||||
|
||||
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
||||
const appController = this.renderContext.appController;
|
||||
const canvas = this.renderContext.canvas;
|
||||
const font = unwrapNull(appController.font);
|
||||
|
||||
const pathTransforms = new Float32Array(4 * (STRING.length + 1));
|
||||
|
||||
let currentX = 0, currentY = 0;
|
||||
const availableWidth = this.canvas.width / this.pixelsPerUnit;
|
||||
const lineHeight = unwrapNull(this.appController.font).opentypeFont.lineHeight();
|
||||
const availableWidth = canvas.width / this.pixelsPerUnit;
|
||||
const lineHeight = font.opentypeFont.lineHeight();
|
||||
|
||||
for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) {
|
||||
const glyphID = unwrapNull(this.appController.textRun).glyphIDs[glyphIndex];
|
||||
const glyphID = unwrapNull(appController.textRun).glyphIDs[glyphIndex];
|
||||
pathTransforms.set([1, 1, currentX, currentY], (glyphIndex + 1) * 4);
|
||||
|
||||
currentX += unwrapNull(this.appController.font).opentypeFont
|
||||
.glyphs
|
||||
.get(glyphID)
|
||||
.advanceWidth;
|
||||
currentX += font.opentypeFont.glyphs.get(glyphID).advanceWidth;
|
||||
if (currentX > availableWidth) {
|
||||
currentX = 0;
|
||||
currentY += lineHeight;
|
||||
|
@ -354,52 +445,6 @@ class BenchmarkTestView extends DemoView {
|
|||
|
||||
return pathTransforms;
|
||||
}
|
||||
|
||||
protected renderingFinished(): void {
|
||||
if (this.renderingPromiseCallback != null) {
|
||||
const glyphCount = unwrapNull(this.appController.textRun).glyphIDs.length;
|
||||
const usPerGlyph = this.lastTimings.rendering * 1000.0 / glyphCount;
|
||||
this.renderingPromiseCallback(usPerGlyph);
|
||||
}
|
||||
}
|
||||
|
||||
get destAllocatedSize(): glmatrix.vec2 {
|
||||
return glmatrix.vec2.clone([this.canvas.width, this.canvas.height]);
|
||||
}
|
||||
|
||||
get destUsedSize(): glmatrix.vec2 {
|
||||
return this.destAllocatedSize;
|
||||
}
|
||||
|
||||
protected get worldTransform() {
|
||||
const transform = glmatrix.mat4.create();
|
||||
const translation = this.camera.translation;
|
||||
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
|
||||
glmatrix.mat4.scale(transform,
|
||||
transform,
|
||||
[2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]);
|
||||
glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
|
||||
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
|
||||
|
||||
const pixelsPerUnit = this.pixelsPerUnit;
|
||||
glmatrix.mat4.scale(transform, transform, [pixelsPerUnit, pixelsPerUnit, 1.0]);
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
private get pixelsPerUnit(): number {
|
||||
return this._pixelsPerEm / unwrapNull(this.appController.font).opentypeFont.unitsPerEm;
|
||||
}
|
||||
|
||||
get pixelsPerEm(): number {
|
||||
return this._pixelsPerEm;
|
||||
}
|
||||
|
||||
set pixelsPerEm(newPixelsPerEm: number) {
|
||||
this._pixelsPerEm = newPixelsPerEm;
|
||||
this.uploadPathTransforms(1);
|
||||
this.setDirty();
|
||||
}
|
||||
}
|
||||
|
||||
function computeMedian(values: number[]): number | null {
|
||||
|
|
|
@ -216,6 +216,8 @@ class MeshDebuggerView extends PathfinderView {
|
|||
|
||||
this.camera.onPan = () => this.setDirty();
|
||||
this.camera.onZoom = () => this.setDirty();
|
||||
|
||||
this.resizeToFit(true);
|
||||
}
|
||||
|
||||
attachMeshes() {
|
||||
|
@ -277,11 +279,6 @@ class MeshDebuggerView extends PathfinderView {
|
|||
const lowerRightPosition = unwrapNull(getPosition(positions, lowerRightIndex));
|
||||
const lowerControlPointPosition = getPosition(positions, lowerControlPointIndex);
|
||||
|
||||
/*const upperCurve = upperControlPointPosition != null;
|
||||
const lowerCurve = lowerControlPointPosition != null;
|
||||
const upperNormals = getNormals(normals, normalIndices, upperCurve, 'upper');
|
||||
const lowerNormals = getNormals(normals, normalIndices, lowerCurve, 'lower');*/
|
||||
|
||||
drawVertexIfNecessary(context,
|
||||
drawnVertices,
|
||||
upperLeftIndex,
|
||||
|
@ -303,28 +300,6 @@ class MeshDebuggerView extends PathfinderView {
|
|||
lowerRightPosition,
|
||||
invScaleFactor);
|
||||
|
||||
/*
|
||||
drawNormalIfNecessary(context,
|
||||
drawnNormals,
|
||||
upperLeftPosition,
|
||||
upperNormals.left,
|
||||
invScaleFactor);
|
||||
drawNormalIfNecessary(context,
|
||||
drawnNormals,
|
||||
upperRightPosition,
|
||||
upperNormals.right,
|
||||
invScaleFactor);
|
||||
drawNormalIfNecessary(context,
|
||||
drawnNormals,
|
||||
lowerLeftPosition,
|
||||
lowerNormals.left,
|
||||
invScaleFactor);
|
||||
drawNormalIfNecessary(context,
|
||||
drawnNormals,
|
||||
lowerRightPosition,
|
||||
lowerNormals.right,
|
||||
invScaleFactor);*/
|
||||
|
||||
context.beginPath();
|
||||
context.moveTo(upperLeftPosition[0], upperLeftPosition[1]);
|
||||
if (upperControlPointPosition != null) {
|
||||
|
|
|
@ -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 {createFramebuffer, createFramebufferDepthTexture, setTextureParameters} from './gl-utils';
|
||||
import {Renderer} from './renderer';
|
||||
import {unwrapNull} from './utils';
|
||||
import {DemoView, Renderer} from './view';
|
||||
import {DemoView} from './view';
|
||||
|
||||
export default class SSAAStrategy extends AntialiasingStrategy {
|
||||
private level: number;
|
||||
|
@ -50,11 +51,11 @@ export default class SSAAStrategy extends AntialiasingStrategy {
|
|||
renderContext.gl.bindTexture(renderContext.gl.TEXTURE_2D, this.supersampledColorTexture);
|
||||
renderContext.gl.texImage2D(renderContext.gl.TEXTURE_2D,
|
||||
0,
|
||||
renderer.colorAlphaFormat,
|
||||
renderContext.colorAlphaFormat,
|
||||
this.supersampledFramebufferSize[0],
|
||||
this.supersampledFramebufferSize[1],
|
||||
0,
|
||||
renderer.colorAlphaFormat,
|
||||
renderContext.colorAlphaFormat,
|
||||
renderContext.gl.UNSIGNED_BYTE,
|
||||
null);
|
||||
setTextureParameters(renderContext.gl, renderContext.gl.LINEAR);
|
||||
|
|
|
@ -18,6 +18,7 @@ import PathfinderBufferTexture from "./buffer-texture";
|
|||
import {OrthographicCamera} from "./camera";
|
||||
import {UniformMap} from './gl-utils';
|
||||
import {PathfinderMeshData} from "./meshes";
|
||||
import {Renderer} from './renderer';
|
||||
import {ShaderMap, ShaderProgramSource} from './shader-loader';
|
||||
import SSAAStrategy from "./ssaa-strategy";
|
||||
import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
|
||||
|
@ -78,24 +79,19 @@ class SVGDemoController extends DemoAppController<SVGDemoView> {
|
|||
|
||||
private meshesReceived(): void {
|
||||
this.view.then(view => {
|
||||
view.uploadPathColors(1);
|
||||
view.uploadPathTransforms(1);
|
||||
view.attachMeshes([this.meshes]);
|
||||
|
||||
view.camera.bounds = this.loader.bounds;
|
||||
view.camera.zoomToFit();
|
||||
view.initCameraBounds(this.loader.bounds);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class SVGDemoView extends DemoView {
|
||||
camera: OrthographicCamera;
|
||||
renderer: SVGDemoRenderer;
|
||||
appController: SVGDemoController;
|
||||
|
||||
protected depthFunction: number = this.gl.GREATER;
|
||||
|
||||
protected usedSizeFactor: glmatrix.vec2 = glmatrix.vec2.fromValues(1.0, 1.0);
|
||||
|
||||
private appController: SVGDemoController;
|
||||
get camera(): OrthographicCamera {
|
||||
return this.renderer.camera;
|
||||
}
|
||||
|
||||
constructor(appController: SVGDemoController,
|
||||
commonShaderSource: string,
|
||||
|
@ -103,14 +99,26 @@ class SVGDemoView extends DemoView {
|
|||
super(commonShaderSource, shaderSources);
|
||||
|
||||
this.appController = appController;
|
||||
this.renderer = new SVGDemoRenderer(this);
|
||||
|
||||
this.camera = new OrthographicCamera(this.canvas, { scaleBounds: true });
|
||||
this.camera.onPan = () => this.setDirty();
|
||||
this.camera.onZoom = () => this.setDirty();
|
||||
this.resizeToFit(true);
|
||||
}
|
||||
|
||||
initCameraBounds(bounds: glmatrix.vec4): void {
|
||||
this.renderer.initCameraBounds(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
class SVGDemoRenderer extends Renderer {
|
||||
renderContext: SVGDemoView;
|
||||
|
||||
camera: OrthographicCamera;
|
||||
|
||||
get destAllocatedSize(): glmatrix.vec2 {
|
||||
return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height);
|
||||
return glmatrix.vec2.clone([
|
||||
this.renderContext.canvas.width,
|
||||
this.renderContext.canvas.height,
|
||||
]);
|
||||
}
|
||||
|
||||
get destFramebuffer(): WebGLFramebuffer | null {
|
||||
|
@ -121,21 +129,70 @@ class SVGDemoView extends DemoView {
|
|||
return this.destAllocatedSize;
|
||||
}
|
||||
|
||||
constructor(renderContext: SVGDemoView) {
|
||||
super(renderContext);
|
||||
|
||||
this.camera = new OrthographicCamera(renderContext.canvas, { scaleBounds: true });
|
||||
this.camera.onPan = () => this.renderContext.setDirty();
|
||||
this.camera.onZoom = () => this.renderContext.setDirty();
|
||||
}
|
||||
|
||||
setHintsUniform(uniforms: UniformMap): void {
|
||||
this.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
|
||||
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
pathBoundingRects(objectIndex: number): Float32Array {
|
||||
panic("SVGDemoView.pathBoundingRects(): TODO");
|
||||
panic("SVGDemoRenderer.pathBoundingRects(): TODO");
|
||||
return glmatrix.vec4.create();
|
||||
}
|
||||
|
||||
pathCountForObject(objectIndex: number): number {
|
||||
return this.appController.loader.pathInstances.length;
|
||||
attachMeshes(meshes: PathfinderMeshData[]): void {
|
||||
super.attachMeshes(meshes);
|
||||
this.uploadPathColors(1);
|
||||
this.uploadPathTransforms(1);
|
||||
}
|
||||
|
||||
initCameraBounds(bounds: glmatrix.vec4): void {
|
||||
this.camera.bounds = bounds;
|
||||
this.camera.zoomToFit();
|
||||
}
|
||||
|
||||
protected get depthFunction(): number {
|
||||
return this.renderContext.gl.GREATER;
|
||||
}
|
||||
|
||||
protected get usedSizeFactor(): glmatrix.vec2 {
|
||||
return glmatrix.vec2.clone([1.0, 1.0]);
|
||||
}
|
||||
|
||||
protected get worldTransform(): glmatrix.mat4 {
|
||||
const transform = glmatrix.mat4.create();
|
||||
const translation = this.camera.translation;
|
||||
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
|
||||
glmatrix.mat4.scale(transform, transform, [
|
||||
2.0 / this.renderContext.canvas.width,
|
||||
2.0 / this.renderContext.canvas.height,
|
||||
1.0,
|
||||
]);
|
||||
glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
|
||||
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
|
||||
return transform;
|
||||
}
|
||||
|
||||
protected get directCurveProgramName(): keyof ShaderMap<void> {
|
||||
return 'directCurve';
|
||||
}
|
||||
|
||||
protected get directInteriorProgramName(): keyof ShaderMap<void> {
|
||||
return 'directInterior';
|
||||
}
|
||||
|
||||
protected newTimingsReceived(): void {
|
||||
this.renderContext.appController.newTimingsReceived(_.pick(this.lastTimings, ['rendering']));
|
||||
}
|
||||
|
||||
protected pathColorsForObject(objectIndex: number): Uint8Array {
|
||||
const instances = this.appController.loader.pathInstances;
|
||||
const instances = this.renderContext.appController.loader.pathInstances;
|
||||
const pathColors = new Uint8Array(4 * (instances.length + 1));
|
||||
|
||||
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
|
||||
|
@ -154,7 +211,7 @@ class SVGDemoView extends DemoView {
|
|||
}
|
||||
|
||||
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
||||
const instances = this.appController.loader.pathInstances;
|
||||
const instances = this.renderContext.appController.loader.pathInstances;
|
||||
const pathTransforms = new Float32Array(4 * (instances.length + 1));
|
||||
|
||||
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
|
||||
|
@ -174,30 +231,6 @@ class SVGDemoView extends DemoView {
|
|||
}
|
||||
|
||||
protected compositeIfNecessary(): void {}
|
||||
|
||||
protected newTimingsReceived() {
|
||||
this.appController.newTimingsReceived(_.pick(this.lastTimings, ['rendering']));
|
||||
}
|
||||
|
||||
protected get worldTransform() {
|
||||
const transform = glmatrix.mat4.create();
|
||||
const translation = this.camera.translation;
|
||||
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
|
||||
glmatrix.mat4.scale(transform,
|
||||
transform,
|
||||
[2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]);
|
||||
glmatrix.mat4.translate(transform, transform, [translation[0], translation[1], 0]);
|
||||
glmatrix.mat4.scale(transform, transform, [this.camera.scale, this.camera.scale, 1.0]);
|
||||
return transform;
|
||||
}
|
||||
|
||||
protected get directCurveProgramName(): keyof ShaderMap<void> {
|
||||
return 'directCurve';
|
||||
}
|
||||
|
||||
protected get directInteriorProgramName(): keyof ShaderMap<void> {
|
||||
return 'directInterior';
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
|
|
|
@ -23,6 +23,7 @@ import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
|
|||
import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from './gl-utils';
|
||||
import {UniformMap} from './gl-utils';
|
||||
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
||||
import {Renderer} from './renderer';
|
||||
import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader';
|
||||
import SSAAStrategy from './ssaa-strategy';
|
||||
import {calculatePixelDescent, calculatePixelRectForGlyph, PathfinderFont} from "./text";
|
||||
|
@ -186,7 +187,7 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
|||
}
|
||||
|
||||
private hintingChanged(): void {
|
||||
this.view.then(view => view.updateHinting());
|
||||
this.view.then(view => view.renderer.updateHinting());
|
||||
}
|
||||
|
||||
private updateText(): void {
|
||||
|
@ -213,7 +214,7 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
|||
this.meshes = meshes;
|
||||
|
||||
view.attachText();
|
||||
view.uploadPathColors(1);
|
||||
view.renderer.uploadPathColors(1);
|
||||
view.attachMeshes([this.meshes]);
|
||||
});
|
||||
});
|
||||
|
@ -240,7 +241,7 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
|||
/// The font size in pixels per em.
|
||||
set fontSize(newFontSize: number) {
|
||||
this._fontSize = newFontSize;
|
||||
this.view.then(view => view.relayoutText());
|
||||
this.view.then(view => view.renderer.relayoutText());
|
||||
}
|
||||
|
||||
get layoutPixelsPerUnit(): number {
|
||||
|
@ -265,6 +266,62 @@ class TextDemoController extends DemoAppController<TextDemoView> {
|
|||
}
|
||||
|
||||
class TextDemoView extends DemoView {
|
||||
renderer: TextDemoRenderer;
|
||||
|
||||
appController: TextDemoController;
|
||||
|
||||
protected get camera(): OrthographicCamera {
|
||||
return this.renderer.camera;
|
||||
}
|
||||
|
||||
constructor(appController: TextDemoController,
|
||||
commonShaderSource: string,
|
||||
shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||
super(commonShaderSource, shaderSources);
|
||||
|
||||
this.appController = appController;
|
||||
this.renderer = new TextDemoRenderer(this);
|
||||
|
||||
this.canvas.addEventListener('dblclick', () => this.appController.showTextEditor(), false);
|
||||
|
||||
this.resizeToFit(true);
|
||||
}
|
||||
|
||||
attachText() {
|
||||
this.panZoomEventsEnabled = false;
|
||||
this.renderer.prepareToAttachText();
|
||||
this.renderer.camera.zoomToFit();
|
||||
this.appController.fontSize = this.renderer.camera.scale *
|
||||
this.appController.font.opentypeFont.unitsPerEm;
|
||||
this.renderer.finishAttachingText();
|
||||
this.panZoomEventsEnabled = true;
|
||||
}
|
||||
|
||||
protected onPan() {
|
||||
this.renderer.viewPanned();
|
||||
}
|
||||
|
||||
protected onZoom() {
|
||||
this.appController.fontSize = this.renderer.camera.scale *
|
||||
this.appController.font.opentypeFont.unitsPerEm;
|
||||
}
|
||||
|
||||
private set panZoomEventsEnabled(flag: boolean) {
|
||||
if (flag) {
|
||||
this.renderer.camera.onPan = () => this.onPan();
|
||||
this.renderer.camera.onZoom = () => this.onZoom();
|
||||
} else {
|
||||
this.renderer.camera.onPan = null;
|
||||
this.renderer.camera.onZoom = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TextDemoRenderer extends Renderer {
|
||||
renderContext: TextDemoView;
|
||||
|
||||
camera: OrthographicCamera;
|
||||
|
||||
atlasFramebuffer: WebGLFramebuffer;
|
||||
atlasDepthTexture: WebGLTexture;
|
||||
|
||||
|
@ -272,22 +329,22 @@ class TextDemoView extends DemoView {
|
|||
glyphTexCoordsBuffer: WebGLBuffer;
|
||||
glyphElementsBuffer: WebGLBuffer;
|
||||
|
||||
appController: TextDemoController;
|
||||
get destFramebuffer(): WebGLFramebuffer {
|
||||
return this.atlasFramebuffer;
|
||||
}
|
||||
|
||||
camera: OrthographicCamera;
|
||||
get destAllocatedSize(): glmatrix.vec2 {
|
||||
return ATLAS_SIZE;
|
||||
}
|
||||
|
||||
get destUsedSize(): glmatrix.vec2 {
|
||||
return this.renderContext.appController.atlas.usedSize;
|
||||
}
|
||||
|
||||
get emboldenAmount(): glmatrix.vec2 {
|
||||
return this.stemDarkeningAmount;
|
||||
}
|
||||
|
||||
private get stemDarkeningAmount(): glmatrix.vec2 {
|
||||
if (this.stemDarkening === 'dark') {
|
||||
return computeStemDarkeningAmount(this.appController.fontSize,
|
||||
this.appController.layoutPixelsPerUnit);
|
||||
}
|
||||
return glmatrix.vec2.create();
|
||||
}
|
||||
|
||||
get bgColor(): glmatrix.vec4 {
|
||||
return glmatrix.vec4.fromValues(1.0, 1.0, 1.0, 0.0);
|
||||
}
|
||||
|
@ -296,55 +353,55 @@ class TextDemoView extends DemoView {
|
|||
return glmatrix.vec4.fromValues(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
protected depthFunction: number = this.gl.GREATER;
|
||||
protected get worldTransform(): glmatrix.mat4 {
|
||||
const transform = glmatrix.mat4.create();
|
||||
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
|
||||
glmatrix.mat4.scale(transform, transform, [2.0 / ATLAS_SIZE[0], 2.0 / ATLAS_SIZE[1], 1.0]);
|
||||
return transform;
|
||||
}
|
||||
|
||||
private subpixelAA: SubpixelAAType;
|
||||
private stemDarkening: StemDarkeningMode;
|
||||
protected get directCurveProgramName(): keyof ShaderMap<void> {
|
||||
return 'directCurve';
|
||||
}
|
||||
|
||||
protected get directInteriorProgramName(): keyof ShaderMap<void> {
|
||||
return 'directInterior';
|
||||
}
|
||||
|
||||
protected get depthFunction(): number {
|
||||
return this.renderContext.gl.GREATER;
|
||||
}
|
||||
|
||||
protected get usedSizeFactor(): glmatrix.vec2 {
|
||||
const usedSize = glmatrix.vec2.create();
|
||||
glmatrix.vec2.div(usedSize, this.renderContext.appController.atlas.usedSize, ATLAS_SIZE);
|
||||
return usedSize;
|
||||
}
|
||||
|
||||
private get stemDarkeningAmount(): glmatrix.vec2 {
|
||||
const appController = this.renderContext.appController;
|
||||
if (this.stemDarkening === 'dark') {
|
||||
return computeStemDarkeningAmount(appController.fontSize,
|
||||
appController.layoutPixelsPerUnit);
|
||||
}
|
||||
return glmatrix.vec2.create();
|
||||
}
|
||||
|
||||
private glyphBounds: Float32Array;
|
||||
private stemDarkening: StemDarkeningMode;
|
||||
private subpixelAA: SubpixelAAType;
|
||||
|
||||
constructor(appController: TextDemoController,
|
||||
commonShaderSource: string,
|
||||
shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||
super(commonShaderSource, shaderSources);
|
||||
private get displayPixelsPerUnit(): number {
|
||||
return this.renderContext.appController.layoutPixelsPerUnit;
|
||||
}
|
||||
|
||||
this.appController = appController;
|
||||
constructor(renderContext: TextDemoView) {
|
||||
super(renderContext);
|
||||
|
||||
this.camera = new OrthographicCamera(this.canvas, {
|
||||
this.camera = new OrthographicCamera(this.renderContext.canvas, {
|
||||
maxScale: MAX_SCALE,
|
||||
minScale: MIN_SCALE,
|
||||
});
|
||||
|
||||
this.canvas.addEventListener('dblclick', () => this.appController.showTextEditor(), false);
|
||||
}
|
||||
|
||||
attachText() {
|
||||
this.panZoomEventsEnabled = false;
|
||||
|
||||
if (this.atlasFramebuffer == null)
|
||||
this.createAtlasFramebuffer();
|
||||
|
||||
this.layoutText();
|
||||
this.camera.zoomToFit();
|
||||
this.appController.fontSize = this.camera.scale *
|
||||
this.appController.font.opentypeFont.unitsPerEm;
|
||||
this.buildAtlasGlyphs();
|
||||
this.setDirty();
|
||||
|
||||
this.panZoomEventsEnabled = true;
|
||||
}
|
||||
|
||||
relayoutText() {
|
||||
this.layoutText();
|
||||
this.buildAtlasGlyphs();
|
||||
this.setDirty();
|
||||
}
|
||||
|
||||
updateHinting(): void {
|
||||
// Need to relayout the text because the pixel bounds of the glyphs can change from this...
|
||||
this.layoutText();
|
||||
this.buildAtlasGlyphs();
|
||||
this.setDirty();
|
||||
}
|
||||
|
||||
setAntialiasingOptions(aaType: AntialiasingStrategyName,
|
||||
|
@ -356,23 +413,53 @@ class TextDemoView extends DemoView {
|
|||
// Need to relayout because changing AA options can cause font dilation to change...
|
||||
this.layoutText();
|
||||
this.buildAtlasGlyphs();
|
||||
this.setDirty();
|
||||
this.renderContext.setDirty();
|
||||
}
|
||||
|
||||
setHintsUniform(uniforms: UniformMap): void {
|
||||
const hint = this.createHint();
|
||||
this.gl.uniform4f(uniforms.uHints,
|
||||
hint.xHeight,
|
||||
hint.hintedXHeight,
|
||||
hint.stemHeight,
|
||||
hint.hintedStemHeight);
|
||||
this.renderContext.gl.uniform4f(uniforms.uHints,
|
||||
hint.xHeight,
|
||||
hint.hintedXHeight,
|
||||
hint.stemHeight,
|
||||
hint.hintedStemHeight);
|
||||
}
|
||||
|
||||
prepareToAttachText(): void {
|
||||
if (this.atlasFramebuffer == null)
|
||||
this.createAtlasFramebuffer();
|
||||
|
||||
this.layoutText();
|
||||
}
|
||||
|
||||
finishAttachingText(): void {
|
||||
this.buildAtlasGlyphs();
|
||||
this.renderContext.setDirty();
|
||||
}
|
||||
|
||||
relayoutText(): void {
|
||||
this.layoutText();
|
||||
this.buildAtlasGlyphs();
|
||||
this.renderContext.setDirty();
|
||||
}
|
||||
|
||||
updateHinting(): void {
|
||||
// Need to relayout the text because the pixel bounds of the glyphs can change from this...
|
||||
this.layoutText();
|
||||
this.buildAtlasGlyphs();
|
||||
this.renderContext.setDirty();
|
||||
}
|
||||
|
||||
viewPanned(): void {
|
||||
this.buildAtlasGlyphs();
|
||||
this.renderContext.setDirty();
|
||||
}
|
||||
|
||||
pathBoundingRects(objectIndex: number): Float32Array {
|
||||
const pathCount = this.appController.pathCount;
|
||||
const atlasGlyphs = this.appController.atlasGlyphs;
|
||||
const pathCount = this.renderContext.appController.pathCount;
|
||||
const atlasGlyphs = this.renderContext.appController.atlasGlyphs;
|
||||
const pixelsPerUnit = this.displayPixelsPerUnit;
|
||||
const font = this.appController.font;
|
||||
const font = this.renderContext.appController.font;
|
||||
const hint = this.createHint();
|
||||
|
||||
const boundingRects = new Float32Array((pathCount + 1) * 4);
|
||||
|
@ -393,16 +480,97 @@ class TextDemoView extends DemoView {
|
|||
return boundingRects;
|
||||
}
|
||||
|
||||
pathCountForObject(objectIndex: number): number {
|
||||
return this.appController.pathCount;
|
||||
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
||||
aaLevel: number,
|
||||
subpixelAA: SubpixelAAType,
|
||||
stemDarkening: StemDarkeningMode):
|
||||
AntialiasingStrategy {
|
||||
this.subpixelAA = subpixelAA;
|
||||
this.stemDarkening = stemDarkening;
|
||||
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
|
||||
}
|
||||
|
||||
protected initContext() {
|
||||
super.initContext();
|
||||
protected clearForDirectRendering(): void {
|
||||
this.renderContext.gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
||||
this.renderContext.gl.clearDepth(0.0);
|
||||
this.renderContext.gl.depthMask(true);
|
||||
this.renderContext.gl.clear(this.renderContext.gl.COLOR_BUFFER_BIT |
|
||||
this.renderContext.gl.DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
protected compositeIfNecessary() {
|
||||
// Set up composite state.
|
||||
this.renderContext.gl.bindFramebuffer(this.renderContext.gl.FRAMEBUFFER, null);
|
||||
this.renderContext.gl.viewport(0, 0, this.renderContext.canvas.width, this.renderContext.canvas.height);
|
||||
this.renderContext.gl.disable(this.renderContext.gl.DEPTH_TEST);
|
||||
this.renderContext.gl.disable(this.renderContext.gl.SCISSOR_TEST);
|
||||
this.renderContext.gl.blendEquation(this.renderContext.gl.FUNC_ADD);
|
||||
this.renderContext.gl.blendFuncSeparate(this.renderContext.gl.SRC_ALPHA,
|
||||
this.renderContext.gl.ONE_MINUS_SRC_ALPHA,
|
||||
this.renderContext.gl.ONE,
|
||||
this.renderContext.gl.ONE);
|
||||
this.renderContext.gl.enable(this.renderContext.gl.BLEND);
|
||||
|
||||
// Clear.
|
||||
this.renderContext.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
||||
this.renderContext.gl.clear(this.renderContext.gl.COLOR_BUFFER_BIT);
|
||||
|
||||
// Set up the composite VAO.
|
||||
const blitProgram = this.renderContext.shaderPrograms.blit;
|
||||
const attributes = blitProgram.attributes;
|
||||
this.renderContext.gl.useProgram(blitProgram.program);
|
||||
this.renderContext.gl.bindBuffer(this.renderContext.gl.ARRAY_BUFFER,
|
||||
this.glyphPositionsBuffer);
|
||||
this.renderContext.gl.vertexAttribPointer(attributes.aPosition,
|
||||
2,
|
||||
this.renderContext.gl.FLOAT,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
this.renderContext.gl.bindBuffer(this.renderContext.gl.ARRAY_BUFFER,
|
||||
this.glyphTexCoordsBuffer);
|
||||
this.renderContext.gl.vertexAttribPointer(attributes.aTexCoord,
|
||||
2,
|
||||
this.renderContext.gl.FLOAT,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
this.renderContext.gl.enableVertexAttribArray(attributes.aPosition);
|
||||
this.renderContext.gl.enableVertexAttribArray(attributes.aTexCoord);
|
||||
this.renderContext.gl.bindBuffer(this.renderContext.gl.ELEMENT_ARRAY_BUFFER,
|
||||
this.glyphElementsBuffer);
|
||||
|
||||
// Create the transform.
|
||||
const transform = glmatrix.mat4.create();
|
||||
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
|
||||
glmatrix.mat4.scale(transform, transform, [
|
||||
2.0 / this.renderContext.canvas.width,
|
||||
2.0 / this.renderContext.canvas.height,
|
||||
1.0,
|
||||
]);
|
||||
glmatrix.mat4.translate(transform,
|
||||
transform,
|
||||
[this.camera.translation[0], this.camera.translation[1], 0.0]);
|
||||
|
||||
// Blit.
|
||||
this.renderContext.gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform);
|
||||
this.renderContext.gl.activeTexture(this.renderContext.gl.TEXTURE0);
|
||||
const destTexture = this.renderContext
|
||||
.appController
|
||||
.atlas
|
||||
.ensureTexture(this.renderContext);
|
||||
this.renderContext.gl.bindTexture(this.renderContext.gl.TEXTURE_2D, destTexture);
|
||||
this.renderContext.gl.uniform1i(blitProgram.uniforms.uSource, 0);
|
||||
this.setIdentityTexScaleUniform(blitProgram.uniforms);
|
||||
const totalGlyphCount = this.renderContext.appController.layout.textFrame.totalGlyphCount;
|
||||
this.renderContext.gl.drawElements(this.renderContext.gl.TRIANGLES,
|
||||
totalGlyphCount * 6,
|
||||
this.renderContext.gl.UNSIGNED_INT,
|
||||
0);
|
||||
}
|
||||
|
||||
protected pathColorsForObject(objectIndex: number): Uint8Array {
|
||||
const pathCount = this.appController.pathCount;
|
||||
const pathCount = this.renderContext.appController.pathCount;
|
||||
|
||||
const pathColors = new Uint8Array(4 * (pathCount + 1));
|
||||
|
||||
|
@ -416,8 +584,8 @@ class TextDemoView extends DemoView {
|
|||
}
|
||||
|
||||
protected pathTransformsForObject(objectIndex: number): Float32Array {
|
||||
const pathCount = this.appController.pathCount;
|
||||
const atlasGlyphs = this.appController.atlasGlyphs;
|
||||
const pathCount = this.renderContext.appController.pathCount;
|
||||
const atlasGlyphs = this.renderContext.appController.atlasGlyphs;
|
||||
const pixelsPerUnit = this.displayPixelsPerUnit;
|
||||
|
||||
const transforms = new Float32Array((pathCount + 1) * 4);
|
||||
|
@ -435,97 +603,33 @@ class TextDemoView extends DemoView {
|
|||
return transforms;
|
||||
}
|
||||
|
||||
protected onPan() {
|
||||
this.buildAtlasGlyphs();
|
||||
this.setDirty();
|
||||
}
|
||||
|
||||
protected onZoom() {
|
||||
this.appController.fontSize = this.camera.scale *
|
||||
this.appController.font.opentypeFont.unitsPerEm;
|
||||
}
|
||||
|
||||
protected compositeIfNecessary() {
|
||||
// Set up composite state.
|
||||
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
|
||||
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.gl.disable(this.gl.DEPTH_TEST);
|
||||
this.gl.disable(this.gl.SCISSOR_TEST);
|
||||
this.gl.blendEquation(this.gl.FUNC_ADD);
|
||||
this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA,
|
||||
this.gl.ONE, this.gl.ONE);
|
||||
this.gl.enable(this.gl.BLEND);
|
||||
|
||||
// Clear.
|
||||
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
||||
|
||||
// Set up the composite VAO.
|
||||
const blitProgram = this.shaderPrograms.blit;
|
||||
const attributes = blitProgram.attributes;
|
||||
this.gl.useProgram(blitProgram.program);
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
|
||||
this.gl.vertexAttribPointer(attributes.aPosition, 2, this.gl.FLOAT, false, 0, 0);
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
|
||||
this.gl.vertexAttribPointer(attributes.aTexCoord, 2, this.gl.FLOAT, false, 0, 0);
|
||||
this.gl.enableVertexAttribArray(attributes.aPosition);
|
||||
this.gl.enableVertexAttribArray(attributes.aTexCoord);
|
||||
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.glyphElementsBuffer);
|
||||
|
||||
// Create the transform.
|
||||
const transform = glmatrix.mat4.create();
|
||||
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
|
||||
glmatrix.mat4.scale(transform,
|
||||
transform,
|
||||
[2.0 / this.canvas.width, 2.0 / this.canvas.height, 1.0]);
|
||||
glmatrix.mat4.translate(transform,
|
||||
transform,
|
||||
[this.camera.translation[0],
|
||||
this.camera.translation[1],
|
||||
0.0]);
|
||||
|
||||
// Blit.
|
||||
this.gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform);
|
||||
this.gl.activeTexture(this.gl.TEXTURE0);
|
||||
this.gl.bindTexture(this.gl.TEXTURE_2D, this.appController.atlas.ensureTexture(this));
|
||||
this.gl.uniform1i(blitProgram.uniforms.uSource, 0);
|
||||
this.setIdentityTexScaleUniform(blitProgram.uniforms);
|
||||
this.gl.drawElements(this.gl.TRIANGLES,
|
||||
this.appController.layout.textFrame.totalGlyphCount * 6,
|
||||
this.gl.UNSIGNED_INT,
|
||||
0);
|
||||
}
|
||||
|
||||
protected clearForDirectRendering(): void {
|
||||
this.gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
||||
this.gl.clearDepth(0.0);
|
||||
this.gl.depthMask(true);
|
||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
protected createAAStrategy(aaType: AntialiasingStrategyName,
|
||||
aaLevel: number,
|
||||
subpixelAA: SubpixelAAType,
|
||||
stemDarkening: StemDarkeningMode):
|
||||
AntialiasingStrategy {
|
||||
this.subpixelAA = subpixelAA;
|
||||
this.stemDarkening = stemDarkening;
|
||||
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
|
||||
}
|
||||
|
||||
protected newTimingsReceived() {
|
||||
this.appController.newTimingsReceived(this.lastTimings);
|
||||
this.renderContext.appController.newTimingsReceived(this.lastTimings);
|
||||
}
|
||||
|
||||
private createAtlasFramebuffer() {
|
||||
const appController = this.renderContext.appController;
|
||||
|
||||
const atlasColorTexture = appController.atlas.ensureTexture(this.renderContext);
|
||||
this.atlasDepthTexture = createFramebufferDepthTexture(this.renderContext.gl, ATLAS_SIZE);
|
||||
this.atlasFramebuffer = createFramebuffer(this.renderContext.gl,
|
||||
this.renderContext.drawBuffersExt,
|
||||
[atlasColorTexture],
|
||||
this.atlasDepthTexture);
|
||||
|
||||
// Allow the antialiasing strategy to set up framebuffers as necessary.
|
||||
if (this.antialiasingStrategy != null)
|
||||
this.antialiasingStrategy.setFramebufferSize(this);
|
||||
}
|
||||
|
||||
private createHint(): Hint {
|
||||
return new Hint(this.appController.font,
|
||||
return new Hint(this.renderContext.appController.font,
|
||||
this.displayPixelsPerUnit,
|
||||
this.appController.useHinting);
|
||||
this.renderContext.appController.useHinting);
|
||||
}
|
||||
|
||||
/// Lays out glyphs on the canvas.
|
||||
private layoutText() {
|
||||
const layout = this.appController.layout;
|
||||
const layout = this.renderContext.appController.layout;
|
||||
layout.layoutRuns();
|
||||
|
||||
const textBounds = layout.textFrame.bounds;
|
||||
|
@ -537,7 +641,7 @@ class TextDemoView extends DemoView {
|
|||
|
||||
const hint = this.createHint();
|
||||
const displayPixelsPerUnit = this.displayPixelsPerUnit;
|
||||
const layoutPixelsPerUnit = this.appController.layoutPixelsPerUnit;
|
||||
const layoutPixelsPerUnit = this.renderContext.appController.layoutPixelsPerUnit;
|
||||
|
||||
let globalGlyphIndex = 0;
|
||||
for (const run of layout.textFrame.runs) {
|
||||
|
@ -566,29 +670,38 @@ class TextDemoView extends DemoView {
|
|||
}
|
||||
}
|
||||
|
||||
this.glyphPositionsBuffer = unwrapNull(this.gl.createBuffer());
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphPositionsBuffer);
|
||||
this.gl.bufferData(this.gl.ARRAY_BUFFER, glyphPositions, this.gl.STATIC_DRAW);
|
||||
this.glyphElementsBuffer = unwrapNull(this.gl.createBuffer());
|
||||
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.glyphElementsBuffer);
|
||||
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, glyphIndices, this.gl.STATIC_DRAW);
|
||||
this.glyphPositionsBuffer = unwrapNull(this.renderContext.gl.createBuffer());
|
||||
this.renderContext.gl.bindBuffer(this.renderContext.gl.ARRAY_BUFFER,
|
||||
this.glyphPositionsBuffer);
|
||||
this.renderContext.gl.bufferData(this.renderContext.gl.ARRAY_BUFFER,
|
||||
glyphPositions,
|
||||
this.renderContext.gl.STATIC_DRAW);
|
||||
this.glyphElementsBuffer = unwrapNull(this.renderContext.gl.createBuffer());
|
||||
this.renderContext.gl.bindBuffer(this.renderContext.gl.ELEMENT_ARRAY_BUFFER,
|
||||
this.glyphElementsBuffer);
|
||||
this.renderContext.gl.bufferData(this.renderContext.gl.ELEMENT_ARRAY_BUFFER,
|
||||
glyphIndices,
|
||||
this.renderContext.gl.STATIC_DRAW);
|
||||
}
|
||||
|
||||
private buildAtlasGlyphs() {
|
||||
const font = this.appController.font;
|
||||
const glyphStore = this.appController.glyphStore;
|
||||
const layoutPixelsPerUnit = this.appController.layoutPixelsPerUnit;
|
||||
const appController = this.renderContext.appController;
|
||||
const font = appController.font;
|
||||
const glyphStore = appController.glyphStore;
|
||||
const layoutPixelsPerUnit = appController.layoutPixelsPerUnit;
|
||||
const displayPixelsPerUnit = this.displayPixelsPerUnit;
|
||||
|
||||
const textFrame = this.appController.layout.textFrame;
|
||||
const textFrame = appController.layout.textFrame;
|
||||
const hint = this.createHint();
|
||||
|
||||
// Only build glyphs in view.
|
||||
const translation = this.camera.translation;
|
||||
const canvasRect = glmatrix.vec4.fromValues(-translation[0],
|
||||
-translation[1],
|
||||
-translation[0] + this.canvas.width,
|
||||
-translation[1] + this.canvas.height);
|
||||
const canvasRect = glmatrix.vec4.clone([
|
||||
-translation[0],
|
||||
-translation[1],
|
||||
-translation[0] + this.renderContext.canvas.width,
|
||||
-translation[1] + this.renderContext.canvas.height,
|
||||
]);
|
||||
|
||||
let atlasGlyphs = [];
|
||||
for (const run of textFrame.runs) {
|
||||
|
@ -621,12 +734,12 @@ class TextDemoView extends DemoView {
|
|||
if (atlasGlyphs.length === 0)
|
||||
return;
|
||||
|
||||
this.appController.atlasGlyphs = atlasGlyphs;
|
||||
this.appController.atlas.layoutGlyphs(atlasGlyphs,
|
||||
font,
|
||||
displayPixelsPerUnit,
|
||||
hint,
|
||||
this.stemDarkeningAmount);
|
||||
appController.atlasGlyphs = atlasGlyphs;
|
||||
appController.atlas.layoutGlyphs(atlasGlyphs,
|
||||
font,
|
||||
displayPixelsPerUnit,
|
||||
hint,
|
||||
this.stemDarkeningAmount);
|
||||
|
||||
this.uploadPathTransforms(1);
|
||||
|
||||
|
@ -635,26 +748,14 @@ class TextDemoView extends DemoView {
|
|||
this.setGlyphTexCoords();
|
||||
}
|
||||
|
||||
private createAtlasFramebuffer() {
|
||||
const atlasColorTexture = this.appController.atlas.ensureTexture(this);
|
||||
this.atlasDepthTexture = createFramebufferDepthTexture(this.gl, ATLAS_SIZE);
|
||||
this.atlasFramebuffer = createFramebuffer(this.gl,
|
||||
this.drawBuffersExt,
|
||||
[atlasColorTexture],
|
||||
this.atlasDepthTexture);
|
||||
|
||||
// Allow the antialiasing strategy to set up framebuffers as necessary.
|
||||
if (this.antialiasingStrategy != null)
|
||||
this.antialiasingStrategy.setFramebufferSize(this);
|
||||
}
|
||||
|
||||
private setGlyphTexCoords() {
|
||||
const textFrame = this.appController.layout.textFrame;
|
||||
const font = this.appController.font;
|
||||
const atlasGlyphs = this.appController.atlasGlyphs;
|
||||
const appController = this.renderContext.appController;
|
||||
const textFrame = appController.layout.textFrame;
|
||||
const font = appController.font;
|
||||
const atlasGlyphs = appController.atlasGlyphs;
|
||||
|
||||
const hint = this.createHint();
|
||||
const layoutPixelsPerUnit = this.appController.layoutPixelsPerUnit;
|
||||
const layoutPixelsPerUnit = appController.layoutPixelsPerUnit;
|
||||
const displayPixelsPerUnit = this.displayPixelsPerUnit;
|
||||
|
||||
const atlasGlyphKeys = atlasGlyphs.map(atlasGlyph => atlasGlyph.glyphKey.sortKey);
|
||||
|
@ -707,61 +808,16 @@ class TextDemoView extends DemoView {
|
|||
}
|
||||
}
|
||||
|
||||
this.glyphTexCoordsBuffer = unwrapNull(this.gl.createBuffer());
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer);
|
||||
this.gl.bufferData(this.gl.ARRAY_BUFFER, this.glyphBounds, this.gl.STATIC_DRAW);
|
||||
this.glyphTexCoordsBuffer = unwrapNull(this.renderContext.gl.createBuffer());
|
||||
this.renderContext.gl.bindBuffer(this.renderContext.gl.ARRAY_BUFFER,
|
||||
this.glyphTexCoordsBuffer);
|
||||
this.renderContext.gl.bufferData(this.renderContext.gl.ARRAY_BUFFER,
|
||||
this.glyphBounds,
|
||||
this.renderContext.gl.STATIC_DRAW);
|
||||
}
|
||||
|
||||
private setIdentityTexScaleUniform(uniforms: UniformMap) {
|
||||
this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
|
||||
}
|
||||
|
||||
protected get usedSizeFactor(): glmatrix.vec2 {
|
||||
const usedSize = glmatrix.vec2.create();
|
||||
glmatrix.vec2.div(usedSize, this.appController.atlas.usedSize, ATLAS_SIZE);
|
||||
return usedSize;
|
||||
}
|
||||
|
||||
private set panZoomEventsEnabled(flag: boolean) {
|
||||
if (flag) {
|
||||
this.camera.onPan = () => this.onPan();
|
||||
this.camera.onZoom = () => this.onZoom();
|
||||
} else {
|
||||
this.camera.onPan = null;
|
||||
this.camera.onZoom = null;
|
||||
}
|
||||
}
|
||||
|
||||
get destFramebuffer(): WebGLFramebuffer {
|
||||
return this.atlasFramebuffer;
|
||||
}
|
||||
|
||||
get destAllocatedSize(): glmatrix.vec2 {
|
||||
return ATLAS_SIZE;
|
||||
}
|
||||
|
||||
get destUsedSize(): glmatrix.vec2 {
|
||||
return this.appController.atlas.usedSize;
|
||||
}
|
||||
|
||||
protected get worldTransform(): glmatrix.mat4 {
|
||||
const transform = glmatrix.mat4.create();
|
||||
glmatrix.mat4.translate(transform, transform, [-1.0, -1.0, 0.0]);
|
||||
glmatrix.mat4.scale(transform, transform, [2.0 / ATLAS_SIZE[0], 2.0 / ATLAS_SIZE[1], 1.0]);
|
||||
return transform;
|
||||
}
|
||||
|
||||
protected get directCurveProgramName(): keyof ShaderMap<void> {
|
||||
return 'directCurve';
|
||||
}
|
||||
|
||||
protected get directInteriorProgramName(): keyof ShaderMap<void> {
|
||||
return 'directInterior';
|
||||
}
|
||||
|
||||
/// Pixels per unit, including dilation.
|
||||
private get displayPixelsPerUnit(): number {
|
||||
return this.appController.layoutPixelsPerUnit;
|
||||
this.renderContext.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,9 +8,6 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
// FIXME(pcwalton): This is turning into a fragile inheritance hierarchy. See if we can refactor to
|
||||
// use composition more.
|
||||
|
||||
import * as glmatrix from 'gl-matrix';
|
||||
|
||||
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
|
||||
|
@ -19,18 +16,11 @@ import PathfinderBufferTexture from './buffer-texture';
|
|||
import {Camera} from "./camera";
|
||||
import {EXTDisjointTimerQuery, QUAD_ELEMENTS, UniformMap} from './gl-utils';
|
||||
import {PathfinderMeshBuffers, PathfinderMeshData} from './meshes';
|
||||
import {Renderer} from './renderer';
|
||||
import {PathfinderShaderProgram, SHADER_NAMES, ShaderMap} from './shader-loader';
|
||||
import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader';
|
||||
import {expectNotNull, PathfinderError, UINT32_SIZE, unwrapNull} from './utils';
|
||||
|
||||
const MAX_PATHS: number = 65535;
|
||||
|
||||
const TIME_INTERVAL_DELAY: number = 32;
|
||||
|
||||
const B_LOOP_BLINN_DATA_SIZE: number = 4;
|
||||
const B_LOOP_BLINN_DATA_TEX_COORD_OFFSET: number = 0;
|
||||
const B_LOOP_BLINN_DATA_SIGN_OFFSET: number = 2;
|
||||
|
||||
const QUAD_POSITIONS: Float32Array = new Float32Array([
|
||||
0.0, 1.0,
|
||||
1.0, 1.0,
|
||||
|
@ -58,19 +48,23 @@ export interface Timings {
|
|||
declare class WebGLQuery {}
|
||||
|
||||
export abstract class PathfinderView {
|
||||
protected canvas: HTMLCanvasElement;
|
||||
canvas: HTMLCanvasElement;
|
||||
|
||||
protected camera: Camera;
|
||||
protected abstract get camera(): Camera;
|
||||
|
||||
private dirty: boolean;
|
||||
|
||||
constructor() {
|
||||
this.dirty = false;
|
||||
|
||||
this.canvas = unwrapNull(document.getElementById('pf-canvas')) as HTMLCanvasElement;
|
||||
|
||||
window.addEventListener('resize', () => this.resizeToFit(false), false);
|
||||
this.resizeToFit(true);
|
||||
}
|
||||
|
||||
setDirty() {
|
||||
if (this.dirty)
|
||||
return;
|
||||
this.dirty = true;
|
||||
window.requestAnimationFrame(() => this.redraw());
|
||||
}
|
||||
|
||||
zoomIn(): void {
|
||||
|
@ -85,18 +79,11 @@ export abstract class PathfinderView {
|
|||
this.setDirty();
|
||||
}
|
||||
|
||||
protected setDirty() {
|
||||
if (this.dirty)
|
||||
return;
|
||||
this.dirty = true;
|
||||
window.requestAnimationFrame(() => this.redraw());
|
||||
}
|
||||
|
||||
protected redraw() {
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
private resizeToFit(initialSize: boolean) {
|
||||
protected resizeToFit(initialSize: boolean) {
|
||||
const width = window.innerWidth;
|
||||
|
||||
let height = window.scrollY + window.innerHeight - this.canvas.getBoundingClientRect().top;
|
||||
|
@ -121,7 +108,9 @@ export abstract class PathfinderView {
|
|||
}
|
||||
}
|
||||
|
||||
export abstract class DemoView extends PathfinderView implements Renderer, RenderContext {
|
||||
export abstract class DemoView extends PathfinderView implements RenderContext {
|
||||
readonly renderer: Renderer;
|
||||
|
||||
gl: WebGLRenderingContext;
|
||||
|
||||
shaderPrograms: ShaderMap<PathfinderShaderProgram>;
|
||||
|
@ -129,29 +118,19 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
|
|||
drawBuffersExt: WebGLDrawBuffers;
|
||||
instancedArraysExt: ANGLEInstancedArrays;
|
||||
textureHalfFloatExt: OESTextureHalfFloat;
|
||||
timerQueryExt: EXTDisjointTimerQuery;
|
||||
vertexArrayObjectExt: OESVertexArrayObject;
|
||||
|
||||
quadPositionsBuffer: WebGLBuffer;
|
||||
quadTexCoordsBuffer: WebGLBuffer;
|
||||
quadElementsBuffer: WebGLBuffer;
|
||||
|
||||
atlasRenderingTimerQuery: WebGLQuery;
|
||||
compositingTimerQuery: WebGLQuery;
|
||||
|
||||
meshes: PathfinderMeshBuffers[];
|
||||
meshData: PathfinderMeshData[];
|
||||
|
||||
pathTransformBufferTextures: PathfinderBufferTexture[];
|
||||
|
||||
get bgColor(): glmatrix.vec4 | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
get fgColor(): glmatrix.vec4 | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
get emboldenAmount(): glmatrix.vec2 {
|
||||
return glmatrix.vec2.create();
|
||||
}
|
||||
|
||||
get colorAlphaFormat(): GLenum {
|
||||
return this.sRGBExt == null ? this.gl.RGBA : this.sRGBExt.SRGB_ALPHA_EXT;
|
||||
}
|
||||
|
@ -161,70 +140,24 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
|
|||
}
|
||||
|
||||
protected sRGBExt: EXTsRGB;
|
||||
protected timerQueryExt: EXTDisjointTimerQuery;
|
||||
|
||||
protected antialiasingStrategy: AntialiasingStrategy | null;
|
||||
protected colorBufferHalfFloatExt: any;
|
||||
protected pathColorsBufferTextures: PathfinderBufferTexture[];
|
||||
|
||||
protected lastTimings: Timings;
|
||||
|
||||
protected get pathIDsAreInstanced(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
private instancedPathIDVBO: WebGLBuffer | null;
|
||||
|
||||
private atlasRenderingTimerQuery: WebGLQuery;
|
||||
private compositingTimerQuery: WebGLQuery;
|
||||
private timerQueryPollInterval: number | null;
|
||||
|
||||
private wantsScreenshot: boolean;
|
||||
|
||||
/// NB: All subclasses are responsible for creating a renderer in their constructors.
|
||||
constructor(commonShaderSource: string, shaderSources: ShaderMap<ShaderProgramSource>) {
|
||||
super();
|
||||
|
||||
this.initContext();
|
||||
|
||||
this.lastTimings = { rendering: 0, compositing: 0 };
|
||||
|
||||
const shaderSource = this.compileShaders(commonShaderSource, shaderSources);
|
||||
this.shaderPrograms = this.linkShaders(shaderSource);
|
||||
|
||||
this.pathTransformBufferTextures = [];
|
||||
this.pathColorsBufferTextures = [];
|
||||
|
||||
if (this.pathIDsAreInstanced)
|
||||
this.initInstancedPathIDVBO();
|
||||
|
||||
this.wantsScreenshot = false;
|
||||
|
||||
this.antialiasingStrategy = new NoAAStrategy(0, 'none');
|
||||
this.antialiasingStrategy.init(this);
|
||||
}
|
||||
|
||||
setAntialiasingOptions(aaType: AntialiasingStrategyName,
|
||||
aaLevel: number,
|
||||
subpixelAA: SubpixelAAType,
|
||||
stemDarkening: StemDarkeningMode) {
|
||||
this.antialiasingStrategy = this.createAAStrategy(aaType,
|
||||
aaLevel,
|
||||
subpixelAA,
|
||||
stemDarkening);
|
||||
|
||||
const canvas = this.canvas;
|
||||
this.antialiasingStrategy.init(this);
|
||||
if (this.meshData != null)
|
||||
this.antialiasingStrategy.attachMeshes(this);
|
||||
|
||||
this.setDirty();
|
||||
}
|
||||
|
||||
attachMeshes(meshes: PathfinderMeshData[]) {
|
||||
this.meshData = meshes;
|
||||
this.meshes = meshes.map(meshes => new PathfinderMeshBuffers(this.gl, meshes));
|
||||
unwrapNull(this.antialiasingStrategy).attachMeshes(this);
|
||||
|
||||
attachMeshes(meshes: PathfinderMeshData[]): void {
|
||||
this.renderer.attachMeshes(meshes);
|
||||
this.setDirty();
|
||||
}
|
||||
|
||||
|
@ -238,81 +171,21 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
|
|||
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.quadElementsBuffer);
|
||||
}
|
||||
|
||||
setFramebufferSizeUniform(uniforms: UniformMap) {
|
||||
const currentViewport = this.gl.getParameter(this.gl.VIEWPORT);
|
||||
this.gl.uniform2i(uniforms.uFramebufferSize, currentViewport[2], currentViewport[3]);
|
||||
}
|
||||
|
||||
setTransformSTUniform(uniforms: UniformMap, objectIndex: number) {
|
||||
// FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an ST matrix is ugly and fragile.
|
||||
// Refactor.
|
||||
const transform = glmatrix.mat4.clone(this.worldTransform);
|
||||
glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex));
|
||||
|
||||
const translation = glmatrix.vec4.clone([transform[12], transform[13], 0.0, 1.0]);
|
||||
|
||||
this.gl.uniform4f(uniforms.uTransformST,
|
||||
transform[0],
|
||||
transform[5],
|
||||
transform[12],
|
||||
transform[13]);
|
||||
}
|
||||
|
||||
setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap): void {
|
||||
const usedSize = this.usedSizeFactor;
|
||||
this.gl.uniform4f(uniforms.uTransformST, 2.0 * usedSize[0], 2.0 * usedSize[1], -1.0, -1.0);
|
||||
this.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
|
||||
}
|
||||
|
||||
setTransformAndTexScaleUniformsForDest(uniforms: UniformMap): void {
|
||||
const usedSize = this.usedSizeFactor;
|
||||
|
||||
const transform = glmatrix.mat4.create();
|
||||
glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]);
|
||||
glmatrix.mat4.scale(transform, transform, [2.0 * usedSize[0], 2.0 * usedSize[1], 1.0]);
|
||||
this.gl.uniformMatrix4fv(uniforms.uTransform, false, transform);
|
||||
|
||||
this.gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]);
|
||||
}
|
||||
|
||||
queueScreenshot() {
|
||||
this.wantsScreenshot = true;
|
||||
this.setDirty();
|
||||
}
|
||||
|
||||
uploadPathColors(objectCount: number) {
|
||||
this.pathColorsBufferTextures = [];
|
||||
|
||||
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
|
||||
const pathColorsBufferTexture = new PathfinderBufferTexture(this.gl, 'uPathColors');
|
||||
const pathColors = this.pathColorsForObject(objectIndex);
|
||||
pathColorsBufferTexture.upload(this.gl, pathColors);
|
||||
this.pathColorsBufferTextures.push(pathColorsBufferTexture);
|
||||
}
|
||||
setAntialiasingOptions(aaType: AntialiasingStrategyName,
|
||||
aaLevel: number,
|
||||
subpixelAA: SubpixelAAType,
|
||||
stemDarkening: StemDarkeningMode) {
|
||||
this.renderer.setAntialiasingOptions(aaType, aaLevel, subpixelAA, stemDarkening);
|
||||
}
|
||||
|
||||
uploadPathTransforms(objectCount: number) {
|
||||
this.pathTransformBufferTextures = [];
|
||||
|
||||
for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) {
|
||||
const pathTransformBufferTexture = new PathfinderBufferTexture(this.gl,
|
||||
'uPathTransform');
|
||||
|
||||
const pathTransforms = this.pathTransformsForObject(objectIndex);
|
||||
pathTransformBufferTexture.upload(this.gl, pathTransforms);
|
||||
this.pathTransformBufferTextures.push(pathTransformBufferTexture);
|
||||
}
|
||||
}
|
||||
|
||||
abstract setHintsUniform(uniforms: UniformMap): void;
|
||||
abstract pathBoundingRects(objectIndex: number): Float32Array;
|
||||
abstract pathCountForObject(objectIndex: number): number;
|
||||
|
||||
protected resized(): void {
|
||||
super.resized();
|
||||
|
||||
if (this.antialiasingStrategy != null)
|
||||
this.antialiasingStrategy.init(this);
|
||||
this.renderer.canvasResized();
|
||||
}
|
||||
|
||||
protected initContext() {
|
||||
|
@ -350,46 +223,7 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
|
|||
protected redraw() {
|
||||
super.redraw();
|
||||
|
||||
if (this.meshes == null)
|
||||
return;
|
||||
|
||||
// Start timing rendering.
|
||||
if (this.timerQueryPollInterval == null) {
|
||||
this.timerQueryExt.beginQueryEXT(this.timerQueryExt.TIME_ELAPSED_EXT,
|
||||
this.atlasRenderingTimerQuery);
|
||||
}
|
||||
|
||||
// Prepare for direct rendering.
|
||||
const antialiasingStrategy = unwrapNull(this.antialiasingStrategy);
|
||||
antialiasingStrategy.prepare(this);
|
||||
|
||||
// Clear.
|
||||
this.clearForDirectRendering();
|
||||
|
||||
// Draw "scenery" (used in the 3D view).
|
||||
this.drawSceneryIfNecessary();
|
||||
|
||||
// Perform direct rendering (Loop-Blinn).
|
||||
if (antialiasingStrategy.shouldRenderDirect)
|
||||
this.renderDirect();
|
||||
|
||||
// Antialias.
|
||||
antialiasingStrategy.antialias(this);
|
||||
|
||||
// End the timer, and start a new one.
|
||||
if (this.timerQueryPollInterval == null) {
|
||||
this.timerQueryExt.endQueryEXT(this.timerQueryExt.TIME_ELAPSED_EXT);
|
||||
this.timerQueryExt.beginQueryEXT(this.timerQueryExt.TIME_ELAPSED_EXT,
|
||||
this.compositingTimerQuery);
|
||||
}
|
||||
|
||||
antialiasingStrategy.resolve(this);
|
||||
|
||||
// Draw the glyphs with the resolved atlas to the default framebuffer.
|
||||
this.compositeIfNecessary();
|
||||
|
||||
// Finish timing.
|
||||
this.finishTiming();
|
||||
this.renderer.redraw();
|
||||
|
||||
// Invoke the post-render hook.
|
||||
this.renderingFinished();
|
||||
|
@ -403,42 +237,6 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
|
|||
|
||||
protected renderingFinished(): void {}
|
||||
|
||||
protected getModelviewTransform(pathIndex: number): glmatrix.mat4 {
|
||||
return glmatrix.mat4.create();
|
||||
}
|
||||
|
||||
protected drawSceneryIfNecessary(): void {}
|
||||
|
||||
protected clearForDirectRendering(): void {
|
||||
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
||||
this.gl.clearDepth(0.0);
|
||||
this.gl.depthMask(true);
|
||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
protected shouldRenderObject(objectIndex: number): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected newTimingsReceived() {}
|
||||
|
||||
protected abstract pathColorsForObject(objectIndex: number): Uint8Array;
|
||||
protected abstract pathTransformsForObject(objectIndex: number): Float32Array;
|
||||
|
||||
protected abstract get depthFunction(): number;
|
||||
|
||||
protected abstract createAAStrategy(aaType: AntialiasingStrategyName,
|
||||
aaLevel: number,
|
||||
subpixelAA: SubpixelAAType,
|
||||
stemDarkening: StemDarkeningMode):
|
||||
AntialiasingStrategy;
|
||||
|
||||
protected abstract compositeIfNecessary(): void;
|
||||
|
||||
protected meshInstanceCountForObject(objectIndex: number): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
private compileShaders(commonSource: string, shaderSources: ShaderMap<ShaderProgramSource>):
|
||||
ShaderMap<UnlinkedShaderProgram> {
|
||||
const shaders: Partial<ShaderMap<Partial<UnlinkedShaderProgram>>> = {};
|
||||
|
@ -483,228 +281,6 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
|
|||
return shaderProgramMap as ShaderMap<PathfinderShaderProgram>;
|
||||
}
|
||||
|
||||
private initInstancedPathIDVBO(): void {
|
||||
const pathIDs = new Uint16Array(MAX_PATHS);
|
||||
for (let pathIndex = 0; pathIndex < MAX_PATHS; pathIndex++)
|
||||
pathIDs[pathIndex] = pathIndex + 1;
|
||||
|
||||
this.instancedPathIDVBO = this.gl.createBuffer();
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instancedPathIDVBO);
|
||||
this.gl.bufferData(this.gl.ARRAY_BUFFER, pathIDs, this.gl.STATIC_DRAW);
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
|
||||
}
|
||||
|
||||
private setTransformUniform(uniforms: UniformMap, objectIndex: number) {
|
||||
const transform = glmatrix.mat4.clone(this.worldTransform);
|
||||
glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex));
|
||||
this.gl.uniformMatrix4fv(uniforms.uTransform, false, transform);
|
||||
}
|
||||
|
||||
private renderDirect() {
|
||||
for (let objectIndex = 0; objectIndex < this.meshes.length; objectIndex++) {
|
||||
if (!this.shouldRenderObject(objectIndex))
|
||||
continue;
|
||||
|
||||
const meshes = this.meshes[objectIndex];
|
||||
|
||||
let instanceCount: number | null;
|
||||
if (!this.pathIDsAreInstanced)
|
||||
instanceCount = null;
|
||||
else
|
||||
instanceCount = this.meshInstanceCountForObject(objectIndex);
|
||||
|
||||
// Set up implicit cover state.
|
||||
this.gl.depthFunc(this.depthFunction);
|
||||
this.gl.depthMask(true);
|
||||
this.gl.enable(this.gl.DEPTH_TEST);
|
||||
this.gl.disable(this.gl.BLEND);
|
||||
|
||||
// Set up the implicit cover interior VAO.
|
||||
//
|
||||
// TODO(pcwalton): Cache these.
|
||||
const directInteriorProgram = this.shaderPrograms[this.directInteriorProgramName];
|
||||
const implicitCoverInteriorVAO = this.vertexArrayObjectExt.createVertexArrayOES();
|
||||
this.vertexArrayObjectExt.bindVertexArrayOES(implicitCoverInteriorVAO);
|
||||
this.initImplicitCoverInteriorVAO(objectIndex);
|
||||
|
||||
// Draw direct interior parts.
|
||||
this.setTransformUniform(directInteriorProgram.uniforms, objectIndex);
|
||||
this.setFramebufferSizeUniform(directInteriorProgram.uniforms);
|
||||
this.setHintsUniform(directInteriorProgram.uniforms);
|
||||
this.pathColorsBufferTextures[objectIndex].bind(this.gl,
|
||||
directInteriorProgram.uniforms,
|
||||
0);
|
||||
this.pathTransformBufferTextures[objectIndex].bind(this.gl,
|
||||
directInteriorProgram.uniforms,
|
||||
1);
|
||||
let indexCount = this.gl.getBufferParameter(this.gl.ELEMENT_ARRAY_BUFFER,
|
||||
this.gl.BUFFER_SIZE) / UINT32_SIZE;
|
||||
if (instanceCount == null) {
|
||||
this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0);
|
||||
} else {
|
||||
this.instancedArraysExt.drawElementsInstancedANGLE(this.gl.TRIANGLES,
|
||||
indexCount,
|
||||
this.gl.UNSIGNED_INT,
|
||||
0,
|
||||
instanceCount);
|
||||
}
|
||||
|
||||
// Set up direct curve state.
|
||||
this.gl.depthMask(false);
|
||||
this.gl.enable(this.gl.BLEND);
|
||||
this.gl.blendEquation(this.gl.FUNC_ADD);
|
||||
this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA,
|
||||
this.gl.ONE, this.gl.ONE);
|
||||
|
||||
// Set up the direct curve VAO.
|
||||
//
|
||||
// TODO(pcwalton): Cache these.
|
||||
const directCurveProgram = this.shaderPrograms[this.directCurveProgramName];
|
||||
const implicitCoverCurveVAO = this.vertexArrayObjectExt.createVertexArrayOES();
|
||||
this.vertexArrayObjectExt.bindVertexArrayOES(implicitCoverCurveVAO);
|
||||
this.initImplicitCoverCurveVAO(objectIndex);
|
||||
|
||||
// Draw direct curve parts.
|
||||
this.setTransformUniform(directCurveProgram.uniforms, objectIndex);
|
||||
this.setFramebufferSizeUniform(directCurveProgram.uniforms);
|
||||
this.setHintsUniform(directInteriorProgram.uniforms);
|
||||
this.pathColorsBufferTextures[objectIndex].bind(this.gl,
|
||||
directCurveProgram.uniforms,
|
||||
0);
|
||||
this.pathTransformBufferTextures[objectIndex].bind(this.gl,
|
||||
directCurveProgram.uniforms,
|
||||
1);
|
||||
indexCount = this.gl.getBufferParameter(this.gl.ELEMENT_ARRAY_BUFFER,
|
||||
this.gl.BUFFER_SIZE) / UINT32_SIZE;
|
||||
if (instanceCount == null) {
|
||||
this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0);
|
||||
} else {
|
||||
this.instancedArraysExt.drawElementsInstancedANGLE(this.gl.TRIANGLES,
|
||||
indexCount,
|
||||
this.gl.UNSIGNED_INT,
|
||||
0,
|
||||
instanceCount);
|
||||
}
|
||||
|
||||
this.vertexArrayObjectExt.bindVertexArrayOES(null);
|
||||
}
|
||||
}
|
||||
|
||||
private initImplicitCoverInteriorVAO(objectIndex: number): void {
|
||||
const meshes = this.meshes[objectIndex];
|
||||
|
||||
const directInteriorProgram = this.shaderPrograms[this.directInteriorProgramName];
|
||||
this.gl.useProgram(directInteriorProgram.program);
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPositions);
|
||||
this.gl.vertexAttribPointer(directInteriorProgram.attributes.aPosition,
|
||||
2,
|
||||
this.gl.FLOAT,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
|
||||
if (this.pathIDsAreInstanced)
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instancedPathIDVBO);
|
||||
else
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPathIDs);
|
||||
this.gl.vertexAttribPointer(directInteriorProgram.attributes.aPathID,
|
||||
1,
|
||||
this.gl.UNSIGNED_SHORT,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
if (this.pathIDsAreInstanced) {
|
||||
this.instancedArraysExt
|
||||
.vertexAttribDivisorANGLE(directInteriorProgram.attributes.aPathID, 1);
|
||||
}
|
||||
|
||||
this.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPosition);
|
||||
this.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPathID);
|
||||
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, meshes.coverInteriorIndices);
|
||||
}
|
||||
|
||||
private initImplicitCoverCurveVAO(objectIndex: number): void {
|
||||
const meshes = this.meshes[objectIndex];
|
||||
|
||||
const directCurveProgram = this.shaderPrograms[this.directCurveProgramName];
|
||||
this.gl.useProgram(directCurveProgram.program);
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPositions);
|
||||
this.gl.vertexAttribPointer(directCurveProgram.attributes.aPosition,
|
||||
2,
|
||||
this.gl.FLOAT,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
|
||||
if (this.pathIDsAreInstanced)
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.instancedPathIDVBO);
|
||||
else
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexPathIDs);
|
||||
this.gl.vertexAttribPointer(directCurveProgram.attributes.aPathID,
|
||||
1,
|
||||
this.gl.UNSIGNED_SHORT,
|
||||
false,
|
||||
0,
|
||||
0);
|
||||
if (this.pathIDsAreInstanced) {
|
||||
this.instancedArraysExt
|
||||
.vertexAttribDivisorANGLE(directCurveProgram.attributes.aPathID, 1);
|
||||
}
|
||||
|
||||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, meshes.bVertexLoopBlinnData);
|
||||
this.gl.vertexAttribPointer(directCurveProgram.attributes.aTexCoord,
|
||||
2,
|
||||
this.gl.UNSIGNED_BYTE,
|
||||
false,
|
||||
B_LOOP_BLINN_DATA_SIZE,
|
||||
B_LOOP_BLINN_DATA_TEX_COORD_OFFSET);
|
||||
this.gl.vertexAttribPointer(directCurveProgram.attributes.aSign,
|
||||
1,
|
||||
this.gl.BYTE,
|
||||
false,
|
||||
B_LOOP_BLINN_DATA_SIZE,
|
||||
B_LOOP_BLINN_DATA_SIGN_OFFSET);
|
||||
this.gl.enableVertexAttribArray(directCurveProgram.attributes.aPosition);
|
||||
this.gl.enableVertexAttribArray(directCurveProgram.attributes.aTexCoord);
|
||||
this.gl.enableVertexAttribArray(directCurveProgram.attributes.aPathID);
|
||||
this.gl.enableVertexAttribArray(directCurveProgram.attributes.aSign);
|
||||
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, meshes.coverCurveIndices);
|
||||
}
|
||||
|
||||
private finishTiming() {
|
||||
if (this.timerQueryPollInterval != null)
|
||||
return;
|
||||
|
||||
this.timerQueryExt.endQueryEXT(this.timerQueryExt.TIME_ELAPSED_EXT);
|
||||
|
||||
this.timerQueryPollInterval = window.setInterval(() => {
|
||||
for (const queryName of ['atlasRenderingTimerQuery', 'compositingTimerQuery'] as
|
||||
Array<'atlasRenderingTimerQuery' | 'compositingTimerQuery'>) {
|
||||
if (this.timerQueryExt.getQueryObjectEXT(this[queryName],
|
||||
this.timerQueryExt
|
||||
.QUERY_RESULT_AVAILABLE_EXT) === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const atlasRenderingTime =
|
||||
this.timerQueryExt.getQueryObjectEXT(this.atlasRenderingTimerQuery,
|
||||
this.timerQueryExt.QUERY_RESULT_EXT);
|
||||
const compositingTime =
|
||||
this.timerQueryExt.getQueryObjectEXT(this.compositingTimerQuery,
|
||||
this.timerQueryExt.QUERY_RESULT_EXT);
|
||||
this.lastTimings = {
|
||||
compositing: compositingTime / 1000000.0,
|
||||
rendering: atlasRenderingTime / 1000000.0,
|
||||
};
|
||||
|
||||
this.newTimingsReceived();
|
||||
|
||||
window.clearInterval(this.timerQueryPollInterval!);
|
||||
this.timerQueryPollInterval = null;
|
||||
}, TIME_INTERVAL_DELAY);
|
||||
}
|
||||
|
||||
private takeScreenshot() {
|
||||
const width = this.canvas.width, height = this.canvas.height;
|
||||
const scratchCanvas = document.createElement('canvas');
|
||||
|
@ -721,18 +297,6 @@ export abstract class DemoView extends PathfinderView implements Renderer, Rende
|
|||
scratchLink.click();
|
||||
document.body.removeChild(scratchLink);
|
||||
}
|
||||
|
||||
abstract get destFramebuffer(): WebGLFramebuffer | null;
|
||||
|
||||
abstract get destAllocatedSize(): glmatrix.vec2;
|
||||
abstract get destUsedSize(): glmatrix.vec2;
|
||||
|
||||
protected abstract get usedSizeFactor(): glmatrix.vec2;
|
||||
|
||||
protected abstract get worldTransform(): glmatrix.mat4;
|
||||
|
||||
protected abstract get directCurveProgramName(): keyof ShaderMap<void>;
|
||||
protected abstract get directInteriorProgramName(): keyof ShaderMap<void>;
|
||||
}
|
||||
|
||||
export interface RenderContext {
|
||||
|
@ -743,40 +307,19 @@ export interface RenderContext {
|
|||
readonly drawBuffersExt: WebGLDrawBuffers;
|
||||
readonly instancedArraysExt: ANGLEInstancedArrays;
|
||||
readonly textureHalfFloatExt: OESTextureHalfFloat;
|
||||
readonly timerQueryExt: EXTDisjointTimerQuery;
|
||||
readonly vertexArrayObjectExt: OESVertexArrayObject;
|
||||
|
||||
readonly colorAlphaFormat: GLenum;
|
||||
|
||||
readonly shaderPrograms: ShaderMap<PathfinderShaderProgram>;
|
||||
|
||||
readonly quadPositionsBuffer: WebGLBuffer;
|
||||
readonly quadElementsBuffer: WebGLBuffer;
|
||||
|
||||
readonly atlasRenderingTimerQuery: WebGLQuery;
|
||||
readonly compositingTimerQuery: WebGLQuery;
|
||||
|
||||
initQuadVAO(attributes: any): void;
|
||||
}
|
||||
|
||||
export interface Renderer {
|
||||
readonly renderContext: RenderContext;
|
||||
|
||||
readonly destFramebuffer: WebGLFramebuffer | null;
|
||||
readonly pathTransformBufferTextures: PathfinderBufferTexture[];
|
||||
|
||||
readonly meshes: PathfinderMeshBuffers[];
|
||||
readonly meshData: PathfinderMeshData[];
|
||||
|
||||
readonly colorAlphaFormat: GLenum;
|
||||
|
||||
readonly destAllocatedSize: glmatrix.vec2;
|
||||
readonly destUsedSize: glmatrix.vec2;
|
||||
|
||||
readonly emboldenAmount: glmatrix.vec2;
|
||||
|
||||
readonly bgColor: glmatrix.vec4 | null;
|
||||
readonly fgColor: glmatrix.vec4 | null;
|
||||
|
||||
pathBoundingRects(objectIndex: number): Float32Array;
|
||||
|
||||
setFramebufferSizeUniform(uniforms: UniformMap): void;
|
||||
setHintsUniform(uniforms: UniformMap): void;
|
||||
setTransformAndTexScaleUniformsForDest(uniforms: UniformMap): void;
|
||||
setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap): void;
|
||||
setTransformSTUniform(uniforms: UniformMap, objectIndex: number): void;
|
||||
setDirty(): void;
|
||||
}
|
||||
|
|
|
@ -17,10 +17,11 @@ import {createFramebuffer, createFramebufferColorTexture} from './gl-utils';
|
|||
import {createFramebufferDepthTexture, setTextureParameters, UniformMap} from './gl-utils';
|
||||
import {WebGLVertexArrayObject} from './gl-utils';
|
||||
import {B_QUAD_LOWER_INDICES_OFFSET, B_QUAD_SIZE, B_QUAD_UPPER_INDICES_OFFSET} from './meshes';
|
||||
import {Renderer} from './renderer';
|
||||
import {PathfinderShaderProgram} from './shader-loader';
|
||||
import {computeStemDarkeningAmount} from './text';
|
||||
import {FLOAT32_SIZE, lerp, UINT32_SIZE, unwrapNull} from './utils';
|
||||
import {RenderContext, Renderer} from './view';
|
||||
import {RenderContext} from './view';
|
||||
|
||||
interface FastEdgeVAOs {
|
||||
upper: WebGLVertexArrayObject;
|
||||
|
@ -145,10 +146,10 @@ export abstract class XCAAStrategy extends AntialiasingStrategy {
|
|||
const renderContext = renderer.renderContext;
|
||||
this.directColorTexture = createFramebufferColorTexture(renderContext.gl,
|
||||
this.destFramebufferSize,
|
||||
renderer.colorAlphaFormat);
|
||||
renderContext.colorAlphaFormat);
|
||||
this.directPathIDTexture = createFramebufferColorTexture(renderContext.gl,
|
||||
this.destFramebufferSize,
|
||||
renderer.colorAlphaFormat);
|
||||
renderContext.colorAlphaFormat);
|
||||
this.directFramebuffer =
|
||||
createFramebuffer(renderContext.gl,
|
||||
renderContext.drawBuffersExt,
|
||||
|
@ -1049,10 +1050,10 @@ export class MCAAMulticolorStrategy extends MCAAStrategy {
|
|||
const renderContext = renderer.renderContext;
|
||||
this.bgColorTexture = createFramebufferColorTexture(renderContext.gl,
|
||||
this.supersampledFramebufferSize,
|
||||
renderer.colorAlphaFormat);
|
||||
renderContext.colorAlphaFormat);
|
||||
this.fgColorTexture = createFramebufferColorTexture(renderContext.gl,
|
||||
this.supersampledFramebufferSize,
|
||||
renderer.colorAlphaFormat);
|
||||
renderContext.colorAlphaFormat);
|
||||
this.edgeDetectFramebuffer = createFramebuffer(renderContext.gl,
|
||||
renderContext.drawBuffersExt,
|
||||
[this.bgColorTexture, this.fgColorTexture],
|
||||
|
|
Loading…
Reference in New Issue