Factor the SVG renderer out of the demo so it can be reused

This commit is contained in:
Patrick Walton 2017-12-05 11:23:13 -08:00
parent 15d8c98ff9
commit d0d08816b9
2 changed files with 209 additions and 181 deletions

View File

@ -1,4 +1,4 @@
// pathfinder/client/src/svg-demo.ts
// pathfinder/demo/client/src/svg-demo.ts
//
// Copyright © 2017 The Pathfinder Project Developers.
//
@ -11,21 +11,13 @@
import * as glmatrix from 'gl-matrix';
import * as _ from 'lodash';
import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy";
import {SubpixelAAType} from "./aa-strategy";
import {DemoAppController} from './app-controller';
import PathfinderBufferTexture from "./buffer-texture";
import {OrthographicCamera} from "./camera";
import {UniformMap} from './gl-utils';
import {PathfinderMeshData} from "./meshes";
import {CompositingOperation, RenderTaskType} from './render-task';
import {PathTransformBuffers, Renderer} from './renderer';
import {ShaderMap, ShaderProgramSource} from './shader-loader';
import SSAAStrategy from "./ssaa-strategy";
import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader';
import {panic, Range, unwrapNull} from './utils';
import {DemoView, Timings} from './view';
import {ECAAMulticolorStrategy, XCAAStrategy} from "./xcaa-strategy";
import {SVGRenderer} from './svg-renderer';
import {DemoView} from './view';
const parseColor = require('parse-color');
@ -33,18 +25,6 @@ const SVG_NS: string = "http://www.w3.org/2000/svg";
const DEFAULT_FILE: string = 'tiger';
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
none: NoAAStrategy,
ssaa: SSAAStrategy,
xcaa: ECAAMulticolorStrategy,
};
interface AntialiasingStrategyTable {
none: typeof NoAAStrategy;
ssaa: typeof SSAAStrategy;
xcaa: typeof XCAAStrategy;
}
class SVGDemoController extends DemoAppController<SVGDemoView> {
loader: SVGLoader;
@ -112,173 +92,20 @@ class SVGDemoView extends DemoView {
}
}
class SVGDemoRenderer extends Renderer {
class SVGDemoRenderer extends SVGRenderer {
renderContext: SVGDemoView;
camera: OrthographicCamera;
get usesSTTransform(): boolean {
return this.camera.usesSTTransform;
protected get loader(): SVGLoader {
return this.renderContext.appController.loader;
}
get destAllocatedSize(): glmatrix.vec2 {
const canvas = this.renderContext.canvas;
return glmatrix.vec2.clone([canvas.width, canvas.height]);
}
get destFramebuffer(): WebGLFramebuffer | null {
return null;
}
get destUsedSize(): glmatrix.vec2 {
return this.destAllocatedSize;
}
get usesIntermediateRenderTargets(): boolean {
return true;
}
get backgroundColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
}
protected get objectCount(): number {
const loader = this.renderContext.appController.loader;
return loader.renderTasks.length;
}
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();
this.camera.onRotate = () => this.renderContext.setDirty();
}
setHintsUniform(uniforms: UniformMap): void {
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
}
pathBoundingRects(objectIndex: number): Float32Array {
const loader = this.renderContext.appController.loader;
const boundingRectsBuffer = new Float32Array((loader.pathBounds.length + 1) * 4);
for (let pathIndex = 0; pathIndex < loader.pathBounds.length; pathIndex++)
boundingRectsBuffer.set(loader.pathBounds[pathIndex], (pathIndex + 1) * 4);
return boundingRectsBuffer;
}
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();
}
renderTaskTypeForObject(objectIndex: number): RenderTaskType {
const loader = this.renderContext.appController.loader;
return loader.renderTasks[objectIndex].type;
}
compositingOperationForObject(objectIndex: number): CompositingOperation | null {
const loader = this.renderContext.appController.loader;
return loader.renderTasks[objectIndex].compositingOperation;
}
meshIndexForObject(objectIndex: number): number {
return 0;
}
pathRangeForObject(objectIndex: number): Range {
const loader = this.renderContext.appController.loader;
return loader.renderTasks[objectIndex].instanceIndices;
}
protected get usedSizeFactor(): glmatrix.vec2 {
return glmatrix.vec2.clone([1.0, 1.0]);
}
protected get worldTransform(): glmatrix.mat4 {
const canvas = this.renderContext.canvas;
const transform = glmatrix.mat4.create();
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]);
const centerPoint = glmatrix.vec3.clone([canvas.width * 0.5, canvas.height * 0.5, 0.0]);
glmatrix.mat4.translate(transform, transform, centerPoint);
glmatrix.mat4.rotateZ(transform, transform, this.camera.rotationAngle);
glmatrix.vec3.negate(centerPoint, centerPoint);
glmatrix.mat4.translate(transform, transform, centerPoint);
const translation = this.camera.translation;
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 clearColorForObject(objectIndex: number): glmatrix.vec4 | null {
return glmatrix.vec4.create();
}
protected directCurveProgramName(): keyof ShaderMap<void> {
if (this.antialiasingStrategy instanceof XCAAStrategy)
return 'xcaaMultiDirectCurve';
return 'directCurve';
}
protected directInteriorProgramName(): keyof ShaderMap<void> {
if (this.antialiasingStrategy instanceof XCAAStrategy)
return 'xcaaMultiDirectInterior';
return 'directInterior';
protected get canvas(): HTMLCanvasElement {
return this.renderContext.canvas;
}
protected newTimingsReceived(): void {
this.renderContext.appController.newTimingsReceived(this.lastTimings);
}
protected pathColorsForObject(objectIndex: number): Uint8Array {
const instances = this.renderContext.appController.loader.pathInstances;
const pathColors = new Uint8Array(4 * (instances.length + 1));
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
const startOffset = (pathIndex + 1) * 4;
// Set color.
const color: ArrayLike<number> = instances[pathIndex].color;
pathColors.set(instances[pathIndex].color, startOffset);
pathColors[startOffset + 3] = color[3] * 255;
}
return pathColors;
}
protected pathTransformsForObject(objectIndex: number): PathTransformBuffers<Float32Array> {
const instances = this.renderContext.appController.loader.pathInstances;
const pathTransforms = this.createPathTransformBuffers(instances.length);
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
// TODO(pcwalton): Set transform.
const startOffset = (pathIndex + 1) * 4;
pathTransforms.st.set([1, 1, 0, 0], startOffset);
}
return pathTransforms;
}
protected createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType):
AntialiasingStrategy {
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
}
protected compositeIfNecessary(): void {}
}
function main() {

View File

@ -0,0 +1,201 @@
// pathfinder/demo/client/src/svg-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 {SubpixelAAType} from './aa-strategy';
import {OrthographicCamera} from "./camera";
import {UniformMap} from './gl-utils';
import {PathfinderMeshData} from './meshes';
import {CompositingOperation, RenderTaskType} from './render-task';
import {PathTransformBuffers, Renderer} from "./renderer";
import {ShaderMap} from './shader-loader';
import SSAAStrategy from './ssaa-strategy';
import {SVGLoader} from './svg-loader';
import {Range} from './utils';
import {RenderContext} from './view';
import {ECAAMulticolorStrategy, XCAAStrategy} from './xcaa-strategy';
interface AntialiasingStrategyTable {
none: typeof NoAAStrategy;
ssaa: typeof SSAAStrategy;
xcaa: typeof XCAAStrategy;
}
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
none: NoAAStrategy,
ssaa: SSAAStrategy,
xcaa: ECAAMulticolorStrategy,
};
export abstract class SVGRenderer extends Renderer {
renderContext: RenderContext;
camera: OrthographicCamera;
get usesSTTransform(): boolean {
return this.camera.usesSTTransform;
}
get destAllocatedSize(): glmatrix.vec2 {
const canvas = this.canvas;
return glmatrix.vec2.clone([canvas.width, canvas.height]);
}
get destFramebuffer(): WebGLFramebuffer | null {
return null;
}
get destUsedSize(): glmatrix.vec2 {
return this.destAllocatedSize;
}
get usesIntermediateRenderTargets(): boolean {
return true;
}
get backgroundColor(): glmatrix.vec4 {
return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]);
}
protected get objectCount(): number {
return this.loader.renderTasks.length;
}
protected abstract get loader(): SVGLoader;
protected abstract get canvas(): HTMLCanvasElement;
constructor(renderContext: RenderContext) {
super(renderContext);
this.camera = new OrthographicCamera(this.canvas, { scaleBounds: true });
this.camera.onPan = () => this.renderContext.setDirty();
this.camera.onZoom = () => this.renderContext.setDirty();
this.camera.onRotate = () => this.renderContext.setDirty();
}
setHintsUniform(uniforms: UniformMap): void {
this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0);
}
pathBoundingRects(objectIndex: number): Float32Array {
const loader = this.loader;
const boundingRectsBuffer = new Float32Array((loader.pathBounds.length + 1) * 4);
for (let pathIndex = 0; pathIndex < loader.pathBounds.length; pathIndex++)
boundingRectsBuffer.set(loader.pathBounds[pathIndex], (pathIndex + 1) * 4);
return boundingRectsBuffer;
}
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();
}
renderTaskTypeForObject(objectIndex: number): RenderTaskType {
return this.loader.renderTasks[objectIndex].type;
}
compositingOperationForObject(objectIndex: number): CompositingOperation | null {
return this.loader.renderTasks[objectIndex].compositingOperation;
}
meshIndexForObject(objectIndex: number): number {
return 0;
}
pathRangeForObject(objectIndex: number): Range {
return this.loader.renderTasks[objectIndex].instanceIndices;
}
protected get usedSizeFactor(): glmatrix.vec2 {
return glmatrix.vec2.clone([1.0, 1.0]);
}
protected get worldTransform(): glmatrix.mat4 {
const canvas = this.canvas;
const transform = glmatrix.mat4.create();
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]);
const centerPoint = glmatrix.vec3.clone([canvas.width * 0.5, canvas.height * 0.5, 0.0]);
glmatrix.mat4.translate(transform, transform, centerPoint);
glmatrix.mat4.rotateZ(transform, transform, this.camera.rotationAngle);
glmatrix.vec3.negate(centerPoint, centerPoint);
glmatrix.mat4.translate(transform, transform, centerPoint);
const translation = this.camera.translation;
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 clearColorForObject(objectIndex: number): glmatrix.vec4 | null {
return glmatrix.vec4.create();
}
protected directCurveProgramName(): keyof ShaderMap<void> {
if (this.antialiasingStrategy instanceof XCAAStrategy)
return 'xcaaMultiDirectCurve';
return 'directCurve';
}
protected directInteriorProgramName(): keyof ShaderMap<void> {
if (this.antialiasingStrategy instanceof XCAAStrategy)
return 'xcaaMultiDirectInterior';
return 'directInterior';
}
protected pathColorsForObject(objectIndex: number): Uint8Array {
const instances = this.loader.pathInstances;
const pathColors = new Uint8Array(4 * (instances.length + 1));
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
const startOffset = (pathIndex + 1) * 4;
// Set color.
const color: ArrayLike<number> = instances[pathIndex].color;
pathColors.set(instances[pathIndex].color, startOffset);
pathColors[startOffset + 3] = color[3] * 255;
}
return pathColors;
}
protected pathTransformsForObject(objectIndex: number): PathTransformBuffers<Float32Array> {
const instances = this.loader.pathInstances;
const pathTransforms = this.createPathTransformBuffers(instances.length);
for (let pathIndex = 0; pathIndex < instances.length; pathIndex++) {
// TODO(pcwalton): Set transform.
const startOffset = (pathIndex + 1) * 4;
pathTransforms.st.set([1, 1, 0, 0], startOffset);
}
return pathTransforms;
}
protected createAAStrategy(aaType: AntialiasingStrategyName,
aaLevel: number,
subpixelAA: SubpixelAAType):
AntialiasingStrategy {
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA);
}
protected compositeIfNecessary(): void {}
}