Start implementing basic SVG rendering. Not working yet.

This commit is contained in:
Patrick Walton 2017-08-28 22:11:15 -07:00
parent 0bae14b326
commit 8e2172f06f
7 changed files with 130 additions and 97 deletions

View File

@ -25,3 +25,6 @@ body {
color: white; color: white;
background: rgba(0, 0, 0, 0.75); background: rgba(0, 0, 0, 0.75);
} }
#pf-svg {
visibility: hidden;
}

View File

@ -19,6 +19,7 @@
"gl-matrix": "^2.4.0", "gl-matrix": "^2.4.0",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"opentype.js": "^0.7.3", "opentype.js": "^0.7.3",
"parse-color": "^1.0.0",
"path-data-polyfill.js": "^1.0.2", "path-data-polyfill.js": "^1.0.2",
"ts-loader": "^2.3.2", "ts-loader": "^2.3.2",
"typescript": "^2.4.2", "typescript": "^2.4.2",

View File

@ -88,10 +88,12 @@ export abstract class ECAAStrategy implements AntialiasingStrategy {
null); null);
setTextureParameters(view.gl, view.gl.NEAREST); setTextureParameters(view.gl, view.gl.NEAREST);
this.aaDepthTexture = createFramebufferDepthTexture(view.gl, this.framebufferSize);
this.aaFramebuffer = createFramebuffer(view.gl, this.aaFramebuffer = createFramebuffer(view.gl,
view.drawBuffersExt, view.drawBuffersExt,
[this.aaAlphaTexture], [this.aaAlphaTexture],
view.destDepthTexture); this.aaDepthTexture);
} }
private createCoverVAO(view: MonochromePathfinderView) { private createCoverVAO(view: MonochromePathfinderView) {
@ -421,6 +423,7 @@ export abstract class ECAAStrategy implements AntialiasingStrategy {
protected directColorTexture: WebGLTexture; protected directColorTexture: WebGLTexture;
protected directPathIDTexture: WebGLTexture; protected directPathIDTexture: WebGLTexture;
protected aaDepthTexture: WebGLTexture;
protected framebufferSize: glmatrix.vec2; protected framebufferSize: glmatrix.vec2;
} }
@ -483,7 +486,7 @@ export class ECAAMulticolorStrategy extends ECAAStrategy {
this.edgeDetectFramebuffer = createFramebuffer(view.gl, this.edgeDetectFramebuffer = createFramebuffer(view.gl,
view.drawBuffersExt, view.drawBuffersExt,
[this.bgColorTexture, this.fgColorTexture], [this.bgColorTexture, this.fgColorTexture],
view.destDepthTexture); this.aaDepthTexture);
} }
protected createEdgeDetectVAO(view: MonochromePathfinderView) { protected createEdgeDetectVAO(view: MonochromePathfinderView) {

View File

@ -46,7 +46,6 @@ export interface Meshes<T> {
export class PathfinderMeshData implements Meshes<ArrayBuffer> { export class PathfinderMeshData implements Meshes<ArrayBuffer> {
constructor(meshes: any) { constructor(meshes: any) {
console.log(meshes);
for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof Meshes<void>>) for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof Meshes<void>>)
this[bufferName] = base64js.toByteArray(meshes[bufferName]).buffer as ArrayBuffer; this[bufferName] = base64js.toByteArray(meshes[bufferName]).buffer as ArrayBuffer;

View File

@ -20,6 +20,8 @@ import {PathfinderView, Timings} from './view';
import AppController from './app-controller'; import AppController from './app-controller';
import SSAAStrategy from "./ssaa-strategy"; import SSAAStrategy from "./ssaa-strategy";
const parseColor = require('parse-color');
const SVG_NS: string = "http://www.w3.org/2000/svg"; const SVG_NS: string = "http://www.w3.org/2000/svg";
const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths"; const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths";
@ -35,9 +37,11 @@ declare class SVGPathSegment {
values: number[]; values: number[];
} }
declare class SVGPathElement { declare global {
interface SVGPathElement {
getPathData(settings: any): SVGPathSegment[]; getPathData(settings: any): SVGPathSegment[];
} }
}
interface AntialiasingStrategyTable { interface AntialiasingStrategyTable {
none: typeof NoAAStrategy; none: typeof NoAAStrategy;
@ -117,7 +121,7 @@ class SVGDemoController extends AppController<SVGDemoView> {
private meshesReceived() { private meshesReceived() {
this.view.then(view => { this.view.then(view => {
// TODO(pcwalton): Upload path color data. view.uploadPathData(this.pathElements);
view.attachMeshes(this.meshes); view.attachMeshes(this.meshes);
}) })
} }
@ -136,7 +140,7 @@ class SVGDemoView extends PathfinderView {
this.appController = appController; this.appController = appController;
this.resized(false); this._scale = 1.0;
} }
protected resized(initialSize: boolean) {} protected resized(initialSize: boolean) {}
@ -145,24 +149,25 @@ class SVGDemoView extends PathfinderView {
return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height); return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height);
} }
get destDepthTexture() { get destFramebuffer(): WebGLFramebuffer | null {
return panic("TODO"); return null;
}
get destFramebuffer() {
return panic("TODO");
} }
get destUsedSize(): glmatrix.vec2 { get destUsedSize(): glmatrix.vec2 {
return this.destAllocatedSize; return this.destAllocatedSize;
} }
setTransformAndTexScaleUniformsForDest() { protected panned(): void {}
panic("TODO");
uploadPathData(elements: SVGPathElement[]) {
const pathColors = new Uint8Array(4 * (elements.length + 1));
for (let pathIndex = 0; pathIndex < elements.length; pathIndex++) {
const style = window.getComputedStyle(elements[pathIndex]);
const fillColor = style.fill === 'none' ? [0, 0, 0, 0] : parseColor(style.fill).rgba;
pathColors.set(fillColor, (pathIndex + 1) * 4);
} }
setTransformSTAndTexScaleUniformsForDest() { this.pathColorsBufferTexture.upload(this.gl, pathColors);
panic("TODO");
} }
protected createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number): protected createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number):
@ -176,6 +181,21 @@ class SVGDemoView extends PathfinderView {
// TODO(pcwalton) // TODO(pcwalton)
} }
protected get usedSizeFactor(): glmatrix.vec2 {
return glmatrix.vec2.create();
}
protected get scale(): number {
return this._scale;
}
protected set scale(newScale: number) {
this._scale = newScale;
this.setDirty();
}
private _scale: number;
private appController: SVGDemoController; private appController: SVGDemoController;
} }

