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;
background: rgba(0, 0, 0, 0.75);
}
#pf-svg {
visibility: hidden;
}

View File

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

View File

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

View File

@ -46,7 +46,6 @@ export interface Meshes<T> {
export class PathfinderMeshData implements Meshes<ArrayBuffer> {
constructor(meshes: any) {
console.log(meshes);
for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof Meshes<void>>)
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 SSAAStrategy from "./ssaa-strategy";
const parseColor = require('parse-color');
const SVG_NS: string = "http://www.w3.org/2000/svg";
const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths";
@ -35,8 +37,10 @@ declare class SVGPathSegment {
values: number[];
}
declare class SVGPathElement {
getPathData(settings: any): SVGPathSegment[];
declare global {
interface SVGPathElement {
getPathData(settings: any): SVGPathSegment[];
}
}
interface AntialiasingStrategyTable {
@ -117,7 +121,7 @@ class SVGDemoController extends AppController<SVGDemoView> {
private meshesReceived() {
this.view.then(view => {
// TODO(pcwalton): Upload path color data.
view.uploadPathData(this.pathElements);
view.attachMeshes(this.meshes);
})
}
@ -136,7 +140,7 @@ class SVGDemoView extends PathfinderView {
this.appController = appController;
this.resized(false);
this._scale = 1.0;
}
protected resized(initialSize: boolean) {}
@ -145,24 +149,25 @@ class SVGDemoView extends PathfinderView {
return glmatrix.vec2.fromValues(this.canvas.width, this.canvas.height);
}
get destDepthTexture() {
return panic("TODO");
}
get destFramebuffer() {
return panic("TODO");
get destFramebuffer(): WebGLFramebuffer | null {
return null;
}
get destUsedSize(): glmatrix.vec2 {
return this.destAllocatedSize;
}
setTransformAndTexScaleUniformsForDest() {
panic("TODO");
}
protected panned(): void {}
setTransformSTAndTexScaleUniformsForDest() {
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);
}
this.pathColorsBufferTexture.upload(this.gl, pathColors);
}
protected createAAStrategy(aaType: AntialiasingStrategyName, aaLevel: number):
@ -176,6 +181,21 @@ class SVGDemoView extends PathfinderView {
// 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;
}

View File

@ -64,8 +64,6 @@ And the mome raths outgrabe.`;
const INITIAL_FONT_SIZE: number = 72.0;
const SCALE_FACTOR: number = 1.0 / 100.0;
const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font";
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) {
this.fpsLabel.innerHTML =
`${newTimes.atlasRendering} ms atlas, ${newTimes.compositing} ms compositing`;
@ -225,6 +214,17 @@ class TextDemoController extends AppController<TextDemoView> {
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 fpsLabel: HTMLElement;
@ -238,8 +238,7 @@ class TextDemoController extends AppController<TextDemoView> {
private meshes: PathfinderMeshData;
/// The font size in pixels per em.
fontSize: number;
private _fontSize: number;
}
class TextDemoView extends MonochromePathfinderView {
@ -250,10 +249,6 @@ class TextDemoView extends MonochromePathfinderView {
super(canvas, commonShaderSource, shaderSources);
this.appController = appController;
this.translation = glmatrix.vec2.create();
this.canvas.addEventListener('wheel', event => this.onWheel(event), false);
}
protected initContext() {
@ -453,38 +448,7 @@ class TextDemoView extends MonochromePathfinderView {
this.setDirty();
}
private onWheel(event: WheelEvent) {
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);
protected panned() {
this.rebuildAtlasIfNecessary();
}
@ -494,33 +458,16 @@ class TextDemoView extends MonochromePathfinderView {
this.setDirty();
}
setIdentityTexScaleUniform(uniforms: UniformMap) {
private setIdentityTexScaleUniform(uniforms: UniformMap) {
this.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0);
}
private get usedSizeFactor(): glmatrix.vec2 {
protected get usedSizeFactor(): glmatrix.vec2 {
const usedSize = glmatrix.vec2.create();
glmatrix.vec2.div(usedSize, this.appController.atlas.usedSize, ATLAS_SIZE);
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() {
// Set up composite state.
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
@ -579,10 +526,6 @@ class TextDemoView extends MonochromePathfinderView {
return this.atlasFramebuffer;
}
get destDepthTexture(): WebGLTexture {
return this.atlasDepthTexture;
}
get destAllocatedSize(): glmatrix.vec2 {
return ATLAS_SIZE;
}
@ -591,6 +534,14 @@ class TextDemoView extends MonochromePathfinderView {
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):
AntialiasingStrategy {
return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel);
@ -600,8 +551,6 @@ class TextDemoView extends MonochromePathfinderView {
this.appController.updateTimings(timings);
}
private translation: glmatrix.vec2;
atlasFramebuffer: WebGLFramebuffer;
atlasDepthTexture: WebGLTexture;

View File

@ -18,6 +18,8 @@ import {ShaderProgramSource, UnlinkedShaderProgram} from './shader-loader';
import {PathfinderError, UINT32_SIZE, expectNotNull, unwrapNull} from './utils';
import PathfinderBufferTexture from './buffer-texture';
const SCALE_FACTOR: number = 1.0 / 100.0;
const TIME_INTERVAL_DELAY: number = 32;
const B_LOOP_BLINN_DATA_SIZE: number = 4;
@ -53,6 +55,8 @@ export abstract class PathfinderView {
this.initContext();
this.translation = glmatrix.vec2.create();
const shaderSource = this.compileShaders(commonShaderSource, shaderSources);
this.shaderPrograms = this.linkShaders(shaderSource);
@ -64,6 +68,8 @@ export abstract class PathfinderView {
window.addEventListener('resize', () => this.resizeToFit(false), false);
this.resizeToFit(true);
this.canvas.addEventListener('wheel', event => this.onWheel(event), false);
}
setAntialiasingOptions(aaType: AntialiasingStrategyName, aaLevel: number) {
@ -365,8 +371,52 @@ export abstract class PathfinderView {
window.requestAnimationFrame(() => this.redraw());
}
abstract setTransformAndTexScaleUniformsForDest(uniforms: UniformMap): void;
abstract setTransformSTAndTexScaleUniformsForDest(uniforms: UniformMap): void;
private onWheel(event: WheelEvent) {
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):
AntialiasingStrategy;
@ -375,14 +425,22 @@ export abstract class PathfinderView {
protected abstract updateTimings(timings: Timings): void;
abstract get destFramebuffer(): WebGLFramebuffer;
abstract get destDepthTexture(): WebGLTexture;
protected abstract panned(): void;
abstract get destFramebuffer(): WebGLFramebuffer | null;
abstract get destAllocatedSize(): 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 translation: glmatrix.vec2;
protected canvas: HTMLCanvasElement;
gl: WebGLRenderingContext;