From 208565603ecd76ae27701ccaeead85d40f1a4136 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 14 Nov 2018 13:56:45 -0800 Subject: [PATCH] WIP --- .gitignore | 1 + demo2/cover.fs.glsl | 19 +++ demo2/cover.vs.glsl | 24 ++++ demo2/declaration.d.ts | 5 + demo2/package-lock.json | 295 ++++++++++++++++++++++++++++++++++++++++ demo2/package.json | 11 ++ demo2/pathfinder.css | 4 + demo2/pathfinder.ts | 201 +++++++++++++++++++++++---- 8 files changed, 531 insertions(+), 29 deletions(-) create mode 100644 demo2/cover.fs.glsl create mode 100644 demo2/cover.vs.glsl create mode 100644 demo2/declaration.d.ts create mode 100644 demo2/package-lock.json create mode 100644 demo2/package.json diff --git a/.gitignore b/.gitignore index 1c44211f..e9a7ac29 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ /demo/client/package-lock.json /demo/server/target /demo/server/Rocket.toml +/demo2/dist .DS_Store target node_modules diff --git a/demo2/cover.fs.glsl b/demo2/cover.fs.glsl new file mode 100644 index 00000000..3503de1d --- /dev/null +++ b/demo2/cover.fs.glsl @@ -0,0 +1,19 @@ +#version 300 es + +// pathfinder/demo2/cover.fs.glsl +// +// Copyright © 2018 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +precision highp float; + +out vec4 oFragColor; + +void main() { + oFragColor = vec4(0.0, 0.0, 1.0, 1.0); +} diff --git a/demo2/cover.vs.glsl b/demo2/cover.vs.glsl new file mode 100644 index 00000000..d0a1cf0d --- /dev/null +++ b/demo2/cover.vs.glsl @@ -0,0 +1,24 @@ +#version 300 es + +// pathfinder/demo2/cover.vs.glsl +// +// Copyright © 2018 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +precision highp float; + +uniform vec2 uFramebufferSize; +uniform vec2 uTileSize; + +in vec2 aTessCoord; +in vec2 aTileOrigin; + +void main() { + vec2 position = aTileOrigin + uTileSize * aTessCoord; + gl_Position = vec4(position / uFramebufferSize * 2.0 - 1.0, 0.0, 1.0); +} diff --git a/demo2/declaration.d.ts b/demo2/declaration.d.ts new file mode 100644 index 00000000..130a7f98 --- /dev/null +++ b/demo2/declaration.d.ts @@ -0,0 +1,5 @@ +declare module "*.glsl"; +declare module "*.jpg"; +declare module "*.svg"; + +declare function require(s: string): any; diff --git a/demo2/package-lock.json b/demo2/package-lock.json new file mode 100644 index 00000000..d79c7d16 --- /dev/null +++ b/demo2/package-lock.json @@ -0,0 +1,295 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@choojs/findup": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@choojs/findup/-/findup-0.2.1.tgz", + "integrity": "sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw==", + "dev": true, + "requires": { + "commander": "2.19.0" + } + }, + "@types/webgl2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz", + "integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw==" + }, + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "glsl-inject-defines": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/glsl-inject-defines/-/glsl-inject-defines-1.0.3.tgz", + "integrity": "sha1-3RqswsF/yyvT/DJBHGYz0Ne2D9Q=", + "dev": true, + "requires": { + "glsl-token-inject-block": "1.1.0", + "glsl-token-string": "1.0.1", + "glsl-tokenizer": "2.1.5" + } + }, + "glsl-resolve": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/glsl-resolve/-/glsl-resolve-0.0.1.tgz", + "integrity": "sha1-iUvvc5ENeSyBtRQxgANdCnivdtM=", + "dev": true, + "requires": { + "resolve": "0.6.3", + "xtend": "2.2.0" + }, + "dependencies": { + "resolve": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.6.3.tgz", + "integrity": "sha1-3ZV5gufnNt699TtYpN2RdUV13UY=", + "dev": true + } + } + }, + "glsl-token-assignments": { + "version": "2.0.2", + "resolved": "http://registry.npmjs.org/glsl-token-assignments/-/glsl-token-assignments-2.0.2.tgz", + "integrity": "sha1-pdgqt4SZwuimuDy2lJXm5mXOAZ8=", + "dev": true + }, + "glsl-token-defines": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/glsl-token-defines/-/glsl-token-defines-1.0.0.tgz", + "integrity": "sha1-y4kqqVmTYjFyhHDU90AySJaX+p0=", + "dev": true, + "requires": { + "glsl-tokenizer": "2.1.5" + } + }, + "glsl-token-depth": { + "version": "1.1.2", + "resolved": "http://registry.npmjs.org/glsl-token-depth/-/glsl-token-depth-1.1.2.tgz", + "integrity": "sha1-I8XjDuK9JViEtKKLyFC495HpXYQ=", + "dev": true + }, + "glsl-token-descope": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/glsl-token-descope/-/glsl-token-descope-1.0.2.tgz", + "integrity": "sha1-D8kKsyYYa4L1l7LnfcniHvzTIHY=", + "dev": true, + "requires": { + "glsl-token-assignments": "2.0.2", + "glsl-token-depth": "1.1.2", + "glsl-token-properties": "1.0.1", + "glsl-token-scope": "1.1.2" + } + }, + "glsl-token-inject-block": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/glsl-token-inject-block/-/glsl-token-inject-block-1.1.0.tgz", + "integrity": "sha1-4QFfWYDBCRgkraomJfHf3ovQADQ=", + "dev": true + }, + "glsl-token-properties": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/glsl-token-properties/-/glsl-token-properties-1.0.1.tgz", + "integrity": "sha1-SD3D2Dnw1LXGFx0VkfJJvlPCip4=", + "dev": true + }, + "glsl-token-scope": { + "version": "1.1.2", + "resolved": "http://registry.npmjs.org/glsl-token-scope/-/glsl-token-scope-1.1.2.tgz", + "integrity": "sha1-oXKOeN8kRE+cuT/RjvD3VQOmQ7E=", + "dev": true + }, + "glsl-token-string": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/glsl-token-string/-/glsl-token-string-1.0.1.tgz", + "integrity": "sha1-WUQdL4V958NEnJRWZgIezjWOSOw=", + "dev": true + }, + "glsl-token-whitespace-trim": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/glsl-token-whitespace-trim/-/glsl-token-whitespace-trim-1.0.0.tgz", + "integrity": "sha1-RtHf6Yx1vX1QTAXX0RsbPpzJOxA=", + "dev": true + }, + "glsl-tokenizer": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/glsl-tokenizer/-/glsl-tokenizer-2.1.5.tgz", + "integrity": "sha512-XSZEJ/i4dmz3Pmbnpsy3cKh7cotvFlBiZnDOwnj/05EwNp2XrhQ4XKJxT7/pDt4kp4YcpRSKz8eTV7S+mwV6MA==", + "dev": true, + "requires": { + "through2": "0.6.5" + } + }, + "glslify-bundle": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glslify-bundle/-/glslify-bundle-5.1.1.tgz", + "integrity": "sha512-plaAOQPv62M1r3OsWf2UbjN0hUYAB7Aph5bfH58VxJZJhloRNbxOL9tl/7H71K7OLJoSJ2ZqWOKk3ttQ6wy24A==", + "dev": true, + "requires": { + "glsl-inject-defines": "1.0.3", + "glsl-token-defines": "1.0.0", + "glsl-token-depth": "1.1.2", + "glsl-token-descope": "1.0.2", + "glsl-token-scope": "1.1.2", + "glsl-token-string": "1.0.1", + "glsl-token-whitespace-trim": "1.0.0", + "glsl-tokenizer": "2.1.5", + "murmurhash-js": "1.0.0", + "shallow-copy": "0.0.1" + } + }, + "glslify-deps": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/glslify-deps/-/glslify-deps-1.3.1.tgz", + "integrity": "sha512-Ogm179MCazwIRyEqs3g3EOY4Y3XIAa0yl8J5RE9rJC6QH1w8weVOp2RZu0mvnYy/2xIas1w166YR2eZdDkWQxg==", + "dev": true, + "requires": { + "@choojs/findup": "0.2.1", + "events": "1.1.1", + "glsl-resolve": "0.0.1", + "glsl-tokenizer": "2.1.5", + "graceful-fs": "4.1.15", + "inherits": "2.0.3", + "map-limit": "0.0.1", + "resolve": "1.8.1" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "map-limit": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/map-limit/-/map-limit-0.0.1.tgz", + "integrity": "sha1-63lhAxwPDo0AG/LVb6toXViCLzg=", + "dev": true, + "requires": { + "once": "1.3.3" + } + }, + "murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E=", + "dev": true + }, + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "1.0.6" + } + }, + "shallow-copy": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", + "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "svgpath": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/svgpath/-/svgpath-2.2.1.tgz", + "integrity": "sha1-CDS7Z8iadkcrK9BswQH6e1F7Iiw=" + }, + "through2": { + "version": "0.6.5", + "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } + }, + "typescript": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz", + "integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz", + "integrity": "sha1-7vax8ZjByN6vrYsXZaBNrUoBxak=", + "dev": true + } + } +} diff --git a/demo2/package.json b/demo2/package.json new file mode 100644 index 00000000..a3dae0f9 --- /dev/null +++ b/demo2/package.json @@ -0,0 +1,11 @@ +{ + "devDependencies": { + "glslify-bundle": "^5.1.1", + "glslify-deps": "^1.3.1", + "typescript": "^3.1.6" + }, + "dependencies": { + "@types/webgl2": "0.0.4", + "svgpath": "^2.2.1" + } +} diff --git a/demo2/pathfinder.css b/demo2/pathfinder.css index b6e56d9b..0baae6b5 100644 --- a/demo2/pathfinder.css +++ b/demo2/pathfinder.css @@ -1,3 +1,7 @@ +html, body { + background: #e0e0e0; +} + .tile { position: absolute; top: 0; diff --git a/demo2/pathfinder.ts b/demo2/pathfinder.ts index f61df844..f943e68a 100644 --- a/demo2/pathfinder.ts +++ b/demo2/pathfinder.ts @@ -8,14 +8,23 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +import COVER_VERTEX_SHADER_SOURCE from "./cover.vs.glsl"; +import COVER_FRAGMENT_SHADER_SOURCE from "./cover.fs.glsl"; import SVG from "../resources/svg/Ghostscript_Tiger.svg"; const SVGPath = require('svgpath'); +const SVG_NS: string = "http://www.w3.org/2000/svg"; + const TILE_SIZE: number = 16.0; const GLOBAL_OFFSET: Point2D = {x: 400.0, y: 200.0}; -const SVG_NS: string = "http://www.w3.org/2000/svg"; +const QUAD_VERTEX_POSITIONS: Uint8Array = new Uint8Array([ + 0, 0, + 1, 0, + 0, 1, + 1, 1, +]); type Point2D = {x: number, y: number}; type Size2D = {width: number, height: number}; @@ -27,13 +36,109 @@ type Edge = 'left' | 'top' | 'right' | 'bottom'; type SVGPath = any; class App { + private canvas: HTMLCanvasElement; private svg: XMLDocument; + private gl: WebGL2RenderingContext; + private coverProgram: Program<'FramebufferSize' | 'TileSize', 'TessCoord' | 'TileOrigin'>; + private quadVertexBuffer: WebGLBuffer; + private coverVertexBuffer: WebGLBuffer; + private coverVertexArray: WebGLVertexArrayObject; + constructor(svg: XMLDocument) { + this.canvas = staticCast(document.getElementById('canvas'), HTMLCanvasElement); this.svg = svg; + + const gl = unwrapNull(this.canvas.getContext('webgl2')); + this.gl = gl; + + const coverProgram = new Program(gl, + COVER_VERTEX_SHADER_SOURCE, + COVER_FRAGMENT_SHADER_SOURCE, + ['FramebufferSize', 'TileSize'], + ['TessCoord', 'TileOrigin']); + this.coverProgram = coverProgram; + + this.quadVertexBuffer = unwrapNull(gl.createBuffer()); + gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTEX_POSITIONS, gl.STATIC_DRAW); + + this.coverVertexBuffer = unwrapNull(gl.createBuffer()); + gl.bindBuffer(gl.ARRAY_BUFFER, this.coverVertexBuffer); + + // Initialize cover VAO. + this.coverVertexArray = unwrapNull(gl.createVertexArray()); + gl.bindVertexArray(this.coverVertexArray); + gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer); + gl.vertexAttribPointer(coverProgram.attributes.TessCoord, + 2, + gl.UNSIGNED_BYTE, + false, + 0, + 0); + gl.bindBuffer(gl.ARRAY_BUFFER, this.coverVertexBuffer); + gl.vertexAttribPointer(coverProgram.attributes.TileOrigin, 2, gl.SHORT, false, 0, 0); + gl.vertexAttribDivisor(coverProgram.attributes.TileOrigin, 1); + gl.enableVertexAttribArray(coverProgram.attributes.TessCoord); + gl.enableVertexAttribArray(coverProgram.attributes.TileOrigin); + + // TODO(pcwalton) } run(): void { + const gl = this.gl, canvas = this.canvas; + + const tiles = this.createTiles(); + + const coverVertexBufferData = new Int16Array(tiles.length * 2); + for (let tileIndex = 0; tileIndex < tiles.length; tileIndex++) { + coverVertexBufferData[tileIndex * 2 + 0] = Math.floor(tiles[tileIndex].origin.x); + coverVertexBufferData[tileIndex * 2 + 1] = Math.floor(tiles[tileIndex].origin.y); + } + gl.bindBuffer(gl.ARRAY_BUFFER, this.coverVertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, coverVertexBufferData, gl.DYNAMIC_DRAW); + console.log(coverVertexBufferData); + + const framebufferSize = {width: canvas.width, height: canvas.height}; + gl.viewport(0, 0, framebufferSize.width, framebufferSize.height); + gl.clearColor(1.0, 1.0, 1.0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.bindVertexArray(this.coverVertexArray); + gl.useProgram(this.coverProgram.program); + gl.uniform2f(this.coverProgram.uniforms.FramebufferSize, + framebufferSize.width, + framebufferSize.height); + gl.uniform2f(this.coverProgram.uniforms.TileSize, TILE_SIZE, TILE_SIZE); + gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, tiles.length); + + /* + for (const tile of tiles) { + const newSVG = staticCast(document.createElementNS(SVG_NS, 'svg'), SVGElement); + newSVG.setAttribute('class', "tile"); + newSVG.style.left = (GLOBAL_OFFSET.x + tile.origin.x) + "px"; + newSVG.style.top = (GLOBAL_OFFSET.y + tile.origin.y) + "px"; + newSVG.style.width = TILE_SIZE + "px"; + newSVG.style.height = TILE_SIZE + "px"; + + const newPath = document.createElementNS(SVG_NS, 'path'); + newPath.setAttribute('d', + tile.path + .translate(-tile.origin.x, -tile.origin.y) + .toString()); + + let color = "#"; + for (let i = 0; i < 6; i++) + color += Math.floor(Math.random() * 16).toString(16); + newPath.setAttribute('fill', color); + + newSVG.appendChild(newPath); + document.body.appendChild(newSVG); + } + */ + } + + private createTiles(): Tile[] { const svgElement = unwrapNull(this.svg.documentElement).cloneNode(true); document.body.appendChild(svgElement); @@ -41,18 +146,22 @@ class App { const tiles: Tile[] = []; for (let pathElementIndex = 0; - pathElementIndex < 15; + pathElementIndex < pathElements.length; pathElementIndex++) { const pathElement = pathElements[pathElementIndex]; const path = canonicalizePath(SVGPath(unwrapNull(pathElement.getAttribute('d')))); const boundingRect = this.boundingRectOfPath(path); - //console.log("path " + pathElementIndex, path.toString(), ":", boundingRect); + /*console.log("path " + pathElementIndex, path.toString(), ":", + boundingRect.origin.x, + boundingRect.origin.y, + boundingRect.size.width, + boundingRect.size.height);*/ - let y = boundingRect.origin.y; + let y = boundingRect.origin.y - boundingRect.origin.y % TILE_SIZE; while (true) { - let x = boundingRect.origin.x; + let x = boundingRect.origin.x - boundingRect.origin.x % TILE_SIZE; while (true) { const tileBounds = { origin: {x, y}, @@ -60,7 +169,8 @@ class App { }; const tilePath = this.clipPathToRect(path, tileBounds); - tiles.push(new Tile(pathElementIndex, tilePath, tileBounds.origin)); + if (tilePath.toString().length > 0) + tiles.push(new Tile(pathElementIndex, tilePath, tileBounds.origin)); if (x >= boundingRect.origin.x + boundingRect.size.width) break; @@ -71,32 +181,11 @@ class App { break; y += TILE_SIZE; } - - for (const tile of tiles) { - const newSVG = staticCast(document.createElementNS(SVG_NS, 'svg'), SVGElement); - newSVG.setAttribute('class', "tile"); - newSVG.style.left = (GLOBAL_OFFSET.x + tile.origin.x) + "px"; - newSVG.style.top = (GLOBAL_OFFSET.y + tile.origin.y) + "px"; - newSVG.style.width = TILE_SIZE + "px"; - newSVG.style.height = TILE_SIZE + "px"; - - const newPath = document.createElementNS(SVG_NS, 'path'); - newPath.setAttribute('d', - tile.path - .translate(-tile.origin.x, -tile.origin.y) - .toString()); - - let color = "#"; - for (let i = 0; i < 6; i++) - color += Math.floor(Math.random() * 16).toString(16); - newPath.setAttribute('fill', color); - - newSVG.appendChild(newPath); - document.body.appendChild(newSVG); - } } document.body.removeChild(svgElement); + + return tiles; } private clipPathToRect(path: SVGPath, tileBounds: Rect): SVGPath { @@ -240,6 +329,60 @@ class Tile { } } +class Program { + program: WebGLProgram; + uniforms: {[key in U]: WebGLUniformLocation}; + attributes: {[key in A]: number}; + + private vertexShader: WebGLShader; + private fragmentShader: WebGLShader; + + constructor(gl: WebGL2RenderingContext, + vertexShaderSource: string, + fragmentShaderSource: string, + uniformNames: U[], + attributeNames: A[]) { + this.vertexShader = unwrapNull(gl.createShader(gl.VERTEX_SHADER)); + gl.shaderSource(this.vertexShader, vertexShaderSource); + gl.compileShader(this.vertexShader); + if (!gl.getShaderParameter(this.vertexShader, gl.COMPILE_STATUS)) { + console.error(gl.getShaderInfoLog(this.vertexShader)); + throw new Error("Vertex shader compilation failed!"); + } + + this.fragmentShader = unwrapNull(gl.createShader(gl.FRAGMENT_SHADER)); + gl.shaderSource(this.fragmentShader, fragmentShaderSource); + gl.compileShader(this.fragmentShader); + if (!gl.getShaderParameter(this.fragmentShader, gl.COMPILE_STATUS)) { + console.error(gl.getShaderInfoLog(this.fragmentShader)); + throw new Error("Fragment shader compilation failed!"); + } + + this.program = unwrapNull(gl.createProgram()); + gl.attachShader(this.program, this.vertexShader); + gl.attachShader(this.program, this.fragmentShader); + gl.linkProgram(this.program); + if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) { + console.error(gl.getProgramInfoLog(this.program)); + throw new Error("Program linking failed!"); + } + + const uniforms: {[key in U]?: WebGLUniformLocation} = {}; + for (const uniformName of uniformNames) { + uniforms[uniformName] = unwrapNull(gl.getUniformLocation(this.program, + "u" + uniformName)); + } + this.uniforms = uniforms as {[key in U]: WebGLUniformLocation}; + + const attributes: {[key in A]?: number} = {}; + for (const attributeName of attributeNames) { + attributes[attributeName] = unwrapNull(gl.getAttribLocation(this.program, + "a" + attributeName)); + } + this.attributes = attributes as {[key in A]: number}; + } +} + function canonicalizePath(path: SVGPath): SVGPath { return path.abs().iterate((segment: string[], index: number, x: number, y: number) => { if (segment[0] === 'H')