View File

@ -64,8 +64,6 @@ And the mome raths outgrabe.`;
const INITIAL_FONT_SIZE: number = 72.0; const INITIAL_FONT_SIZE: number = 72.0;
const SCALE_FACTOR: number = 1.0 / 100.0;
const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font"; const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font";
const B_POSITION_SIZE: number = 8; const B_POSITION_SIZE: number = 8;
@ -207,15 +205,6 @@ class TextDemoController extends AppController<TextDemoView> {
}) })
} }
scaleFontSize(scale: number) {
this.setFontSize(scale * this.fontSize);
}
private setFontSize(newPixelsPerEm: number) {
this.fontSize = newPixelsPerEm;
this.view.then(view => view.attachText());
}
updateTimings(newTimes: Timings) { updateTimings(newTimes: Timings) {
this.fpsLabel.innerHTML = this.fpsLabel.innerHTML =
`${newTimes.atlasRendering} ms atlas, ${newTimes.compositing} ms compositing`; `${newTimes.atlasRendering} ms atlas, ${newTimes.compositing} ms compositing`;
@ -225,6 +214,17 @@ class TextDemoController extends AppController<TextDemoView> {
return this._atlas; return this._atlas;
} }
/// The font size in pixels per em.
get fontSize(): number {
return this._fontSize;
}
/// The font size in pixels per em.
set fontSize(newFontSize: number) {
this._fontSize = newFontSize;
this.view.then(view => view.attachText());
}
private aaLevelSelect: HTMLSelectElement; private aaLevelSelect: HTMLSelectElement;
private fpsLabel: HTMLElement; private fpsLabel: HTMLElement;
@ -238,8 +238,7 @@ class TextDemoController extends AppController<TextDemoView> {
private meshes: PathfinderMeshData; private meshes: PathfinderMeshData;
/// The font size in pixels per em. private _fontSize: number;
fontSize: number;
} }
class TextDemoView extends MonochromePathfinderView { class TextDemoView extends MonochromePathfinderView {
@ -250,10 +249,6 @@ class TextDemoView extends MonochromePathfinderView {
super(canvas, commonShaderSource, shaderSources); super(canvas, commonShaderSource, shaderSources);
this.appController = appController; this.appController = appController;
this.translation = glmatrix.vec2.create();
this.canvas.addEventListener('wheel', event => this.onWheel(event), false);
} }
protected initContext() { protected initContext() {
@ -453,38 +448,7 @@ class TextDemoView extends MonochromePathfinderView {
this.setDirty(); this.setDirty();
} }
private onWheel(event: WheelEvent) { protected panned() {
event.preventDefault();
if (event.ctrlKey) {
// Zoom event: see https://developer.mozilla.org/en-US/docs/Web/Events/wheel
const mouseLocation = glmatrix.vec2.fromValues(event.clientX, event.clientY);
const canvasLocation = this.canvas.getBoundingClientRect();
mouseLocation[0] -= canvasLocation.left;
mouseLocation[1] = canvasLocation.bottom - mouseLocation[1];
glmatrix.vec2.scale(mouseLocation, mouseLocation, window.devicePixelRatio);
const absoluteTranslation = glmatrix.vec2.create();
glmatrix.vec2.sub(absoluteTranslation, this.translation, mouseLocation);
glmatrix.vec2.scale(absoluteTranslation,
absoluteTranslation,
1.0 / this.appController.fontSize);
const scale = 1.0 - event.deltaY * window.devicePixelRatio * SCALE_FACTOR;
this.appController.scaleFontSize(scale);
glmatrix.vec2.scale(absoluteTranslation,
absoluteTranslation,
this.appController.fontSize);
glmatrix.vec2.add(this.translation, absoluteTranslation, mouseLocation);
return;
}
// Pan event.
const delta = glmatrix.vec2.fromValues(-event.deltaX, event.deltaY);
glmatrix.vec2.scale(delta, delta, window.devicePixelRatio);
glmatrix.vec2.add(this.translation, this.translation, delta);
this.rebuildAtlasIfNecessary(); this.rebuildAtlasIfNecessary();
} }
@ -494,33 +458,16 @@ class TextDemoView extends MonochromePathfinderView {
this.setDirty(); this.setDirty();
} }
setIdentityTexScaleUniform(uniforms: UniformMap) { private setIdentityTexScaleUniform(uniforms: UniformMap) {
this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0); this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
} }
private get usedSizeFactor(): glmatrix.vec2 { protected get usedSizeFactor(): glmatrix.vec2 {
const usedSize = glmatrix.vec2.create(); const usedSize = glmatrix.vec2.create();
glmatrix.vec2.div(usedSize, this.appController.atlas.usedSize, ATLAS_SIZE); glmatrix.vec2.div(usedSize, this.appController.atlas.usedSize, ATLAS_SIZE);
return usedSize; return usedSize;
} }
setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap) {
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) {
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]);
}
protected compositeIfNecessary() { protected compositeIfNecessary() {
// Set up composite state. // Set up composite state.
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
@ -579,10 +526,6 @@ class TextDemoView extends MonochromePathfinderView {
return this.atlasFramebuffer; return this.atlasFramebuffer;
} }
get destDepthTexture(): WebGLTexture {
return this.atlasDepthTexture;
}
get destAllocatedSize(): glmatrix.vec2 { get destAllocatedSize(): glmatrix.vec2 {
return ATLAS_SIZE; return ATLAS_SIZE;
} }
@ -591,6 +534,14 @@ class TextDemoView extends MonochromePathfinderView {
return this.appController.atlas.usedSize; return this.appController.atlas.usedSize;
} }
protected get scale(): number {
return this.appController.fontSize;
}
protected set scale(newScale: number) {
this.appController.fontSize = newScale;
}
protected createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number): protected createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number):
AntialiasingStrategy { AntialiasingStrategy {
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel); return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel);
@ -600,8 +551,6 @@ class TextDemoView extends MonochromePathfinderView {
this.appController.updateTimings(timings); this.appController.updateTimings(timings);
} }
private translation: glmatrix.vec2;
atlasFramebuffer: WebGLFramebuffer; atlasFramebuffer: WebGLFramebuffer;
atlasDepthTexture: WebGLTexture; atlasDepthTexture: WebGLTexture;

View File

@ -18,6 +18,8 @@ import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader';
import {PathfinderError, UINT32_SIZE, expectNotNull, unwrapNull} from './utils'; import {PathfinderError, UINT32_SIZE, expectNotNull, unwrapNull} from './utils';
import PathfinderBufferTexture from './buffer-texture'; import PathfinderBufferTexture from './buffer-texture';
const SCALE_FACTOR: number = 1.0 / 100.0;
const TIME_INTERVAL_DELAY: number = 32; const TIME_INTERVAL_DELAY: number = 32;
const B_LOOP_BLINN_DATA_SIZE: number = 4; const B_LOOP_BLINN_DATA_SIZE: number = 4;
@ -53,6 +55,8 @@ export abstract class PathfinderView {
this.initContext(); this.initContext();
this.translation = glmatrix.vec2.create();
const shaderSource = this.compileShaders(commonShaderSource, shaderSources); const shaderSource = this.compileShaders(commonShaderSource, shaderSources);
this.shaderPrograms = this.linkShaders(shaderSource); this.shaderPrograms = this.linkShaders(shaderSource);
@ -64,6 +68,8 @@ export abstract class PathfinderView {
window.addEventListener('resize', () => this.resizeToFit(false), false); window.addEventListener('resize', () => this.resizeToFit(false), false);
this.resizeToFit(true); this.resizeToFit(true);
this.canvas.addEventListener('wheel', event => this.onWheel(event), false);
} }
setAntialiasingOptions(aaType: AntialiasingStrategyName, aaLevel: number) { setAntialiasingOptions(aaType: AntialiasingStrategyName, aaLevel: number) {
@ -365,8 +371,52 @@ export abstract class PathfinderView {
window.requestAnimationFrame(() => this.redraw()); window.requestAnimationFrame(() => this.redraw());
} }
abstract setTransformAndTexScaleUniformsForDest(uniforms: UniformMap): void; private onWheel(event: WheelEvent) {
abstract setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap): void; event.preventDefault();
if (event.ctrlKey) {
// Zoom event: see https://developer.mozilla.org/en-US/docs/Web/Events/wheel
const mouseLocation = glmatrix.vec2.fromValues(event.clientX, event.clientY);
const canvasLocation = this.canvas.getBoundingClientRect();
mouseLocation[0] -= canvasLocation.left;
mouseLocation[1] = canvasLocation.bottom - mouseLocation[1];
glmatrix.vec2.scale(mouseLocation, mouseLocation, window.devicePixelRatio);
const absoluteTranslation = glmatrix.vec2.create();
glmatrix.vec2.sub(absoluteTranslation, this.translation, mouseLocation);
glmatrix.vec2.scale(absoluteTranslation, absoluteTranslation, 1.0 / this.scale);
this.scale *= 1.0 - event.deltaY * window.devicePixelRatio * SCALE_FACTOR;
glmatrix.vec2.scale(absoluteTranslation, absoluteTranslation, this.scale);
glmatrix.vec2.add(this.translation, absoluteTranslation, mouseLocation);
return;
}
// Pan event.
const delta = glmatrix.vec2.fromValues(-event.deltaX, event.deltaY);
glmatrix.vec2.scale(delta, delta, window.devicePixelRatio);
glmatrix.vec2.add(this.translation, this.translation, delta);
this.panned();
}
setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap) {
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) {
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]);
}
protected abstract createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number): protected abstract createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number):
AntialiasingStrategy; AntialiasingStrategy;
@ -375,14 +425,22 @@ export abstract class PathfinderView {
protected abstract updateTimings(timings: Timings): void; protected abstract updateTimings(timings: Timings): void;
abstract get destFramebuffer(): WebGLFramebuffer; protected abstract panned(): void;
abstract get destDepthTexture(): WebGLTexture;
abstract get destFramebuffer(): WebGLFramebuffer | null;
abstract get destAllocatedSize(): glmatrix.vec2; abstract get destAllocatedSize(): glmatrix.vec2;
abstract get destUsedSize(): glmatrix.vec2; abstract get destUsedSize(): glmatrix.vec2;
protected abstract get usedSizeFactor(): glmatrix.vec2;
protected abstract get scale(): number;
protected abstract set scale(newScale: number);
protected antialiasingStrategy: AntialiasingStrategy; protected antialiasingStrategy: AntialiasingStrategy;
protected translation: glmatrix.vec2;
protected canvas: HTMLCanvasElement; protected canvas: HTMLCanvasElement;
gl: WebGLRenderingContext; gl: WebGLRenderingContext;