diff --git a/demo/.gitignore b/demo/.gitignore deleted file mode 100644 index a56a7ef4..00000000 --- a/demo/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules - diff --git a/demo/client/build/rustdoc-webpack-plugin/index.js b/demo/client/build/rustdoc-webpack-plugin/index.js deleted file mode 100644 index a34ed592..00000000 --- a/demo/client/build/rustdoc-webpack-plugin/index.js +++ /dev/null @@ -1,46 +0,0 @@ -const {spawn} = require('child_process'); -const process = require('process'); - -class RustdocPlugin { - constructor(options) { - this.options = Object.assign({directories: [], flags: {}}, options); - } - - apply(compiler) { - let rustdocFlags = []; - for (let key in this.options.flags) { - if (this.options.flags.hasOwnProperty(key)) - rustdocFlags.push("--" + key + "=" + this.options.flags[key]); - } - rustdocFlags = rustdocFlags.join(" "); - - compiler.plugin('after-compile', (compilation, done) => { - let directoriesLeft = this.options.directories.length; - for (const directory of this.options.directories) { - console.log("Building documentation for `" + directory + "`..."); - const cargo = spawn("cargo", ["doc", "--no-deps"], { - cwd: directory, - env: Object.assign({RUSTDOCFLAGS: rustdocFlags}, process.env), - }); - cargo.stdout.setEncoding('utf8'); - cargo.stderr.setEncoding('utf8'); - cargo.stdout.on('data', data => console.log(data)); - cargo.stderr.on('data', data => console.log(data)); - cargo.on('close', code => { - if (code !== 0) { - const message = "Failed to build documentation for `" + directory + "`!"; - console.error(message); - throw new Error(message); - } - - console.log("Built documentation for `" + directory + "`."); - directoriesLeft--; - if (directoriesLeft === 0) - done(); - }); - } - }); - } -} - -module.exports = RustdocPlugin; diff --git a/demo/client/build/rustdoc-webpack-plugin/package.json b/demo/client/build/rustdoc-webpack-plugin/package.json deleted file mode 100644 index ee391a28..00000000 --- a/demo/client/build/rustdoc-webpack-plugin/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "rustdoc-webpack-plugin", - "version": "0.1.0", - "description": "Webpack plugin for building Rust package documentation", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Patrick Walton", - "license": "MIT OR Apache-2.0" -} diff --git a/demo/client/css/pathfinder-doc.css b/demo/client/css/pathfinder-doc.css deleted file mode 100644 index 9c83cf19..00000000 --- a/demo/client/css/pathfinder-doc.css +++ /dev/null @@ -1,43 +0,0 @@ -body { - padding: 0 !important; -} -h1, -h2, -h3 -h4, -.sidebar, -a.source, -.search-input, -.content table :not(code) > a, -.collapse-toggle, -ul.item-list > li > .out-of-band { - font-family: inherit !important; -} -.content { - padding: 0 15px 0 0 !important; -} -nav.navbar { - max-width: inherit !important; - border: none !important; - margin: 0 !important; -} -nav.sidebar { - top: auto !important; -} -img { - max-width: inherit !important; -} -h4 > span.invisible, -.structfield > span.invisible, -.variant > span.invisible { - visibility: visible !important; -} - -/* - * Hide `generate_gamma_lut` and `pathfinder_server`. - * FIXME(pcwalton): Is there a better way to do this? - */ -.sidebar-elems > .block.crate > ul > li:first-child, -.sidebar-elems > .block.crate > ul > li:last-child { - display: none; -} diff --git a/demo/client/css/pathfinder.css b/demo/client/css/pathfinder.css deleted file mode 100644 index 751b962a..00000000 --- a/demo/client/css/pathfinder.css +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Fonts - */ - -@font-face { - font-family: "Inter UI"; - font-weight: 400; - src: url("/woff2/inter-ui/Inter-UI-Regular.woff2") format('woff2'); -} - -@font-face { - font-family: "Inter UI"; - font-weight: 700; - src: url("/woff2/inter-ui/Inter-UI-Bold.woff2") format('woff2'); -} - -@font-face { - font-family: "Inter UI"; - font-weight: 400; - font-style: italic; - src: url("/woff2/inter-ui/Inter-UI-RegularItalic.woff2") format('woff2'); -} - -@font-face { - font-family: "Inter UI"; - font-weight: 700; - font-style: italic; - src: url("/woff2/inter-ui/Inter-UI-BoldItalic.woff2") format('woff2'); -} - -@font-face { - font-family: "Material Icons"; - font-style: normal; - font-weight: 400; - src: local('Material Icons'), - local('MaterialIcons-Regular'), - url("/woff2/material-icons/MaterialIcons-Regular.woff2") format('woff2'); -} - -/* - * Core - */ - -body { - font-family: "Inter UI", sans-serif; -} -body.pf-unscrollable { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; -} -nav { - -moz-user-select: none; - user-select: none; -} -#pf-load-font-button-label, -#pf-load-svg-button-label { - left: 1em; - margin: 0; -} -#pf-settings-container, #pf-rotate-slider-container { - position: absolute; - right: 1rem; - bottom: 2rem; - pointer-events: none; - text-align: right; -} -#pf-toolbar { - margin-right: 1rem; -} -#pf-settings, #pf-rotate-slider-card { - text-align: initial; - user-select: none; - -moz-user-select: none; - opacity: 1.0; - transition: opacity 300ms, transform 300ms, visibility 300ms; - transform: translateY(0); -} -#pf-settings:not(.pf-invisible), #pf-rotate-slider-card:not(.pf-invisible) { - pointer-events: auto; -} -.pf-toolbar-button { - pointer-events: auto; -} -#pf-test-pane { - pointer-events: auto; -} -.pf-toolbar-button.btn-outline-secondary:not(:hover) { - background: rgba(255, 255, 255, 0.75); -} -#pf-file-select { - position: absolute; - visibility: hidden; -} -#pf-settings.pf-invisible, #pf-rotate-slider-card.pf-invisible { - opacity: 0.0; - transform: translateY(1em); - visibility: hidden; -} -button > svg { - width: 1.25em; - display: block; -} -button > svg path { - fill: currentColor; -} -.pf-pointer-events-none { - pointer-events: none; -} -.pf-pointer-events-auto { - pointer-events: auto; -} -#pf-rendering-options-group { - right: 1em; -} -.pf-maximized-canvas { - display: block; - position: absolute; - touch-action: none; -} -#pf-canvas.pf-draggable { - cursor: grab; - cursor: -webkit-grab; -} -#pf-canvas.pf-draggable.pf-grabbing { - cursor: grabbing; - cursor: -webkit-grabbing; -} -#pf-fps-label { - color: white; - background: rgba(0, 0, 0, 0.75); - width: 17em; -} -#pf-svg, #pf-svg * { - visibility: hidden !important; -} -#pf-outer-container { - display: flex; - flex-direction: column; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; -} -#pf-inner-container { - overflow: scroll; -} -.pf-spinner { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; -} -.pf-spinner-hidden { - display: none !important; -} -.pf-display-none { - display: none !important; -} -#pf-benchmark-results-modal .modal-body { - overflow: scroll; - max-height: 75vh; -} -.pf-benchmark-results-global { - padding: 0.75rem; -} -.pf-benchmark-results-label { - font-weight: bold; -} -.pf-material-icons { - font-family: "Material Icons"; - display: block; - font-size: 18px; - margin: -3px 0 -3px; -} -#pf-rotate-slider { - margin: 2px 0 0; -} -label.pf-disabled { - color: gray; -} -.btn-group input[type="radio"] { - -moz-appearance: none; -} - -/* - * Arrow - * - * http://www.cssarrowplease.com/ - */ - -#pf-settings > .pf-arrow-box:after, #pf-settings .pf-arrow-box:before { - right: 22px; -} - -#pf-rotate-slider-card > .pf-arrow-box:after, #pf-rotate-slider-card .pf-arrow-box:before { - right: 166px; -} - -.pf-arrow-box:after, .pf-arrow-box:before { - top: 100%; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; -} -.pf-arrow-box:after { - border-color: rgba(0, 0, 0, 0); - border-top-color: white; - border-width: 14px; - margin-right: -14px; -} -.pf-arrow-box:before { - border-color: rgba(0, 0, 0, 0); - border-top-color: rgba(0, 0, 0, 0.125); - border-width: 15px; - margin-right: -15px; -} - -#pf-masthead-logo { - width: 30vw; -} - -.github-corner { - display: block; - position: absolute; - right: 0; - top: 0; - height: 100%; - text-align: right; -} -.github-corner > svg { - fill: #151513; - color: #fff; - height: 100%; - width: auto; -} -.github-corner:hover .octo-arm { - animation: octocat-wave 560ms ease-in-out; -} -@keyframes octocat-wave{ - 0%, 100% { - transform: rotate(0); - } - 20%, 60% { - transform: rotate(-25deg); - } - 40%, 80% { - transform: rotate(10deg); - } -} -@media (max-width: 500px) { - .github-corner:hover .octo-arm { - animation: none; - } - .github-corner .octo-arm { - animation: octocat-wave 560ms ease-in-out; - } -} - -/* - * Spinner - * - * http://tobiasahlin.com/spinkit/ - */ - -.sk-fading-circle { - margin: auto; - width: 40px; - height: 40px; -} - -.sk-fading-circle .sk-circle { - width: 100%; - height: 100%; - position: absolute; - left: 0; - top: 0; -} - -.sk-fading-circle .sk-circle:before { - content: ''; - display: block; - margin: 0 auto; - width: 15%; - height: 15%; - background-color: #333; - border-radius: 100%; - -webkit-animation: sk-circleFadeDelay 1.2s infinite ease-in-out both; - animation: sk-circleFadeDelay 1.2s infinite ease-in-out both; -} -.sk-fading-circle .sk-circle2 { - -webkit-transform: rotate(30deg); - -ms-transform: rotate(30deg); - transform: rotate(30deg); -} -.sk-fading-circle .sk-circle3 { - -webkit-transform: rotate(60deg); - -ms-transform: rotate(60deg); - transform: rotate(60deg); -} -.sk-fading-circle .sk-circle4 { - -webkit-transform: rotate(90deg); - -ms-transform: rotate(90deg); - transform: rotate(90deg); -} -.sk-fading-circle .sk-circle5 { - -webkit-transform: rotate(120deg); - -ms-transform: rotate(120deg); - transform: rotate(120deg); -} -.sk-fading-circle .sk-circle6 { - -webkit-transform: rotate(150deg); - -ms-transform: rotate(150deg); - transform: rotate(150deg); -} -.sk-fading-circle .sk-circle7 { - -webkit-transform: rotate(180deg); - -ms-transform: rotate(180deg); - transform: rotate(180deg); -} -.sk-fading-circle .sk-circle8 { - -webkit-transform: rotate(210deg); - -ms-transform: rotate(210deg); - transform: rotate(210deg); -} -.sk-fading-circle .sk-circle9 { - -webkit-transform: rotate(240deg); - -ms-transform: rotate(240deg); - transform: rotate(240deg); -} -.sk-fading-circle .sk-circle10 { - -webkit-transform: rotate(270deg); - -ms-transform: rotate(270deg); - transform: rotate(270deg); -} -.sk-fading-circle .sk-circle11 { - -webkit-transform: rotate(300deg); - -ms-transform: rotate(300deg); - transform: rotate(300deg); -} -.sk-fading-circle .sk-circle12 { - -webkit-transform: rotate(330deg); - -ms-transform: rotate(330deg); - transform: rotate(330deg); -} -.sk-fading-circle .sk-circle2:before { - -webkit-animation-delay: -1.1s; - animation-delay: -1.1s; -} -.sk-fading-circle .sk-circle3:before { - -webkit-animation-delay: -1s; - animation-delay: -1s; -} -.sk-fading-circle .sk-circle4:before { - -webkit-animation-delay: -0.9s; - animation-delay: -0.9s; -} -.sk-fading-circle .sk-circle5:before { - -webkit-animation-delay: -0.8s; - animation-delay: -0.8s; -} -.sk-fading-circle .sk-circle6:before { - -webkit-animation-delay: -0.7s; - animation-delay: -0.7s; -} -.sk-fading-circle .sk-circle7:before { - -webkit-animation-delay: -0.6s; - animation-delay: -0.6s; -} -.sk-fading-circle .sk-circle8:before { - -webkit-animation-delay: -0.5s; - animation-delay: -0.5s; -} -.sk-fading-circle .sk-circle9:before { - -webkit-animation-delay: -0.4s; - animation-delay: -0.4s; -} -.sk-fading-circle .sk-circle10:before { - -webkit-animation-delay: -0.3s; - animation-delay: -0.3s; -} -.sk-fading-circle .sk-circle11:before { - -webkit-animation-delay: -0.2s; - animation-delay: -0.2s; -} -.sk-fading-circle .sk-circle12:before { - -webkit-animation-delay: -0.1s; - animation-delay: -0.1s; -} - -@-webkit-keyframes sk-circleFadeDelay { - 0%, 39%, 100% { opacity: 0; } - 40% { opacity: 1; } -} - -@keyframes sk-circleFadeDelay { - 0%, 39%, 100% { opacity: 0; } - 40% { opacity: 1; } -} diff --git a/demo/client/html/3d-demo.html.hbs b/demo/client/html/3d-demo.html.hbs deleted file mode 100644 index 7966ff65..00000000 --- a/demo/client/html/3d-demo.html.hbs +++ /dev/null @@ -1,58 +0,0 @@ - - - - 3D Demo — Pathfinder - - {{>partials/header.html}} - - - - {{>partials/navbar.html isDemo=true}} - -
- -
- -
-
-
- -
-
- - -
-
-
-
-
-
- - -
-
- {{>partials/spinner.html}} - - diff --git a/demo/client/html/benchmark.html.hbs b/demo/client/html/benchmark.html.hbs deleted file mode 100644 index bd5dab81..00000000 --- a/demo/client/html/benchmark.html.hbs +++ /dev/null @@ -1,122 +0,0 @@ - - - - Benchmark — Pathfinder - - {{>partials/header.html}} - - - - {{>partials/navbar.html isTool=true}} - - - - - - - diff --git a/demo/client/html/doc-before-content.html.hbs b/demo/client/html/doc-before-content.html.hbs deleted file mode 100644 index b79d2080..00000000 --- a/demo/client/html/doc-before-content.html.hbs +++ /dev/null @@ -1 +0,0 @@ -{{>partials/navbar.html isDoc=true}} diff --git a/demo/client/html/doc-header.html.hbs b/demo/client/html/doc-header.html.hbs deleted file mode 100644 index bc514e4d..00000000 --- a/demo/client/html/doc-header.html.hbs +++ /dev/null @@ -1,2 +0,0 @@ -{{>partials/header.html}} - diff --git a/demo/client/html/index.html.hbs b/demo/client/html/index.html.hbs deleted file mode 100644 index ad870752..00000000 --- a/demo/client/html/index.html.hbs +++ /dev/null @@ -1,23 +0,0 @@ - - - - Pathfinder - - {{>partials/header.html}} - - - {{>partials/navbar.html}} -
-
-
-
-

Pathfinder

-

- A fast, high-quality, open source text and vector graphics renderer for - GPUs. -

-
-
-
- - diff --git a/demo/client/html/mesh-debugger.html.hbs b/demo/client/html/mesh-debugger.html.hbs deleted file mode 100644 index cc32bd5d..00000000 --- a/demo/client/html/mesh-debugger.html.hbs +++ /dev/null @@ -1,73 +0,0 @@ - - - - Mesh Debugger — Pathfinder - - {{>partials/header.html}} - - - - {{>partials/navbar.html isTool=true}} - - -
-
- -
-
- - {{>partials/spinner.html}} - - diff --git a/demo/client/html/partials/header.html.hbs b/demo/client/html/partials/header.html.hbs deleted file mode 100644 index e757f451..00000000 --- a/demo/client/html/partials/header.html.hbs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/demo/client/html/partials/navbar.html.hbs b/demo/client/html/partials/navbar.html.hbs deleted file mode 100644 index c7a10051..00000000 --- a/demo/client/html/partials/navbar.html.hbs +++ /dev/null @@ -1,42 +0,0 @@ - diff --git a/demo/client/html/partials/rotate.html.hbs b/demo/client/html/partials/rotate.html.hbs deleted file mode 100644 index 2b09ec10..00000000 --- a/demo/client/html/partials/rotate.html.hbs +++ /dev/null @@ -1,18 +0,0 @@ -
-
-
-
- -
- -
-
-
-
- diff --git a/demo/client/html/partials/spinner.html.hbs b/demo/client/html/partials/spinner.html.hbs deleted file mode 100644 index 6ea99543..00000000 --- a/demo/client/html/partials/spinner.html.hbs +++ /dev/null @@ -1,15 +0,0 @@ - -
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/demo/client/html/partials/switch.html.hbs b/demo/client/html/partials/switch.html.hbs deleted file mode 100644 index 9dbd24ec..00000000 --- a/demo/client/html/partials/switch.html.hbs +++ /dev/null @@ -1,9 +0,0 @@ - -
- - -
diff --git a/demo/client/html/reference-test.html.hbs b/demo/client/html/reference-test.html.hbs deleted file mode 100644 index b1a444d2..00000000 --- a/demo/client/html/reference-test.html.hbs +++ /dev/null @@ -1,188 +0,0 @@ - - - - Reference Test — Pathfinder - - {{>partials/header.html}} - - - - -
-
- {{>partials/navbar.html isTool=true}} -
-
-
-
- -
-
- - - - - - - - - - - - - - - - -
FontChar.SizeAASubpixelReferenceExpected SSIMActual SSIM
-
-
- - - - - - - - - - - - - -
SVGAAReferenceExpected SSIMActual SSIM
-
-
-
-
- - -
-
- -
- -
px
-
-
-
- - -
-
- - -
-
- {{>partials/switch.html id="pf-subpixel-aa" - title="Subpixel AA"}} -
-
- - -
-
- -
-
-
-
-
-
-
- -
-
- - -
-
- -
-
-
-
-
-
-
-
- -
-
- -
-
- -
-
-
-
-
- - diff --git a/demo/client/html/svg-demo.html.hbs b/demo/client/html/svg-demo.html.hbs deleted file mode 100644 index 4963ce79..00000000 --- a/demo/client/html/svg-demo.html.hbs +++ /dev/null @@ -1,78 +0,0 @@ - - - - SVG Demo — Pathfinder - - {{>partials/header.html}} - - - - {{>partials/navbar.html isDemo=true}} - - -
- -
-
- - -
- {{>partials/rotate.html}} - - -
-
-
- -
-
- - - -
-
- - -
-
-
-
-
-
- -
-
- {{>partials/spinner.html}} - - diff --git a/demo/client/html/text-demo.html.hbs b/demo/client/html/text-demo.html.hbs deleted file mode 100644 index e1ce6155..00000000 --- a/demo/client/html/text-demo.html.hbs +++ /dev/null @@ -1,133 +0,0 @@ - - - - Text Demo — Pathfinder - - {{>partials/header.html}} - - - - {{>partials/navbar.html isDemo=true}} - -
- -
-
- - -
- {{>partials/rotate.html}} - - -
-
-
- -
-
- - - -
-
- - -
-
- - -
-
- - -
-
- {{>partials/switch.html id="pf-gamma-correction" - title="Gamma Correction"}} -
-
- {{>partials/switch.html id="pf-stem-darkening" - title="Stem Darkening"}} -
-
- - -
-
-
-
-
-
- -
-
- - {{>partials/spinner.html}} - - diff --git a/demo/client/package.json b/demo/client/package.json deleted file mode 100644 index 00f2d33b..00000000 --- a/demo/client/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "pathfinder-demo", - "version": "0.1.0", - "description": "Demo for Pathfinder 2", - "scripts": { - "build": "webpack", - "build-watch": "webpack --watch", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Patrick Walton ", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@types/base64-js": "^1.2.5", - "@types/gl-matrix": "^2.4.0", - "@types/lodash": "^4.14.104", - "@types/node": "^8.9.4", - "@types/opentype.js": "0.0.0", - "@types/papaparse": "^4.1.33", - "@types/webgl-ext": "0.0.29", - "base64-js": "^1.2.3", - "bootstrap": "^4.0.0", - "gl-matrix": "^2.4.0", - "handlebars": "^4.0.11", - "handlebars-loader": "^1.6.0", - "handlebars-webpack-plugin": "^1.3.2", - "html-loader": "^0.5.5", - "image-ssim": "^0.2.0", - "jquery": "^3.3.1", - "lodash": "^4.17.5", - "opentype.js": "^0.7.3", - "papaparse": "^4.3.7", - "parse-color": "^1.0.0", - "path-data-polyfill.js": "^1.0.2", - "popper.js": "^1.13.0", - "rustdoc-webpack-plugin": "file:./build/rustdoc-webpack-plugin", - "ts-loader": "^2.3.7", - "typescript": "^2.7.2" - }, - "devDependencies": { - "tslint": "^5.9.1", - "tslint-loader": "^3.5.3", - "webpack": "^3.11.0" - } -} diff --git a/demo/client/src/3d-demo.ts b/demo/client/src/3d-demo.ts deleted file mode 100644 index 684c72f0..00000000 --- a/demo/client/src/3d-demo.ts +++ /dev/null @@ -1,1143 +0,0 @@ -// pathfinder/client/src/3d-demo.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; -import * as _ from "lodash"; -import * as opentype from "opentype.js"; - -import {mat4, vec2} from "gl-matrix"; -import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; -import {SubpixelAAType} from "./aa-strategy"; -import {DemoAppController} from "./app-controller"; -import {Atlas, ATLAS_SIZE, AtlasGlyph, GlyphKey} from './atlas'; -import PathfinderBufferTexture from "./buffer-texture"; -import {CameraView, PerspectiveCamera} from "./camera"; -import {UniformMap} from './gl-utils'; -import {PathfinderMeshPack, PathfinderPackedMeshes} from "./meshes"; -import {PathTransformBuffers, Renderer} from './renderer'; -import {ShaderMap, ShaderProgramSource} from "./shader-loader"; -import SSAAStrategy from "./ssaa-strategy"; -import {BUILTIN_FONT_URI, ExpandedMeshData} from "./text"; -import {calculatePixelRectForGlyph, GlyphStore, Hint, PathfinderFont} from "./text"; -import {SimpleTextLayout, TextFrame, TextRun, UnitMetrics} from "./text"; -import {TextRenderContext, TextRenderer} from './text-renderer'; -import {assert, FLOAT32_SIZE, panic, PathfinderError, Range, UINT16_SIZE} from "./utils"; -import {unwrapNull} from "./utils"; -import {DemoView, Timings} from "./view"; - -const TEXT_AVAILABLE_WIDTH: number = 150000; -const TEXT_PADDING: number = 2000; - -const TEXT_SCALE: glmatrix.vec3 = glmatrix.vec3.fromValues(1.0 / 200.0, 1.0 / 200.0, 1.0 / 200.0); - -const TEXT_DATA_URI: string = "/data/mozmonument.json"; - -const FONT: string = 'open-sans'; - -const PIXELS_PER_UNIT: number = 1.0; - -const FOV: number = 45.0; -const NEAR_CLIP_PLANE: number = 0.1; -const FAR_CLIP_PLANE: number = 100000.0; - -const ATLAS_FONT_SIZE: number = 48; - -const MAX_DISTANCE: number = 200.0; - -const TEXT_TRANSLATION: number[] = [ - -TEXT_AVAILABLE_WIDTH * 0.5, - 0.0, - TEXT_AVAILABLE_WIDTH * 0.5 + TEXT_PADDING, -]; - -const MONUMENT_TRANSLATION: glmatrix.vec3 = glmatrix.vec3.fromValues(0.0, -690.0, 0.0); -const MONUMENT_SCALE: glmatrix.vec3 = - glmatrix.vec3.fromValues((TEXT_AVAILABLE_WIDTH * 0.5 + TEXT_PADDING) * TEXT_SCALE[0], - 700.0, - (TEXT_AVAILABLE_WIDTH * 0.5 + TEXT_PADDING) * TEXT_SCALE[2]); - -const TEXT_COLOR: Uint8Array = new Uint8Array([0xf2, 0xf8, 0xf8, 0xff]); - -const AMBIENT_COLOR: glmatrix.vec3 = glmatrix.vec3.clone([0.063, 0.063, 0.063]); -const DIFFUSE_COLOR: glmatrix.vec3 = glmatrix.vec3.clone([0.356, 0.264, 0.136]); -const SPECULAR_COLOR: glmatrix.vec3 = glmatrix.vec3.clone([0.490, 0.420, 0.324]); - -const MONUMENT_SHININESS: number = 32.0; - -const CUBE_VERTEX_POSITIONS: Float32Array = new Float32Array([ - -1.0, -1.0, -1.0, // 0 - 1.0, -1.0, -1.0, // 1 - -1.0, -1.0, 1.0, // 2 - 1.0, -1.0, 1.0, // 3 - -1.0, 1.0, -1.0, // 4 - 1.0, 1.0, -1.0, // 5 - -1.0, 1.0, 1.0, // 6 - 1.0, 1.0, 1.0, // 7 -]); - -const CUBE_INDICES: Uint16Array = new Uint16Array([ - 0, 1, 2, 2, 1, 3, // bottom - 0, 5, 1, 0, 4, 5, // front - 2, 4, 0, 2, 6, 4, // left - 3, 5, 1, 3, 7, 5, // right - 2, 7, 3, 2, 6, 7, // back - 4, 5, 6, 6, 5, 7, // top -]); - -const MONUMENT_NORMALS: glmatrix.vec4[] = [ - glmatrix.vec4.clone([ 0.0, -1.0, 0.0, 1.0]), - glmatrix.vec4.clone([ 0.0, 0.0, -1.0, 1.0]), - glmatrix.vec4.clone([-1.0, 0.0, 0.0, 1.0]), - glmatrix.vec4.clone([ 1.0, 0.0, 0.0, 1.0]), - glmatrix.vec4.clone([ 0.0, 0.0, 1.0, 1.0]), - glmatrix.vec4.clone([ 0.0, 1.0, 0.0, 1.0]), -]; - -const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { - none: NoAAStrategy, - ssaa: SSAAStrategy, -}; - -interface AntialiasingStrategyTable { - none: typeof NoAAStrategy; - ssaa: typeof SSAAStrategy; -} - -interface TextLine { - names: string[]; -} - -interface MonumentSide { - lines: TextLine[]; -} - -interface MeshDescriptor { - glyphID: number; - textFrameIndex: number; - positions: glmatrix.vec2[]; -} - -class ThreeDController extends DemoAppController { - font!: PathfinderFont; - textFrames!: TextFrame[]; - glyphStore!: GlyphStore; - meshDescriptors!: MeshDescriptor[]; - - atlasGlyphs!: AtlasGlyph[]; - atlas!: Atlas; - - baseMeshes!: PathfinderMeshPack; - private expandedMeshes!: PathfinderPackedMeshes[]; - - private monumentPromise!: Promise; - - start() { - super.start(); - - this.atlas = new Atlas; - - this.monumentPromise = window.fetch(TEXT_DATA_URI) - .then(response => response.json()) - .then(textData => this.parseTextData(textData)); - - this.loadInitialFile(this.builtinFileURI); - } - - protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void { - this.font = new PathfinderFont(fileData, builtinName); - this.monumentPromise.then(monument => this.layoutMonument(fileData, monument)); - } - - protected createView(areaLUT: HTMLImageElement, - gammaLUT: HTMLImageElement, - commonShaderSource: string, - shaderSources: ShaderMap): - ThreeDView { - return new ThreeDView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources); - } - - protected get builtinFileURI(): string { - return BUILTIN_FONT_URI; - } - - protected get defaultFile(): string { - return FONT; - } - - private parseTextData(textData: any): MonumentSide[] { - const sides = []; - for (let sideIndex = 0; sideIndex < 4; sideIndex++) - sides[sideIndex] = { upper: { lines: [] }, lower: { lines: [] } }; - - for (const nameData of textData.monument) { - const side = parseInt(nameData.side, 10) - 1; - const row = parseInt(nameData.row, 10) - 1; - const index = parseInt(nameData.number, 10) - 1; - - if (sides[side] == null) - continue; - - const lines: TextLine[] = sides[side][nameData.panel as ('upper' | 'lower')].lines; - if (lines[row] == null) - lines[row] = { names: [] }; - - lines[row].names[index] = nameData.name; - } - - return sides.map(side => ({ lines: side.upper.lines.concat(side.lower.lines) })); - } - - private layoutMonument(fileData: ArrayBuffer, monument: MonumentSide[]) { - this.textFrames = []; - let glyphsNeeded: number[] = []; - - for (const monumentSide of monument) { - const textRuns = []; - for (let lineNumber = 0; lineNumber < monumentSide.lines.length; lineNumber++) { - const line = monumentSide.lines[lineNumber]; - - const lineY = -lineNumber * this.font.opentypeFont.lineHeight(); - const lineGlyphs = line.names.map(name => { - const glyphs = this.font.opentypeFont.stringToGlyphs(name); - const glyphIDs = glyphs.map(glyph => (glyph as any).index); - const width = _.sumBy(glyphs, glyph => glyph.advanceWidth); - return { glyphs: glyphIDs, width: width }; - }); - - const usedSpace = _.sumBy(lineGlyphs, 'width'); - const emptySpace = Math.max(TEXT_AVAILABLE_WIDTH - usedSpace, 0.0); - const spacing = emptySpace / Math.max(lineGlyphs.length - 1, 1); - - let currentX = 0.0; - for (const glyphInfo of lineGlyphs) { - const textRunOrigin = [currentX, lineY]; - const textRun = new TextRun(glyphInfo.glyphs, textRunOrigin, this.font); - textRun.layout(); - textRuns.push(textRun); - currentX += glyphInfo.width + spacing; - } - } - - const textFrame = new TextFrame(textRuns, this.font); - this.textFrames.push(textFrame); - glyphsNeeded.push(...textFrame.allGlyphIDs); - } - - glyphsNeeded.sort((a, b) => a - b); - glyphsNeeded = _.sortedUniq(glyphsNeeded); - - this.glyphStore = new GlyphStore(this.font, glyphsNeeded); - this.glyphStore.partition().then(result => { - // Build the atlas glyphs needed. - this.atlasGlyphs = []; - for (const glyphID of glyphsNeeded) { - const glyphKey = new GlyphKey(glyphID, null); - const glyphStoreIndex = this.glyphStore.indexOfGlyphWithID(glyphID); - if (glyphStoreIndex != null) - this.atlasGlyphs.push(new AtlasGlyph(glyphStoreIndex, glyphKey)); - } - - const hint = new Hint(this.glyphStore.font, PIXELS_PER_UNIT, false); - - this.baseMeshes = result.meshes; - - this.meshDescriptors = []; - - for (let textFrameIndex = 0; - textFrameIndex < this.textFrames.length; - textFrameIndex++) { - const textFrame = this.textFrames[textFrameIndex]; - const textBounds = textFrame.bounds; - - let glyphDescriptors = []; - for (const run of textFrame.runs) { - for (let glyphIndex = 0; glyphIndex < run.glyphIDs.length; glyphIndex++) { - glyphDescriptors.push({ - glyphID: run.glyphIDs[glyphIndex], - position: run.calculatePixelOriginForGlyphAt(glyphIndex, - PIXELS_PER_UNIT, - 0.0, - hint, - textBounds), - }); - } - } - - glyphDescriptors = _.sortBy(glyphDescriptors, descriptor => descriptor.glyphID); - - let currentMeshDescriptor: (MeshDescriptor | null) = null; - for (const glyphDescriptor of glyphDescriptors) { - if (currentMeshDescriptor == null || - glyphDescriptor.glyphID !== currentMeshDescriptor.glyphID) { - if (currentMeshDescriptor != null) - this.meshDescriptors.push(currentMeshDescriptor); - currentMeshDescriptor = { - glyphID: glyphDescriptor.glyphID, - positions: [], - textFrameIndex: textFrameIndex, - }; - } - currentMeshDescriptor.positions.push(glyphDescriptor.position); - } - if (currentMeshDescriptor != null) - this.meshDescriptors.push(currentMeshDescriptor); - } - - this.expandedMeshes = this.meshDescriptors.map(meshDescriptor => { - const glyphIndex = _.sortedIndexOf(glyphsNeeded, meshDescriptor.glyphID); - return new PathfinderPackedMeshes(this.baseMeshes, [glyphIndex + 1]); - }); - - this.view.then(view => view.attachMeshes(this.expandedMeshes)); - }); - } -} - -class ThreeDView extends DemoView implements TextRenderContext { - cameraView: CameraView; - get atlas(): Atlas { - return this.appController.atlas; - } - - get atlasGlyphs(): AtlasGlyph[] { - return this.appController.atlasGlyphs; - } - - set atlasGlyphs(newAtlasGlyphs: AtlasGlyph[]) { - this.appController.atlasGlyphs = newAtlasGlyphs; - } - - get glyphStore(): GlyphStore { - return this.appController.glyphStore; - } - - get font(): PathfinderFont { - return this.appController.font; - } - - get fontSize(): number { - return ATLAS_FONT_SIZE; - } - - get useHinting(): boolean { - return false; - } - - get atlasPixelsPerUnit(): number { - return ATLAS_FONT_SIZE / this.font.opentypeFont.unitsPerEm; - } - - renderer: ThreeDRenderer; - - appController: ThreeDController; - - private vrDisplay: VRDisplay | null; - private vrFrameData: VRFrameData | null; - private inVRRAF: boolean; - - protected get camera(): PerspectiveCamera { - return this.renderer.camera; - } - - constructor(appController: ThreeDController, - areaLUT: HTMLImageElement, - gammaLUT: HTMLImageElement, - commonShaderSource: string, - shaderSources: ShaderMap) { - super(areaLUT, gammaLUT, commonShaderSource, shaderSources); - - this.cameraView = new ThreeDAtlasCameraView; - - this.appController = appController; - this.renderer = new ThreeDRenderer(this); - - this.inVRRAF = false; - this.vrDisplay = null; - if ("VRFrameData" in window) { - this.vrFrameData = new VRFrameData; - } else { - this.vrFrameData = null; - } - - this.vrSetup(); - - this.resizeToFit(true); - } - - newTimingsReceived(timings: Timings): void {} - - vrSetup(): void { - if (!('getVRDisplays' in navigator)) { - return; - } - - navigator.getVRDisplays().then(displays => { - if (displays.length === 0) { - return; - } - this.vrDisplay = displays[displays.length - 1]; - - // It's heighly recommended that you set the near and far planes to - // something appropriate for your scene so the projection matrices - // WebVR produces have a well scaled depth buffer. - this.renderer.setClipPlanes(this.vrDisplay); - unwrapNull(document.getElementById('pf-vr')).style.display = "initial"; - }); - - window.addEventListener('vrdisplaypresentchange', () => { - if (this.vrDisplay == null) - return; - - if (this.vrDisplay.isPresenting) { - const eye = this.vrDisplay.getEyeParameters("left"); - - this.vrDisplayHeight = eye.renderHeight; - this.vrDisplayWidth = eye.renderWidth * 2; - this.resizeToFit(true); - const vrCallback = () => { - if (this.vrDisplay == null || !this.renderer.inVR) { - return; - } - this.vrDisplay.requestAnimationFrame(vrCallback); - this.inVRRAF = true; - this.redraw(); - this.inVRRAF = false; - }; - this.vrDisplay.requestAnimationFrame(vrCallback); - } else { - this.renderer.inVR = false; - this.resizeToFit(true); - } - }); - } - - enterVR(): void { - if (this.vrDisplay != null) { - this.renderer.inVR = true; - this.vrDisplay.requestPresent([{ source: this.canvas }]); - } - } - - redrawVR(): void { - if (this.vrDisplay == null || - this.vrFrameData == null) { - this.renderer.redraw(); - return; - } - if (!this.inVRRAF) { - // redraw() called outside of vr RAF, will get drawn later - return; - } - this.vrDisplay.getFrameData(this.vrFrameData); - this.renderer.redrawVR(this.vrFrameData); - this.vrDisplay.submitFrame(); - } -} - -class ThreeDRenderer extends Renderer { - renderContext!: ThreeDView; - - camera: PerspectiveCamera; - - needsStencil: boolean = false; - vrRightEye: boolean = false; - - get isMulticolor(): boolean { - return false; - } - - get destFramebuffer(): WebGLFramebuffer | null { - return null; - } - - get destAllocatedSize(): glmatrix.vec2 { - let width = this.renderContext.canvas.width; - if (this.inVR) { - width = width / 2; - } - return glmatrix.vec2.clone([ - width, - this.renderContext.canvas.height, - ]); - } - - get destUsedSize(): glmatrix.vec2 { - return this.destAllocatedSize; - } - - get allowSubpixelAA(): boolean { - return false; - } - - get backgroundColor(): glmatrix.vec4 { - return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]); - } - - 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]); - } - - protected get objectCount(): number { - return this.meshBuffers == null ? 0 : this.meshBuffers.length; - } - - private cubeVertexPositionBuffer: WebGLBuffer; - private cubeIndexBuffer: WebGLBuffer; - - private glyphPositionsBuffer!: WebGLBuffer; - private glyphPositions: number[]; - private glyphPositionRanges: Range[]; - private glyphTexCoords: glmatrix.vec4[]; - private glyphSizes: glmatrix.vec2[]; - - private distantGlyphVAO: WebGLVertexArrayObjectOES | null; - - private vrProjectionMatrix: Float32Array | null; - - constructor(renderContext: ThreeDView) { - super(renderContext); - - const gl = renderContext.gl; - - this.glyphPositions = []; - this.glyphPositionRanges = []; - this.glyphTexCoords = []; - this.glyphSizes = []; - - this.distantGlyphVAO = null; - this.vrProjectionMatrix = null; - - this.camera = new PerspectiveCamera(renderContext.canvas, { - innerCollisionExtent: MONUMENT_SCALE[0], - }); - this.camera.onChange = () => renderContext.setDirty(); - - this.cubeVertexPositionBuffer = unwrapNull(gl.createBuffer()); - gl.bindBuffer(gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer); - gl.bufferData(gl.ARRAY_BUFFER, CUBE_VERTEX_POSITIONS, gl.STATIC_DRAW); - - this.cubeIndexBuffer = unwrapNull(gl.createBuffer()); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, CUBE_INDICES, gl.STATIC_DRAW); - } - - attachMeshes(expandedMeshes: PathfinderPackedMeshes[]) { - super.attachMeshes(expandedMeshes); - - this.renderAtlasGlyphs(this.renderContext.appController.atlasGlyphs); - - this.uploadPathColors(expandedMeshes.length); - this.uploadPathTransforms(expandedMeshes.length); - - this.uploadGlyphPositions(); - } - - pathCountForObject(objectIndex: number): number { - 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); - } - - redrawVR(frame: VRFrameData): void { - this.clearDestFramebuffer(true); - this.vrProjectionMatrix = frame.leftProjectionMatrix; - this.vrRightEye = false; - this.camera.setView((glmatrix.mat4.clone as any)(frame.leftViewMatrix), frame.pose); - this.redraw(); - this.vrRightEye = true; - this.vrProjectionMatrix = frame.rightProjectionMatrix; - this.camera.setView((glmatrix.mat4.clone as any)(frame.rightViewMatrix), frame.pose); - this.redraw(); - } - - setDrawViewport() { - let offset = 0; - if (this.vrRightEye) { - offset = this.destAllocatedSize[0]; - } - const renderContext = this.renderContext; - const gl = renderContext.gl; - gl.viewport(offset, 0, this.destAllocatedSize[0], this.destAllocatedSize[1]); - } - - setClipPlanes(display: VRDisplay) { - display.depthNear = NEAR_CLIP_PLANE; - display.depthFar = FAR_CLIP_PLANE; - } - - pathTransformsForObject(objectIndex: number): PathTransformBuffers { - const meshDescriptor = this.renderContext.appController.meshDescriptors[objectIndex]; - const pathCount = this.pathCountForObject(objectIndex); - const pathTransforms = this.createPathTransformBuffers(pathCount); - for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) { - const glyphOrigin = meshDescriptor.positions[pathIndex]; - pathTransforms.st.set([1, 1, glyphOrigin[0], glyphOrigin[1]], (pathIndex + 1) * 4); - } - return pathTransforms; - } - - protected clearColorForObject(objectIndex: number): glmatrix.vec4 | null { - return null; - } - - protected drawSceneryIfNecessary(): void { - const gl = this.renderContext.gl; - - // Set up the depth buffer for drawing the monument. - gl.clearDepth(1.0); - gl.clear(gl.DEPTH_BUFFER_BIT); - - this.drawMonument(); - - // Clear to avoid Z-fighting. - gl.clearDepth(1.0); - gl.clear(gl.DEPTH_BUFFER_BIT); - - this.drawDistantGlyphs(); - - // Set up the depth buffer for direct rendering. - gl.clearDepth(0.0); - gl.clear(gl.DEPTH_BUFFER_BIT); - } - - protected compositeIfNecessary(): void {} - - protected pathColorsForObject(objectIndex: number): Uint8Array { - return TEXT_COLOR; - } - - protected meshInstanceCountForObject(objectIndex: number): number { - return this.renderContext.appController.meshDescriptors[objectIndex].positions.length; - } - - protected createAAStrategy(aaType: AntialiasingStrategyName, - aaLevel: number, - subpixelAA: SubpixelAAType): - AntialiasingStrategy { - if (aaType !== 'xcaa') - return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA); - throw new PathfinderError("Unsupported antialiasing type!"); - } - - protected clearDestFramebuffer(force: boolean): void { - const gl = this.renderContext.gl; - - gl.bindFramebuffer(gl.FRAMEBUFFER, this.destFramebuffer); - // clear the entire viewport - gl.viewport(0, 0, this.renderContext.canvas.width, this.renderContext.canvas.height); - - gl.clearColor(1.0, 1.0, 1.0, 1.0); - gl.clearDepth(1.0); - gl.depthMask(true); - if (force || this.vrProjectionMatrix == null) { - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - } - - protected getModelviewTransform(objectIndex: number): glmatrix.mat4 { - 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); - return transform; - } - - protected instanceRangeForObject(glyphDescriptorIndex: number): Range { - if (!this.objectIsVisible(glyphDescriptorIndex)) - return new Range(0, 0); - - const totalLength = - this.renderContext.appController.meshDescriptors[glyphDescriptorIndex].positions.length; - - const cameraTransform = this.calculateCameraTransform(glmatrix.vec3.create(), TEXT_SCALE); - const worldTransform = this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE); - const glyphsTransform = glmatrix.mat4.clone(cameraTransform); - const renderTransform = glmatrix.mat4.clone(worldTransform); - const modelviewTransform = this.getModelviewTransform(glyphDescriptorIndex); - glmatrix.mat4.mul(glyphsTransform, cameraTransform, modelviewTransform); - glmatrix.mat4.mul(renderTransform, renderTransform, modelviewTransform); - - const nearbyRange = this.findNearbyGlyphPositions(glyphsTransform, glyphDescriptorIndex); - const glyphPositionRange = this.glyphPositionRanges[glyphDescriptorIndex]; - nearbyRange.start -= glyphPositionRange.start; - nearbyRange.end -= glyphPositionRange.start; - return nearbyRange; - } - - protected newTimingsReceived(): void { - const newTimings: Partial = _.pick(this.lastTimings, ['rendering']); - this.renderContext.appController.newTimingsReceived(newTimings); - } - - protected directCurveProgramName(): keyof ShaderMap { - return 'direct3DCurve'; - } - - protected directInteriorProgramName(): keyof ShaderMap { - return 'direct3DInterior'; - } - - // Cheap but effective backface culling. - private objectIsVisible(objectIndex: number): boolean { - const textFrameIndex = this.renderContext - .appController - .meshDescriptors[objectIndex] - .textFrameIndex; - - const translation = this.camera.translation; - const extent = TEXT_TRANSLATION[2] * TEXT_SCALE[2]; - switch (textFrameIndex) { - case 0: return translation[2] < -extent; - case 1: return translation[0] < -extent; - case 2: return translation[2] > extent; - default: return translation[0] > extent; - } - } - - private uploadGlyphPositions(): void { - const gl = this.renderContext.gl; - const font = this.renderContext.font; - const meshDescriptors = this.renderContext.appController.meshDescriptors; - - this.glyphPositions = []; - this.glyphPositionRanges = []; - for (const meshDescriptor of meshDescriptors) { - const glyphIndex = this.renderContext.atlasGlyphs.findIndex(atlasGlyph => { - return atlasGlyph.glyphKey.id === meshDescriptor.glyphID; - }); - const glyph = this.renderContext.atlasGlyphs[glyphIndex]; - const glyphMetrics = unwrapNull(font.metricsForGlyph(glyph.glyphKey.id)); - const glyphUnitMetrics = new UnitMetrics(glyphMetrics, 0.0, glmatrix.vec2.create()); - - const firstPosition = this.glyphPositions.length / 2; - - for (const position of meshDescriptor.positions) { - this.glyphPositions.push(position[0] + glyphUnitMetrics.left, - position[1] + glyphUnitMetrics.descent); - } - - const lastPosition = this.glyphPositions.length / 2; - this.glyphPositionRanges.push(new Range(firstPosition, lastPosition)); - } - - this.glyphPositionsBuffer = unwrapNull(gl.createBuffer()); - gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.glyphPositions), gl.STATIC_DRAW); - } - - private drawMonument(): void { - const renderContext = this.renderContext; - const gl = renderContext.gl; - - // Set up the cube VBO. - const monumentProgram = this.renderContext.shaderPrograms.demo3DMonument; - gl.useProgram(monumentProgram.program); - gl.bindBuffer(gl.ARRAY_BUFFER, this.cubeVertexPositionBuffer); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.cubeIndexBuffer); - gl.vertexAttribPointer(monumentProgram.attributes.aPosition, 3, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(monumentProgram.attributes.aPosition); - - // Set uniforms for the monument. - const projection = this.calculateProjectionTransform(); - const modelview = this.calculateModelviewTransform(MONUMENT_TRANSLATION, MONUMENT_SCALE); - gl.uniformMatrix4fv(monumentProgram.uniforms.uProjection, false, projection); - gl.uniformMatrix4fv(monumentProgram.uniforms.uModelview, false, modelview); - const cameraModelview = this.calculateCameraModelviewTransform(); - const lightPosition = glmatrix.vec4.clone([-1750.0, -700.0, 1750.0, 1.0]); - glmatrix.vec4.transformMat4(lightPosition, lightPosition, cameraModelview); - gl.uniform3f(monumentProgram.uniforms.uLightPosition, - lightPosition[0] / lightPosition[3], - lightPosition[1] / lightPosition[3], - lightPosition[2] / lightPosition[3]); - gl.uniform3fv(monumentProgram.uniforms.uAmbientColor, AMBIENT_COLOR); - gl.uniform3fv(monumentProgram.uniforms.uDiffuseColor, DIFFUSE_COLOR); - gl.uniform3fv(monumentProgram.uniforms.uSpecularColor, SPECULAR_COLOR); - gl.uniform1f(monumentProgram.uniforms.uShininess, MONUMENT_SHININESS); - - // Set state for the monument. - gl.enable(gl.DEPTH_TEST); - gl.depthFunc(gl.LESS); - gl.depthMask(true); - gl.disable(gl.SCISSOR_TEST); - gl.disable(gl.BLEND); - - // Loop over each face. - for (let face = 0; face < 6; face++) { - // Set the uniforms for this face. - const normal = glmatrix.vec4.clone(MONUMENT_NORMALS[face]); - glmatrix.vec4.transformMat4(normal, normal, this.camera.rotationMatrix); - gl.uniform3f(monumentProgram.uniforms.uNormal, - normal[0] / normal[3], - normal[1] / normal[3], - normal[2] / normal[3]); - gl.uniform1f(monumentProgram.uniforms.uEnableLighting, this.inVR ? 0 : 1); - // Draw the face! - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, face * 6 * UINT16_SIZE); - } - } - - private drawDistantGlyphs(): void { - const appController = this.renderContext.appController; - const gl = this.renderContext.gl; - - // Prepare the distant glyph VAO. - if (this.distantGlyphVAO == null) - this.distantGlyphVAO = this.renderContext.vertexArrayObjectExt.createVertexArrayOES(); - this.renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.distantGlyphVAO); - const distantGlyphProgram = this.renderContext.shaderPrograms.demo3DDistantGlyph; - gl.useProgram(distantGlyphProgram.program); - gl.bindBuffer(gl.ARRAY_BUFFER, this.renderContext.quadPositionsBuffer); - gl.vertexAttribPointer(distantGlyphProgram.attributes.aQuadPosition, - 2, - gl.FLOAT, - false, - 0, - 0); - gl.enableVertexAttribArray(distantGlyphProgram.attributes.aQuadPosition); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.renderContext.quadElementsBuffer); - - // Set global uniforms. - gl.uniform4fv(distantGlyphProgram.uniforms.uColor, - _.map(TEXT_COLOR, number => number / 0xff)); - const atlasTexture = this.renderContext.atlas.ensureTexture(this.renderContext); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, atlasTexture); - gl.uniform1i(distantGlyphProgram.uniforms.uAtlas, 0); - - // Set state. - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.SCISSOR_TEST); - gl.enable(gl.BLEND); - gl.blendEquation(gl.FUNC_ADD); - gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE); - - // Draw textures for distant glyphs. - const cameraTransform = this.calculateCameraTransform(glmatrix.vec3.create(), TEXT_SCALE); - const worldTransform = this.calculateWorldTransform(glmatrix.vec3.create(), TEXT_SCALE); - - for (let glyphDescriptorIndex = 0; - glyphDescriptorIndex < this.glyphPositionRanges.length; - glyphDescriptorIndex++) { - if (!this.objectIsVisible(glyphDescriptorIndex)) - continue; - - const meshDescriptor = appController.meshDescriptors[glyphDescriptorIndex]; - const glyphIndex = this.renderContext.atlasGlyphs.findIndex(glyph => { - return glyph.glyphKey.id === meshDescriptor.glyphID; - }); - - // Calculate transforms. - const glyphsTransform = glmatrix.mat4.clone(cameraTransform); - const renderTransform = glmatrix.mat4.clone(worldTransform); - const modelviewTransform = this.getModelviewTransform(glyphDescriptorIndex); - glmatrix.mat4.mul(glyphsTransform, cameraTransform, modelviewTransform); - glmatrix.mat4.mul(renderTransform, renderTransform, modelviewTransform); - - const glyphPositionRange = this.glyphPositionRanges[glyphDescriptorIndex]; - const nearbyGlyphPositionRange = this.findNearbyGlyphPositions(glyphsTransform, - glyphDescriptorIndex); - - // Set uniforms. - gl.uniformMatrix4fv(distantGlyphProgram.uniforms.uTransform, false, renderTransform); - - const glyphTexCoords = this.glyphTexCoords[glyphIndex]; - gl.uniform4f(distantGlyphProgram.uniforms.uGlyphTexCoords, - glyphTexCoords[0], - glyphTexCoords[1], - glyphTexCoords[2], - glyphTexCoords[3]); - const glyphSize = this.glyphSizes[glyphIndex]; - gl.uniform2f(distantGlyphProgram.uniforms.uGlyphSize, glyphSize[0], glyphSize[1]); - - const rangeBefore = new Range(glyphPositionRange.start, - nearbyGlyphPositionRange.start); - if (!rangeBefore.isEmpty) { - // Would be nice to have `glDrawElementsInstancedBaseInstance`... - // FIXME(pcwalton): Cache VAOs? - gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer); - gl.vertexAttribPointer(distantGlyphProgram.attributes.aPosition, - 2, - gl.FLOAT, - false, - 0, - rangeBefore.start * FLOAT32_SIZE * 2); - gl.enableVertexAttribArray(distantGlyphProgram.attributes.aPosition); - this.renderContext - .instancedArraysExt - .vertexAttribDivisorANGLE(distantGlyphProgram.attributes.aPosition, 1); - - this.renderContext - .instancedArraysExt - .drawElementsInstancedANGLE(gl.TRIANGLES, - 6, - gl.UNSIGNED_BYTE, - 0, - rangeBefore.length); - } - - const rangeAfter = new Range(nearbyGlyphPositionRange.end, glyphPositionRange.end); - if (!rangeAfter.isEmpty) { - gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer); - gl.vertexAttribPointer(distantGlyphProgram.attributes.aPosition, - 2, - gl.FLOAT, - false, - 0, - rangeAfter.start * FLOAT32_SIZE * 2); - gl.enableVertexAttribArray(distantGlyphProgram.attributes.aPosition); - this.renderContext - .instancedArraysExt - .vertexAttribDivisorANGLE(distantGlyphProgram.attributes.aPosition, 1); - - this.renderContext - .instancedArraysExt - .drawElementsInstancedANGLE(gl.TRIANGLES, - 6, - gl.UNSIGNED_BYTE, - 0, - rangeAfter.length); - } - } - - this.renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); - } - - private calculateProjectionTransform(): glmatrix.mat4 { - if (this.vrProjectionMatrix != null) - return (glmatrix.mat4.clone as any)(this.vrProjectionMatrix); - - const canvas = this.renderContext.canvas; - const projection = glmatrix.mat4.create(); - glmatrix.mat4.perspective(projection, - FOV / 180.0 * Math.PI, - canvas.width / canvas.height, - NEAR_CLIP_PLANE, - FAR_CLIP_PLANE); - return projection; - } - - private calculateCameraModelviewTransform(): glmatrix.mat4 { - const modelview = glmatrix.mat4.create(); - glmatrix.mat4.mul(modelview, modelview, this.camera.rotationMatrix); - glmatrix.mat4.translate(modelview, modelview, this.camera.translation); - return modelview; - } - - private calculateModelviewTransform(modelviewTranslation: glmatrix.vec3, - modelviewScale: glmatrix.vec3): - glmatrix.mat4 { - const modelview = this.calculateCameraModelviewTransform(); - glmatrix.mat4.translate(modelview, modelview, modelviewTranslation); - glmatrix.mat4.scale(modelview, modelview, modelviewScale); - return modelview; - } - - private calculateWorldTransform(modelviewTranslation: glmatrix.vec3, - modelviewScale: glmatrix.vec3): - glmatrix.mat4 { - const projection = this.calculateProjectionTransform(); - const modelview = this.calculateModelviewTransform(modelviewTranslation, modelviewScale); - - const transform = glmatrix.mat4.create(); - glmatrix.mat4.mul(transform, projection, modelview); - return transform; - } - - private calculateCameraTransform(modelviewTranslation: glmatrix.vec3, - modelviewScale: glmatrix.vec3): - glmatrix.mat4 { - const transform = glmatrix.mat4.create(); - glmatrix.mat4.translate(transform, transform, this.camera.translation); - glmatrix.mat4.translate(transform, transform, modelviewTranslation); - glmatrix.mat4.scale(transform, transform, modelviewScale); - return transform; - } - - private renderAtlasGlyphs(atlasGlyphs: AtlasGlyph[]): void { - const hint = new Hint(this.renderContext.font, - this.renderContext.atlasPixelsPerUnit, - false); - this.renderContext.atlas.layoutGlyphs(atlasGlyphs, - this.renderContext.font, - this.renderContext.atlasPixelsPerUnit, - 0.0, - hint, - glmatrix.vec2.create()); - - const atlasRenderer = new ThreeDAtlasRenderer(this.renderContext, atlasGlyphs); - const baseMeshes = this.renderContext.appController.baseMeshes; - const expandedMeshes = new PathfinderPackedMeshes(baseMeshes); - atlasRenderer.attachMeshes([expandedMeshes]); - atlasRenderer.renderAtlas(); - this.glyphTexCoords = atlasRenderer.glyphTexCoords; - this.glyphSizes = atlasRenderer.glyphSizes; - } - - private findNearbyGlyphPositions(transform: glmatrix.mat4, glyphDescriptorIndex: number): - Range { - const glyphPositionRange = this.glyphPositionRanges[glyphDescriptorIndex]; - const startPosition = this.findFirstGlyphPositionInRange(transform, - glyphPositionRange, - -MAX_DISTANCE); - const endPosition = this.findFirstGlyphPositionInRange(transform, - new Range(startPosition, - glyphPositionRange.end), - MAX_DISTANCE); - return new Range(startPosition, endPosition); - } - - private findFirstGlyphPositionInRange(transform: glmatrix.mat4, - range: Range, - maxDistance: number): - number { - let lo = range.start, hi = range.end; - while (lo < hi) { - const mid = lo + ((hi - lo) >> 1); - const glyphPosition = this.calculateTransformedGlyphPosition(transform, mid); - const glyphDistance = -glyphPosition[1]; - if (glyphDistance < maxDistance) - lo = mid + 1; - else - hi = mid; - } - return lo; - } - - private calculateTransformedGlyphPosition(transform: glmatrix.mat4, - glyphPositionIndex: number): - glmatrix.vec4 { - const position = glmatrix.vec4.clone([ - this.glyphPositions[glyphPositionIndex * 2 + 0], - this.glyphPositions[glyphPositionIndex * 2 + 1], - 0.0, - 1.0, - ]); - glmatrix.vec4.transformMat4(position, position, transform); - return position; - } -} - -class ThreeDAtlasRenderer extends TextRenderer { - glyphTexCoords: glmatrix.vec4[]; - glyphSizes: glmatrix.vec2[]; - - private allAtlasGlyphs: AtlasGlyph[]; - - get backgroundColor(): glmatrix.vec4 { - return glmatrix.vec4.create(); - } - - constructor(renderContext: ThreeDView, atlasGlyphs: AtlasGlyph[]) { - super(renderContext); - this.allAtlasGlyphs = atlasGlyphs; - this.glyphTexCoords = []; - this.glyphSizes = []; - } - - renderAtlas(): void { - this.createAtlasFramebuffer(); - this.buildAtlasGlyphs(this.allAtlasGlyphs); - this.redraw(); - this.calculateGlyphTexCoords(); - } - - protected compositeIfNecessary(): void {} - - private calculateGlyphTexCoords(): void { - const pixelsPerUnit = this.pixelsPerUnit; - const glyphCount = this.renderContext.atlasGlyphs.length; - const font = this.renderContext.font; - const hint = this.createHint(); - - this.glyphTexCoords = []; - this.glyphSizes = []; - - for (let glyphIndex = 0; glyphIndex < glyphCount; glyphIndex++) { - const glyph = this.renderContext.atlasGlyphs[glyphIndex]; - const glyphPixelOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit); - const glyphMetrics = font.metricsForGlyph(glyph.glyphKey.id); - if (glyphMetrics == null) - continue; - - const glyphUnitMetrics = new UnitMetrics(glyphMetrics, 0.0, glmatrix.vec2.create()); - const atlasGlyphRect = calculatePixelRectForGlyph(glyphUnitMetrics, - glyphPixelOrigin, - pixelsPerUnit, - hint); - - this.glyphSizes.push(glmatrix.vec2.clone([ - glyphUnitMetrics.right - glyphUnitMetrics.left, - glyphUnitMetrics.ascent - glyphUnitMetrics.descent, - ])); - - const atlasGlyphBL = atlasGlyphRect.slice(0, 2) as glmatrix.vec2; - const atlasGlyphTR = atlasGlyphRect.slice(2, 4) as glmatrix.vec2; - glmatrix.vec2.div(atlasGlyphBL, atlasGlyphBL, ATLAS_SIZE); - glmatrix.vec2.div(atlasGlyphTR, atlasGlyphTR, ATLAS_SIZE); - - this.glyphTexCoords.push(glmatrix.vec4.clone([ - atlasGlyphBL[0], - atlasGlyphBL[1], - atlasGlyphTR[0], - atlasGlyphTR[1], - ])); - } - } -} - -class ThreeDAtlasCameraView implements CameraView { - get width(): number { - return ATLAS_SIZE[0]; - } - - get height(): number { - return ATLAS_SIZE[1]; - } - - get classList(): DOMTokenList | null { - return null; - } - - addEventListener(type: K, - listener: (this: HTMLCanvasElement, - ev: HTMLElementEventMap[K]) => - any, - useCapture?: boolean): void {} - - getBoundingClientRect(): ClientRect { - return new ClientRect(); - } -} - -function main() { - const controller = new ThreeDController; - window.addEventListener('load', () => controller.start(), false); -} - -main(); diff --git a/demo/client/src/aa-strategy.ts b/demo/client/src/aa-strategy.ts deleted file mode 100644 index bf05aad8..00000000 --- a/demo/client/src/aa-strategy.ts +++ /dev/null @@ -1,167 +0,0 @@ -// pathfinder/client/src/aa-strategy.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; - -import {createFramebuffer, createFramebufferColorTexture, UniformMap} from './gl-utils'; -import {createFramebufferDepthTexture} from './gl-utils'; -import {Renderer} from './renderer'; -import {unwrapNull} from './utils'; -import {DemoView} from './view'; - -const SUBPIXEL_AA_KERNELS: {readonly [kind in SubpixelAAType]: glmatrix.vec4} = { - // These intentionally do not precisely match what Core Graphics does (a Lanczos function), - // because we don't want any ringing artefacts. - 'core-graphics': glmatrix.vec4.clone([0.033165660, 0.102074051, 0.221434336, 0.286651906]), - 'freetype': glmatrix.vec4.clone([0.0, 0.031372549, 0.301960784, 0.337254902]), - 'none': glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]), -}; - -export type AntialiasingStrategyName = 'none' | 'ssaa' | 'xcaa'; - -export type DirectRenderingMode = 'none' | 'conservative' | 'color'; - -export type SubpixelAAType = 'none' | 'freetype' | 'core-graphics'; - -export type GammaCorrectionMode = 'off' | 'on'; - -export type StemDarkeningMode = 'none' | 'dark'; - -export interface TileInfo { - size: glmatrix.vec2; - position: glmatrix.vec2; -} - -export abstract class AntialiasingStrategy { - // The type of direct rendering that should occur, if any. - abstract readonly directRenderingMode: DirectRenderingMode; - - // How many rendering passes this AA strategy requires. - abstract readonly passCount: number; - - protected subpixelAA: SubpixelAAType; - - constructor(subpixelAA: SubpixelAAType) { - this.subpixelAA = subpixelAA; - } - - // Prepares any OpenGL data. This is only called on startup and canvas resize. - init(renderer: Renderer): void { - this.setFramebufferSize(renderer); - } - - // Uploads any mesh data. This is called whenever a new set of meshes is supplied. - abstract attachMeshes(renderer: Renderer): void; - - // This is called whenever the framebuffer has changed. - abstract setFramebufferSize(renderer: Renderer): void; - - // Returns the transformation matrix that should be applied when directly rendering. - abstract get transform(): glmatrix.mat4; - - // Called before rendering. - // - // Typically, this redirects rendering to a framebuffer of some sort. - abstract prepareForRendering(renderer: Renderer): void; - - // Called before directly rendering. - // - // Typically, this redirects rendering to a framebuffer of some sort. - abstract prepareForDirectRendering(renderer: Renderer): void; - - // Called before directly rendering a single object. - abstract prepareToRenderObject(renderer: Renderer, objectIndex: number): void; - - abstract finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void; - - // Called after direct rendering. - // - // This usually performs the actual antialiasing. - abstract antialiasObject(renderer: Renderer, objectIndex: number): void; - - // Called after antialiasing each object. - abstract finishAntialiasingObject(renderer: Renderer, objectIndex: number): void; - - // Called before rendering each object directly. - abstract resolveAAForObject(renderer: Renderer, objectIndex: number): void; - - // Called after antialiasing. - // - // This usually blits to the real framebuffer. - abstract resolve(pass: number, renderer: Renderer): void; - - setSubpixelAAKernelUniform(renderer: Renderer, uniforms: UniformMap): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - const kernel = SUBPIXEL_AA_KERNELS[this.subpixelAA]; - gl.uniform4f(uniforms.uKernel, kernel[0], kernel[1], kernel[2], kernel[3]); - } - - worldTransformForPass(renderer: Renderer, pass: number): glmatrix.mat4 { - return glmatrix.mat4.create(); - } -} - -export class NoAAStrategy extends AntialiasingStrategy { - framebufferSize: glmatrix.vec2; - - get passCount(): number { - return 1; - } - - constructor(level: number, subpixelAA: SubpixelAAType) { - super(subpixelAA); - this.framebufferSize = glmatrix.vec2.create(); - } - - attachMeshes(renderer: Renderer) {} - - setFramebufferSize(renderer: Renderer) { - this.framebufferSize = renderer.destAllocatedSize; - } - - get transform(): glmatrix.mat4 { - return glmatrix.mat4.create(); - } - - prepareForRendering(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - gl.bindFramebuffer(gl.FRAMEBUFFER, renderer.destFramebuffer); - renderer.setDrawViewport(); - gl.disable(gl.SCISSOR_TEST); - } - - prepareForDirectRendering(renderer: Renderer): void {} - - prepareToRenderObject(renderer: Renderer, objectIndex: number): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - gl.bindFramebuffer(gl.FRAMEBUFFER, renderer.destFramebuffer); - renderer.setDrawViewport(); - gl.disable(gl.SCISSOR_TEST); - } - - finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void {} - - antialiasObject(renderer: Renderer, objectIndex: number): void {} - - finishAntialiasingObject(renderer: Renderer, objectIndex: number): void {} - - resolveAAForObject(renderer: Renderer): void {} - - resolve(pass: number, renderer: Renderer): void {} - - get directRenderingMode(): DirectRenderingMode { - return 'color'; - } -} diff --git a/demo/client/src/app-controller.ts b/demo/client/src/app-controller.ts deleted file mode 100644 index 1ac86945..00000000 --- a/demo/client/src/app-controller.ts +++ /dev/null @@ -1,398 +0,0 @@ -// pathfinder/client/src/app-controller.ts -// -// Copyright © 2017 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. - -import {AntialiasingStrategyName, GammaCorrectionMode, StemDarkeningMode} from "./aa-strategy"; -import {SubpixelAAType} from "./aa-strategy"; -import {FilePickerView} from "./file-picker"; -import {ShaderLoader, ShaderMap, ShaderProgramSource} from './shader-loader'; -import {expectNotNull, unwrapNull, unwrapUndef} from './utils'; -import {DemoView, Timings, TIMINGS} from "./view"; - -const AREA_LUT_URI: string = "/textures/area-lut.png"; -const GAMMA_LUT_URI: string = "/textures/gamma-lut.png"; - -const SWITCHES: SwitchMap = { - gammaCorrection: { - defaultValue: 'on', - id: 'pf-gamma-correction', - offValue: 'off', - onValue: 'on', - switchInputsName: 'gammaCorrectionSwitchInputs', - }, - stemDarkening: { - defaultValue: 'dark', - id: 'pf-stem-darkening', - offValue: 'none', - onValue: 'dark', - switchInputsName: 'stemDarkeningSwitchInputs', - }, -}; - -interface SwitchDescriptor { - id: string; - switchInputsName: keyof Switches; - onValue: string; - offValue: string; - defaultValue: string; -} - -interface SwitchMap { - gammaCorrection: SwitchDescriptor; - stemDarkening: SwitchDescriptor; -} - -export interface AAOptions { - gammaCorrection: GammaCorrectionMode; - stemDarkening: StemDarkeningMode; - subpixelAA: SubpixelAAType; -} - -export interface SwitchInputs { - on: HTMLInputElement; - off: HTMLInputElement; -} - -interface Switches { - gammaCorrectionSwitchInputs: SwitchInputs | null; - stemDarkeningSwitchInputs: SwitchInputs | null; -} - -export abstract class AppController { - protected canvas!: HTMLCanvasElement; - protected selectFileElement: HTMLSelectElement | null = null; - protected screenshotButton: HTMLButtonElement | null = null; - - start(): void { - this.selectFileElement = document.getElementById('pf-select-file') as HTMLSelectElement | - null; - } - - protected loadInitialFile(builtinFileURI: string): void { - if (this.selectFileElement != null) { - const selectedOption = this.selectFileElement.selectedOptions[0] as HTMLOptionElement; - this.fetchFile(selectedOption.value, builtinFileURI); - } else { - this.fetchFile(this.defaultFile, builtinFileURI); - } - } - - protected fetchFile(file: string, builtinFileURI: string): Promise { - return new Promise(resolve => { - window.fetch(`${builtinFileURI}/${file}`) - .then(response => response.arrayBuffer()) - .then(data => { - this.fileLoaded(data, file); - resolve(); - }); - }); - } - - protected abstract fileLoaded(data: ArrayBuffer, builtinName: string | null): void; - - protected abstract get defaultFile(): string; -} - -export abstract class DemoAppController extends AppController - implements Switches { - view!: Promise; - - gammaCorrectionSwitchInputs: SwitchInputs | null = null; - stemDarkeningSwitchInputs: SwitchInputs | null = null; - - protected abstract readonly builtinFileURI: string; - - protected filePickerView: FilePickerView | null = null; - - protected aaLevelSelect: HTMLSelectElement | null = null; - protected subpixelAASelect: HTMLSelectElement | null = null; - - private fpsLabel: HTMLElement | null = null; - - constructor() { - super(); - } - - start() { - super.start(); - - this.initPopup('pf-settings', 'pf-settings-button', 'pf-settings-close-button'); - this.initPopup('pf-rotate-slider-card', 'pf-rotate-button', 'pf-rotate-close-button'); - - const screenshotButton = document.getElementById('pf-screenshot-button') as - HTMLButtonElement | null; - if (screenshotButton != null) { - screenshotButton.addEventListener('click', () => { - this.view.then(view => view.queueScreenshot()); - }, false); - } - - const zoomInButton = document.getElementById('pf-zoom-in-button') as HTMLButtonElement | - null; - if (zoomInButton != null) { - zoomInButton.addEventListener('click', () => { - this.view.then(view => view.zoomIn()); - }, false); - } - - const zoomOutButton = document.getElementById('pf-zoom-out-button') as HTMLButtonElement | - null; - if (zoomOutButton != null) { - zoomOutButton.addEventListener('click', () => { - this.view.then(view => view.zoomOut()); - }, false); - } - - const zoomPulseButton = document.getElementById('pf-zoom-pulse-button') as - HTMLButtonElement | null; - if (zoomPulseButton != null) { - zoomPulseButton.addEventListener('click', () => { - this.view.then(view => view.zoomPulse()); - }, false); - } - - const rotateSlider = document.getElementById('pf-rotate-slider') as HTMLInputElement | - null; - if (rotateSlider != null) { - rotateSlider.addEventListener('input', event => { - this.view.then(view => { - view.rotate((event.target as HTMLInputElement).valueAsNumber); - }); - }, false); - } - - const vrButton = document.getElementById('pf-vr') as HTMLInputElement | - null; - if (vrButton != null) { - vrButton.addEventListener('click', event => { - this.view.then(view => { - view.enterVR(); - }); - }, false); - } - - this.filePickerView = FilePickerView.create(); - if (this.filePickerView != null) { - this.filePickerView.onFileLoaded = fileData => this.fileLoaded(fileData, null); - } - - if (this.selectFileElement != null) { - this.selectFileElement - .addEventListener('click', event => this.fileSelectionChanged(event), false); - } - - this.fpsLabel = document.getElementById('pf-fps-label'); - - const shaderLoader = new ShaderLoader; - shaderLoader.load(); - - const areaLUTPromise = this.loadTexture(AREA_LUT_URI); - const gammaLUTPromise = this.loadTexture(GAMMA_LUT_URI); - - const promises: any[] = [ - areaLUTPromise, - gammaLUTPromise, - shaderLoader.common, - shaderLoader.shaders, - ]; - this.view = Promise.all(promises).then(assets => { - return this.createView(assets[0], assets[1], assets[2], assets[3]); - }); - - this.aaLevelSelect = document.getElementById('pf-aa-level-select') as - (HTMLSelectElement | null); - if (this.aaLevelSelect != null) - this.aaLevelSelect.addEventListener('change', () => this.updateAALevel(), false); - - this.subpixelAASelect = document.getElementById('pf-subpixel-aa-select') as - (HTMLSelectElement | null); - if (this.subpixelAASelect != null) - this.subpixelAASelect.addEventListener('change', () => this.updateAALevel(), false); - - // The event listeners here use `window.setTimeout()` because jQuery won't fire the "live" - // click listener that Bootstrap sets up until the event bubbles up to the document. This - // click listener is what toggles the `checked` attribute, so we have to wait until it - // fires before updating the antialiasing settings. - for (const switchName of Object.keys(SWITCHES) as Array) { - const switchInputsName = SWITCHES[switchName].switchInputsName; - const switchID = SWITCHES[switchName].id; - const switchOnInput = document.getElementById(`${switchID}-select-on`); - const switchOffInput = document.getElementById(`${switchID}-select-off`); - if (switchOnInput != null && switchOffInput != null) { - this[switchInputsName] = { - off: switchOffInput as HTMLInputElement, - on: switchOnInput as HTMLInputElement, - }; - } else { - this[switchInputsName] = null; - } - - const buttons = document.getElementById(`${switchID}-buttons`) as HTMLElement | null; - if (buttons == null) - continue; - - buttons.addEventListener('click', () => { - window.setTimeout(() => this.updateAALevel(), 0); - }, false); - } - - this.updateAALevel(); - } - - newTimingsReceived(timings: Partial) { - if (this.fpsLabel == null) - return; - - while (this.fpsLabel.lastChild != null) - this.fpsLabel.removeChild(this.fpsLabel.lastChild); - - for (const timing of Object.keys(timings) as Array) { - const tr = document.createElement('div'); - tr.classList.add('row'); - - const keyTD = document.createElement('div'); - const valueTD = document.createElement('div'); - keyTD.classList.add('col'); - valueTD.classList.add('col'); - keyTD.appendChild(document.createTextNode(TIMINGS[timing])); - valueTD.appendChild(document.createTextNode(timings[timing] + " ms")); - - tr.appendChild(keyTD); - tr.appendChild(valueTD); - this.fpsLabel.appendChild(tr); - } - - this.fpsLabel.classList.remove('invisible'); - } - - protected updateAALevel(): Promise { - let aaType: AntialiasingStrategyName, aaLevel: number; - if (this.aaLevelSelect != null) { - const selectedOption = this.aaLevelSelect.selectedOptions[0]; - const aaValues = unwrapNull(/^([a-z-]+)(?:-([0-9]+))?$/.exec(selectedOption.value)); - aaType = aaValues[1] as AntialiasingStrategyName; - aaLevel = aaValues[2] === "" ? 1 : parseInt(aaValues[2], 10); - } else { - aaType = 'none'; - aaLevel = 0; - } - - this.updateUIForAALevelChange(aaType, aaLevel); - - const aaOptions: Partial = {}; - for (const switchName of Object.keys(SWITCHES) as Array) { - const switchDescriptor = SWITCHES[switchName]; - const switchInputsName = switchDescriptor.switchInputsName; - const switchInputs = this[switchInputsName]; - if (switchInputs == null) - aaOptions[switchName] = switchDescriptor.defaultValue as any; - else if (switchInputs.on.checked && !switchInputs.on.disabled) - aaOptions[switchName] = switchDescriptor.onValue as any; - else - aaOptions[switchName] = switchDescriptor.offValue as any; - } - if (this.subpixelAASelect != null) { - const selectedOption = this.subpixelAASelect.selectedOptions[0]; - aaOptions.subpixelAA = selectedOption.value as SubpixelAAType; - } else { - aaOptions.subpixelAA = 'none'; - } - - return this.view.then(view => { - view.setAntialiasingOptions(aaType, aaLevel, aaOptions as AAOptions); - }); - } - - protected updateUIForAALevelChange(aaType: AntialiasingStrategyName, aaLevel: number): void { - // Overridden by subclasses. - } - - protected abstract createView(areaLUT: HTMLImageElement, - gammaLUT: HTMLImageElement, - commonShaderSource: string, - shaderSources: ShaderMap): - View; - - private initPopup(cardID: string, popupButtonID: string, closeButtonID: string): void { - const card = document.getElementById(cardID) as HTMLElement | null; - const button = document.getElementById(popupButtonID) as HTMLButtonElement | null; - const closeButton = document.getElementById(closeButtonID) as HTMLButtonElement | null; - - if (button != null) { - button.addEventListener('click', event => { - event.stopPropagation(); - unwrapNull(card).classList.toggle('pf-invisible'); - }, false); - } - if (closeButton != null) { - closeButton.addEventListener('click', () => { - unwrapNull(card).classList.add('pf-invisible'); - }, false); - } - - if (card == null) - return; - - document.body.addEventListener('click', event => { - let element = event.target as Element | null; - while (element != null) { - if (element === card) - return; - element = element.parentElement; - } - - card.classList.add('pf-invisible'); - }, false); - } - - private loadTexture(uri: string): Promise { - return window.fetch(uri) - .then(response => response.blob()) - .then(blob => { - const imgElement = document.createElement('img'); - imgElement.src = URL.createObjectURL(blob); - const promise: Promise = new Promise(resolve => { - imgElement.addEventListener('load', () => { - resolve(imgElement); - }, false); - }); - return promise; - }); - } - - private fileSelectionChanged(event: Event): void { - const selectFileElement = event.currentTarget as HTMLSelectElement; - const selectedOption = selectFileElement.selectedOptions[0] as HTMLOptionElement; - - if (selectedOption.value === 'load-custom' && this.filePickerView != null) { - this.filePickerView.open(); - - const oldSelectedIndex = selectFileElement.selectedIndex; - const newOption = document.createElement('option'); - newOption.id = 'pf-custom-option-placeholder'; - newOption.appendChild(document.createTextNode("Custom")); - selectFileElement.insertBefore(newOption, selectedOption); - selectFileElement.selectedIndex = oldSelectedIndex; - return; - } - - // Remove the "Custom…" placeholder if it exists. - const placeholder = document.getElementById('pf-custom-option-placeholder'); - if (placeholder != null) - selectFileElement.removeChild(placeholder); - - // Fetch the file. - this.fetchFile(selectedOption.value, this.builtinFileURI); - } -} - -export function setSwitchInputsValue(switchInputs: SwitchInputs, on: boolean): void { - switchInputs.on.checked = on; - switchInputs.off.checked = !on; -} diff --git a/demo/client/src/atlas.ts b/demo/client/src/atlas.ts deleted file mode 100644 index 784af743..00000000 --- a/demo/client/src/atlas.ts +++ /dev/null @@ -1,160 +0,0 @@ -// pathfinder/client/src/atlas.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; -import * as _ from 'lodash'; - -import {setTextureParameters} from './gl-utils'; -import {calculatePixelDescent, calculatePixelRectForGlyph, calculatePixelXMin, Hint} from './text'; -import {PathfinderFont, UnitMetrics} from './text'; -import {unwrapNull} from './utils'; -import {RenderContext} from './view'; - -export const SUBPIXEL_GRANULARITY: number = 4; - -export const ATLAS_SIZE: glmatrix.vec2 = glmatrix.vec2.fromValues(2048, 4096); - -export class Atlas { - private _texture: WebGLTexture | null; - private _usedSize: glmatrix.vec2; - - constructor() { - this._texture = null; - this._usedSize = glmatrix.vec2.create(); - } - - layoutGlyphs(glyphs: AtlasGlyph[], - font: PathfinderFont, - pixelsPerUnit: number, - rotationAngle: number, - hint: Hint, - emboldenAmount: glmatrix.vec2): - void { - let nextOrigin = glmatrix.vec2.fromValues(1.0, 1.0); - let shelfBottom = 2.0; - - for (const glyph of glyphs) { - // Place the glyph, and advance the origin. - const metrics = font.metricsForGlyph(glyph.glyphKey.id); - if (metrics == null) - continue; - - const unitMetrics = new UnitMetrics(metrics, rotationAngle, emboldenAmount); - glyph.setPixelLowerLeft(nextOrigin, unitMetrics, pixelsPerUnit); - - let pixelOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit); - nextOrigin[0] = calculatePixelRectForGlyph(unitMetrics, - pixelOrigin, - pixelsPerUnit, - hint)[2] + 1.0; - - // If the glyph overflowed the shelf, make a new one and reposition the glyph. - if (nextOrigin[0] > ATLAS_SIZE[0]) { - nextOrigin = glmatrix.vec2.clone([1.0, shelfBottom + 1.0]); - glyph.setPixelLowerLeft(nextOrigin, unitMetrics, pixelsPerUnit); - pixelOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit); - nextOrigin[0] = calculatePixelRectForGlyph(unitMetrics, - pixelOrigin, - pixelsPerUnit, - hint)[2] + 1.0; - } - - // Grow the shelf as necessary. - const glyphBottom = calculatePixelRectForGlyph(unitMetrics, - pixelOrigin, - pixelsPerUnit, - hint)[3]; - shelfBottom = Math.max(shelfBottom, glyphBottom + 1.0); - } - - // FIXME(pcwalton): Could be more precise if we don't have a full row. - this._usedSize = glmatrix.vec2.clone([ATLAS_SIZE[0], shelfBottom]); - } - - ensureTexture(renderContext: RenderContext): WebGLTexture { - if (this._texture != null) - return this._texture; - - const gl = renderContext.gl; - const texture = unwrapNull(gl.createTexture()); - this._texture = texture; - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D(gl.TEXTURE_2D, - 0, - gl.RGBA, - ATLAS_SIZE[0], - ATLAS_SIZE[1], - 0, - gl.RGBA, - gl.UNSIGNED_BYTE, - null); - setTextureParameters(gl, gl.NEAREST); - - return texture; - } - - get usedSize(): glmatrix.vec2 { - return this._usedSize; - } -} - -export class AtlasGlyph { - readonly glyphStoreIndex: number; - readonly glyphKey: GlyphKey; - readonly origin: glmatrix.vec2; - - constructor(glyphStoreIndex: number, glyphKey: GlyphKey) { - this.glyphStoreIndex = glyphStoreIndex; - this.glyphKey = glyphKey; - this.origin = glmatrix.vec2.create(); - } - - calculateSubpixelOrigin(pixelsPerUnit: number): glmatrix.vec2 { - const pixelOrigin = glmatrix.vec2.create(); - glmatrix.vec2.scale(pixelOrigin, this.origin, pixelsPerUnit); - glmatrix.vec2.round(pixelOrigin, pixelOrigin); - if (this.glyphKey.subpixel != null) - pixelOrigin[0] += this.glyphKey.subpixel / SUBPIXEL_GRANULARITY; - return pixelOrigin; - } - - setPixelLowerLeft(pixelLowerLeft: glmatrix.vec2, metrics: UnitMetrics, pixelsPerUnit: number): - void { - const pixelXMin = calculatePixelXMin(metrics, pixelsPerUnit); - const pixelDescent = calculatePixelDescent(metrics, pixelsPerUnit); - const pixelOrigin = glmatrix.vec2.clone([pixelLowerLeft[0] - pixelXMin, - pixelLowerLeft[1] - pixelDescent]); - this.setPixelOrigin(pixelOrigin, pixelsPerUnit); - } - - private setPixelOrigin(pixelOrigin: glmatrix.vec2, pixelsPerUnit: number): void { - glmatrix.vec2.scale(this.origin, pixelOrigin, 1.0 / pixelsPerUnit); - } - - get pathID(): number { - if (this.glyphKey.subpixel == null) - return this.glyphStoreIndex + 1; - return this.glyphStoreIndex * SUBPIXEL_GRANULARITY + this.glyphKey.subpixel + 1; - } -} - -export class GlyphKey { - readonly id: number; - readonly subpixel: number | null; - - constructor(id: number, subpixel: number | null) { - this.id = id; - this.subpixel = subpixel; - } - - get sortKey(): number { - return this.subpixel == null ? this.id : this.id * SUBPIXEL_GRANULARITY + this.subpixel; - } -} diff --git a/demo/client/src/benchmark.ts b/demo/client/src/benchmark.ts deleted file mode 100644 index 6a1b9347..00000000 --- a/demo/client/src/benchmark.ts +++ /dev/null @@ -1,653 +0,0 @@ -// pathfinder/client/src/benchmark.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; -import * as _ from 'lodash'; -import * as opentype from "opentype.js"; - -import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; -import {SubpixelAAType} from "./aa-strategy"; -import {AppController, DemoAppController, setSwitchInputsValue} from "./app-controller"; -import PathfinderBufferTexture from './buffer-texture'; -import {OrthographicCamera} from './camera'; -import {UniformMap} from './gl-utils'; -import {PathfinderMeshPack, PathfinderPackedMeshes} from "./meshes"; -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 {SVGRenderer} from './svg-renderer'; -import {BUILTIN_FONT_URI, ExpandedMeshData, GlyphStore, PathfinderFont, TextFrame} from "./text"; -import {computeStemDarkeningAmount, TextRun} from "./text"; -import {assert, lerp, PathfinderError, unwrapNull, unwrapUndef} from "./utils"; -import {DemoView, Timings} from "./view"; -import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy'; - -const STRING: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - -const DEFAULT_FONT: string = 'nimbus-sans'; -const DEFAULT_SVG_FILE: string = 'tiger'; - -const TEXT_COLOR: number[] = [0, 0, 0, 255]; - -// In milliseconds. -const MIN_RUNTIME: number = 100; -const MAX_RUNTIME: number = 3000; - -const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { - none: NoAAStrategy, - ssaa: SSAAStrategy, - xcaa: AdaptiveStencilMeshAAAStrategy, -}; - -interface BenchmarkModeMap { - text: T; - svg: T; -} - -type BenchmarkMode = 'text' | 'svg'; - -interface AntialiasingStrategyTable { - none: typeof NoAAStrategy; - ssaa: typeof SSAAStrategy; - xcaa: typeof AdaptiveStencilMeshAAAStrategy; -} - -interface TestParameter { - start: number; - stop: number; - step: number; -} - -const DISPLAY_HEADER_LABELS: BenchmarkModeMap = { - svg: ["Size (px)", "GPU time (ms)"], - text: ["Font size (px)", "GPU time per glyph (µs)"], -}; - -const TEST_SIZES: BenchmarkModeMap = { - svg: { start: 64, stop: 2048, step: 16 }, - text: { start: 6, stop: 200, step: 1 }, -}; - -class BenchmarkAppController extends DemoAppController { - font: PathfinderFont | null = null; - textRun: TextRun | null = null; - - svgLoader!: SVGLoader; - - mode!: BenchmarkMode; - - protected get defaultFile(): string { - if (this.mode === 'text') - return DEFAULT_FONT; - return DEFAULT_SVG_FILE; - } - - protected get builtinFileURI(): string { - if (this.mode === 'text') - return BUILTIN_FONT_URI; - return BUILTIN_SVG_URI; - } - - private optionsModal!: HTMLDivElement; - - private resultsModal!: HTMLDivElement; - private resultsTableHeader!: HTMLTableSectionElement; - private resultsTableBody!: HTMLTableSectionElement; - private resultsPartitioningTimeLabel!: HTMLSpanElement; - - private glyphStore!: GlyphStore; - private baseMeshes!: PathfinderMeshPack; - private expandedMeshes!: ExpandedMeshData; - - private size!: number; - private currentRun!: number; - private startTime!: number; - private elapsedTimes!: ElapsedTime[]; - private partitionTime!: number; - - start(): void { - super.start(); - - this.mode = 'text'; - - this.optionsModal = unwrapNull(document.getElementById('pf-benchmark-modal')) as - HTMLDivElement; - - this.resultsModal = unwrapNull(document.getElementById('pf-benchmark-results-modal')) as - HTMLDivElement; - this.resultsTableHeader = - unwrapNull(document.getElementById('pf-benchmark-results-table-header')) as - HTMLTableSectionElement; - this.resultsTableBody = - unwrapNull(document.getElementById('pf-benchmark-results-table-body')) as - HTMLTableSectionElement; - this.resultsPartitioningTimeLabel = - unwrapNull(document.getElementById('pf-benchmark-results-partitioning-time')) as - HTMLSpanElement; - - const resultsSaveCSVButton = - unwrapNull(document.getElementById('pf-benchmark-results-save-csv-button')); - resultsSaveCSVButton.addEventListener('click', () => this.saveCSV(), false); - - const resultsCloseButton = - unwrapNull(document.getElementById('pf-benchmark-results-close-button')); - resultsCloseButton.addEventListener('click', () => { - window.jQuery(this.resultsModal).modal('hide'); - }, false); - - const runBenchmarkButton = unwrapNull(document.getElementById('pf-run-benchmark-button')); - runBenchmarkButton.addEventListener('click', () => this.runBenchmark(), false); - - const aaLevelFormGroup = unwrapNull(document.getElementById('pf-aa-level-form-group')) as - HTMLDivElement; - const benchmarkTextForm = unwrapNull(document.getElementById('pf-benchmark-text-form')) as - HTMLFormElement; - const benchmarkSVGForm = unwrapNull(document.getElementById('pf-benchmark-svg-form')) as - HTMLFormElement; - - window.jQuery(this.optionsModal).modal(); - - const benchmarkTextTab = document.getElementById('pf-benchmark-text-tab') as - HTMLAnchorElement; - const benchmarkSVGTab = document.getElementById('pf-benchmark-svg-tab') as - HTMLAnchorElement; - window.jQuery(benchmarkTextTab).on('shown.bs.tab', event => { - this.mode = 'text'; - if (aaLevelFormGroup.parentElement != null) - aaLevelFormGroup.parentElement.removeChild(aaLevelFormGroup); - benchmarkTextForm.insertBefore(aaLevelFormGroup, benchmarkTextForm.firstChild); - this.modeChanged(); - }); - window.jQuery(benchmarkSVGTab).on('shown.bs.tab', event => { - this.mode = 'svg'; - if (aaLevelFormGroup.parentElement != null) - aaLevelFormGroup.parentElement.removeChild(aaLevelFormGroup); - benchmarkSVGForm.insertBefore(aaLevelFormGroup, benchmarkSVGForm.firstChild); - this.modeChanged(); - }); - - this.loadInitialFile(this.builtinFileURI); - } - - protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void { - switch (this.mode) { - case 'text': - this.textFileLoaded(fileData, builtinName); - return; - case 'svg': - this.svgFileLoaded(fileData, builtinName); - return; - } - } - - protected createView(areaLUT: HTMLImageElement, - gammaLUT: HTMLImageElement, - commonShaderSource: string, - shaderSources: ShaderMap): - BenchmarkTestView { - return new BenchmarkTestView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources); - } - - private modeChanged(): void { - this.loadInitialFile(this.builtinFileURI); - if (this.aaLevelSelect != null) - this.aaLevelSelect.selectedIndex = 0; - if (this.subpixelAASelect != null) - this.subpixelAASelect.selectedIndex = 0; - this.updateAALevel(); - } - - private textFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void { - const font = new PathfinderFont(fileData, builtinName); - this.font = font; - - const textRun = new TextRun(STRING, [0, 0], font); - textRun.layout(); - this.textRun = textRun; - const textFrame = new TextFrame([textRun], font); - - const glyphIDs = textFrame.allGlyphIDs; - glyphIDs.sort((a, b) => a - b); - this.glyphStore = new GlyphStore(font, glyphIDs); - - this.glyphStore.partition().then(result => { - this.baseMeshes = result.meshes; - - const partitionTime = result.time / this.glyphStore.glyphIDs.length * 1e6; - const timeLabel = this.resultsPartitioningTimeLabel; - while (timeLabel.firstChild != null) - timeLabel.removeChild(timeLabel.firstChild); - timeLabel.appendChild(document.createTextNode("" + partitionTime)); - - const expandedMeshes = textFrame.expandMeshes(this.baseMeshes, glyphIDs); - this.expandedMeshes = expandedMeshes; - - this.view.then(view => { - view.recreateRenderer(); - view.attachMeshes([expandedMeshes.meshes]); - }); - }); - } - - private svgFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void { - this.svgLoader = new SVGLoader; - this.svgLoader.loadFile(fileData); - this.svgLoader.partition().then(meshes => { - this.view.then(view => { - view.recreateRenderer(); - view.attachMeshes([new PathfinderPackedMeshes(meshes)]); - view.initCameraBounds(this.svgLoader.svgViewBox); - }); - }); - } - - private reset(): void { - this.currentRun = 0; - this.startTime = Date.now(); - } - - private runBenchmark(): void { - window.jQuery(this.optionsModal).modal('hide'); - - this.reset(); - this.elapsedTimes = []; - this.size = TEST_SIZES[this.mode].start; - this.view.then(view => this.runOneBenchmarkTest(view)); - } - - private runDone(): boolean { - const totalElapsedTime = Date.now() - this.startTime; - if (totalElapsedTime < MIN_RUNTIME) - return false; - if (totalElapsedTime >= MAX_RUNTIME) - return true; - - // Compute median absolute devation. - const elapsedTime = unwrapUndef(_.last(this.elapsedTimes)); - elapsedTime.times.sort((a, b) => a - b); - const median = unwrapNull(computeMedian(elapsedTime.times)); - const absoluteDeviations = elapsedTime.times.map(time => Math.abs(time - median)); - absoluteDeviations.sort((a, b) => a - b); - const medianAbsoluteDeviation = unwrapNull(computeMedian(absoluteDeviations)); - const medianAbsoluteDeviationFraction = medianAbsoluteDeviation / median; - return medianAbsoluteDeviationFraction <= 0.01; - } - - private runOneBenchmarkTest(view: BenchmarkTestView): void { - const renderedPromise = new Promise((resolve, reject) => { - view.renderingPromiseCallback = resolve; - view.size = this.size; - }); - renderedPromise.then(elapsedTime => { - if (this.currentRun === 0) - this.elapsedTimes.push(new ElapsedTime(this.size)); - unwrapUndef(_.last(this.elapsedTimes)).times.push(elapsedTime); - - this.currentRun++; - if (this.runDone()) { - this.reset(); - - if (this.size >= TEST_SIZES[this.mode].stop) { - this.showResults(); - return; - } - - this.size += TEST_SIZES[this.mode].step; - } - - this.runOneBenchmarkTest(view); - }); - } - - private showResults(): void { - while (this.resultsTableHeader.lastChild != null) - this.resultsTableHeader.removeChild(this.resultsTableHeader.lastChild); - while (this.resultsTableBody.lastChild != null) - this.resultsTableBody.removeChild(this.resultsTableBody.lastChild); - - const tr = document.createElement('tr'); - for (const headerLabel of DISPLAY_HEADER_LABELS[this.mode]) { - const th = document.createElement('th'); - th.appendChild(document.createTextNode(headerLabel)); - tr.appendChild(th); - } - this.resultsTableHeader.appendChild(tr); - - for (const elapsedTime of this.elapsedTimes) { - const tr = document.createElement('tr'); - const sizeTH = document.createElement('th'); - const timeTD = document.createElement('td'); - sizeTH.appendChild(document.createTextNode("" + elapsedTime.size)); - const time = this.mode === 'svg' ? elapsedTime.timeInMS : elapsedTime.time; - timeTD.appendChild(document.createTextNode("" + time)); - sizeTH.scope = 'row'; - tr.appendChild(sizeTH); - tr.appendChild(timeTD); - this.resultsTableBody.appendChild(tr); - } - - window.jQuery(this.resultsModal).modal(); - } - - private saveCSV(): void { - let output = "Size,Time\n"; - for (const elapsedTime of this.elapsedTimes) { - const time = this.mode === 'svg' ? elapsedTime.timeInMS : elapsedTime.time; - output += `${elapsedTime.size},${time}\n`; - } - - // https://stackoverflow.com/a/30832210 - const file = new Blob([output], {type: 'text/csv'}); - const a = document.createElement('a'); - const url = URL.createObjectURL(file); - a.href = url; - a.download = "pathfinder-benchmark-results.csv"; - document.body.appendChild(a); - a.click(); - - window.setTimeout(() => { - document.body.removeChild(a); - URL.revokeObjectURL(url); - }, 0); - } -} - -class BenchmarkTestView extends DemoView { - renderer!: BenchmarkTextRenderer | BenchmarkSVGRenderer; - - readonly appController: BenchmarkAppController; - - renderingPromiseCallback: ((time: number) => void) | null = null; - - get camera(): OrthographicCamera { - return this.renderer.camera; - } - - set size(newSize: number) { - if (this.renderer instanceof BenchmarkTextRenderer) { - this.renderer.pixelsPerEm = newSize; - } else if (this.renderer instanceof BenchmarkSVGRenderer) { - const camera = this.renderer.camera; - camera.zoomToSize(newSize); - camera.center(); - } - } - - constructor(appController: BenchmarkAppController, - areaLUT: HTMLImageElement, - gammaLUT: HTMLImageElement, - commonShaderSource: string, - shaderSources: ShaderMap) { - super(areaLUT, gammaLUT, commonShaderSource, shaderSources); - this.appController = appController; - this.recreateRenderer(); - this.resizeToFit(true); - } - - recreateRenderer(): void { - switch (this.appController.mode) { - case 'svg': - this.renderer = new BenchmarkSVGRenderer(this); - break; - case 'text': - this.renderer = new BenchmarkTextRenderer(this); - break; - } - } - - initCameraBounds(viewBox: glmatrix.vec4): void { - if (this.renderer instanceof BenchmarkSVGRenderer) - this.renderer.initCameraBounds(viewBox); - } - - protected renderingFinished(): void { - if (this.renderingPromiseCallback == null) - return; - - const appController = this.appController; - let time = this.renderer.lastTimings.rendering * 1000.0; - if (appController.mode === 'text') - time /= unwrapNull(appController.textRun).glyphIDs.length; - this.renderingPromiseCallback(time); - } -} - -class BenchmarkTextRenderer extends Renderer { - renderContext!: BenchmarkTestView; - - camera: OrthographicCamera; - - needsStencil: boolean = false; - isMulticolor: boolean = false; - - get destFramebuffer(): WebGLFramebuffer | null { - return null; - } - - get bgColor(): glmatrix.vec4 { - return glmatrix.vec4.clone([1.0, 1.0, 1.0, 0.0]); - } - - get fgColor(): glmatrix.vec4 { - return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]); - } - - get destAllocatedSize(): glmatrix.vec2 { - const canvas = this.renderContext.canvas; - return glmatrix.vec2.clone([canvas.width, canvas.height]); - } - - get destUsedSize(): glmatrix.vec2 { - return this.destAllocatedSize; - } - - get allowSubpixelAA(): boolean { - return true; - } - - 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 worldTransform(): glmatrix.mat4 { - 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; - } - - protected get objectCount(): number { - return this.meshBuffers == null ? 0 : this.meshBuffers.length; - } - - 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(renderContext: BenchmarkTestView) { - super(renderContext); - - this.camera = new OrthographicCamera(renderContext.canvas, { fixed: true }); - this.camera.onPan = () => renderContext.setDirty(); - this.camera.onZoom = () => renderContext.setDirty(); - } - - attachMeshes(meshes: PathfinderPackedMeshes[]): void { - super.attachMeshes(meshes); - - this.uploadPathColors(1); - this.uploadPathTransforms(1); - } - - pathCountForObject(objectIndex: number): number { - return STRING.length; - } - - pathBoundingRects(objectIndex: number): Float32Array { - 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(appController.textRun).glyphIDs[glyphIndex]; - - const metrics = font.metricsForGlyph(glyphID); - if (metrics == null) - continue; - - boundingRects[(glyphIndex + 1) * 4 + 0] = metrics.xMin; - boundingRects[(glyphIndex + 1) * 4 + 1] = metrics.yMin; - boundingRects[(glyphIndex + 1) * 4 + 2] = metrics.xMax; - boundingRects[(glyphIndex + 1) * 4 + 3] = metrics.yMax; - } - - return boundingRects; - } - - setHintsUniform(uniforms: UniformMap): void { - this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0); - } - - pathTransformsForObject(objectIndex: number): PathTransformBuffers { - const appController = this.renderContext.appController; - const canvas = this.renderContext.canvas; - const font = unwrapNull(appController.font); - - const pathTransforms = this.createPathTransformBuffers(STRING.length); - - let currentX = 0, currentY = 0; - const availableWidth = canvas.width / this.pixelsPerUnit; - const lineHeight = font.opentypeFont.lineHeight(); - - for (let glyphIndex = 0; glyphIndex < STRING.length; glyphIndex++) { - const glyphID = unwrapNull(appController.textRun).glyphIDs[glyphIndex]; - pathTransforms.st.set([1, 1, currentX, currentY], (glyphIndex + 1) * 4); - - currentX += font.opentypeFont.glyphs.get(glyphID).advanceWidth; - if (currentX > availableWidth) { - currentX = 0; - currentY += lineHeight; - } - } - - return pathTransforms; - } - - protected createAAStrategy(aaType: AntialiasingStrategyName, - aaLevel: number, - subpixelAA: SubpixelAAType): - AntialiasingStrategy { - return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA); - } - - protected compositeIfNecessary(): void {} - - protected updateTimings(timings: Timings): void { - // TODO(pcwalton) - } - - protected pathColorsForObject(objectIndex: number): Uint8Array { - const pathColors = new Uint8Array(4 * (STRING.length + 1)); - for (let pathIndex = 0; pathIndex < STRING.length; pathIndex++) - pathColors.set(TEXT_COLOR, (pathIndex + 1) * 4); - return pathColors; - } - - protected directCurveProgramName(): keyof ShaderMap { - return 'directCurve'; - } - - protected directInteriorProgramName(): keyof ShaderMap { - return 'directInterior'; - } -} - -class BenchmarkSVGRenderer extends SVGRenderer { - renderContext!: BenchmarkTestView; - - protected get loader(): SVGLoader { - return this.renderContext.appController.svgLoader; - } - - protected get canvas(): HTMLCanvasElement { - return this.renderContext.canvas; - } - - constructor(renderContext: BenchmarkTestView) { - super(renderContext, {sizeToFit: false}); - } -} - -function computeMedian(values: number[]): number | null { - if (values.length === 0) - return null; - const mid = values.length / 2; - if (values.length % 2 === 1) - return values[Math.floor(mid)]; - return lerp(values[mid - 1], values[mid], 0.5); -} - -class ElapsedTime { - readonly size: number; - readonly times: number[]; - - constructor(size: number) { - this.size = size; - this.times = []; - } - - get time(): number { - const median = computeMedian(this.times); - return median == null ? 0.0 : median; - } - - get timeInMS(): number { - return this.time / 1000.0; - } -} - -function main() { - const controller = new BenchmarkAppController; - window.addEventListener('load', () => controller.start(), false); -} - -main(); diff --git a/demo/client/src/buffer-texture.ts b/demo/client/src/buffer-texture.ts deleted file mode 100644 index e93ddab8..00000000 --- a/demo/client/src/buffer-texture.ts +++ /dev/null @@ -1,137 +0,0 @@ -// pathfinder/client/src/buffer-texture.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; - -import {setTextureParameters, UniformMap} from './gl-utils'; -import {assert, expectNotNull} from './utils'; - -export default class PathfinderBufferTexture { - readonly texture: WebGLTexture; - readonly uniformName: string; - - private size: glmatrix.vec2; - private glType: number; - private destroyed: boolean; - - constructor(gl: WebGLRenderingContext, uniformName: string) { - this.texture = expectNotNull(gl.createTexture(), "Failed to create buffer texture!"); - this.size = glmatrix.vec2.create(); - this.uniformName = uniformName; - this.glType = 0; - this.destroyed = false; - } - - destroy(gl: WebGLRenderingContext): void { - assert(!this.destroyed, "Buffer texture destroyed!"); - gl.deleteTexture(this.texture); - this.destroyed = true; - } - - upload(gl: WebGLRenderingContext, data: Float32Array | Uint8Array): void { - assert(!this.destroyed, "Buffer texture destroyed!"); - - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this.texture); - - const glType = data instanceof Float32Array ? gl.FLOAT : gl.UNSIGNED_BYTE; - const areaNeeded = Math.ceil(data.length / 4); - if (glType !== this.glType || areaNeeded > this.area) { - const sideLength = nextPowerOfTwo(Math.ceil(Math.sqrt(areaNeeded))); - this.size = glmatrix.vec2.clone([sideLength, sideLength]); - this.glType = glType; - - gl.texImage2D(gl.TEXTURE_2D, - 0, - gl.RGBA, - sideLength, - sideLength, - 0, - gl.RGBA, - glType, - null); - setTextureParameters(gl, gl.NEAREST); - } - - const mainDimensions = glmatrix.vec4.clone([ - 0, - 0, - this.size[0], - (areaNeeded / this.size[0]) | 0, - ]); - const remainderDimensions = glmatrix.vec4.clone([ - 0, - mainDimensions[3], - areaNeeded % this.size[0], - 1, - ]); - const splitIndex = mainDimensions[2] * mainDimensions[3] * 4; - - if (mainDimensions[2] > 0 && mainDimensions[3] > 0) { - gl.texSubImage2D(gl.TEXTURE_2D, - 0, - mainDimensions[0], - mainDimensions[1], - mainDimensions[2], - mainDimensions[3], - gl.RGBA, - this.glType, - data.slice(0, splitIndex)); - } - - if (remainderDimensions[2] > 0) { - // Round data up to a multiple of 4 elements if necessary. - let remainderLength = data.length - splitIndex; - let remainder: Float32Array | Uint8Array; - if (remainderLength % 4 === 0) { - remainder = data.slice(splitIndex); - } else { - remainderLength += 4 - remainderLength % 4; - remainder = new (data.constructor as any)(remainderLength); - remainder.set(data.slice(splitIndex)); - } - - gl.texSubImage2D(gl.TEXTURE_2D, - 0, - remainderDimensions[0], - remainderDimensions[1], - remainderDimensions[2], - remainderDimensions[3], - gl.RGBA, - this.glType, - remainder); - } - } - - bind(gl: WebGLRenderingContext, uniforms: UniformMap, textureUnit: number): void { - assert(!this.destroyed, "Buffer texture destroyed!"); - - gl.activeTexture(gl.TEXTURE0 + textureUnit); - gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.uniform2i(uniforms[`${this.uniformName}Dimensions`], this.size[0], this.size[1]); - gl.uniform1i(uniforms[this.uniformName], textureUnit); - } - - private get area(): number { - assert(!this.destroyed, "Buffer texture destroyed!"); - return this.size[0] * this.size[1]; - } -} - -/// https://stackoverflow.com/a/466242 -function nextPowerOfTwo(n: number): number { - n--; - n |= n >> 1; - n |= n >> 2; - n |= n >> 4; - n |= n >> 8; - n |= n >> 16; - return n + 1; -} diff --git a/demo/client/src/camera.ts b/demo/client/src/camera.ts deleted file mode 100644 index 505e5178..00000000 --- a/demo/client/src/camera.ts +++ /dev/null @@ -1,506 +0,0 @@ -// pathfinder/client/src/camera.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; -import * as _ from 'lodash'; - -import {EPSILON, unwrapNull} from "./utils"; -import {PathfinderView} from "./view"; - -const PIXELS_PER_LINE: number = 16.0; - -const ORTHOGRAPHIC_ZOOM_SPEED: number = 1.0 / 100.0; - -const ORTHOGRAPHIC_ZOOM_IN_FACTOR: number = 1.2; -const ORTHOGRAPHIC_ZOOM_OUT_FACTOR: number = 1.0 / ORTHOGRAPHIC_ZOOM_IN_FACTOR; - -const ORTHOGRAPHIC_DEFAULT_MIN_SCALE: number = 0.01; -const ORTHOGRAPHIC_DEFAULT_MAX_SCALE: number = 1000.0; - -const PERSPECTIVE_MOVEMENT_SPEED: number = 10.0; -const PERSPECTIVE_ROTATION_SPEED: number = 1.0 / 300.0; - -const PERSPECTIVE_MOVEMENT_VECTORS: PerspectiveMovementVectors = _.fromPairs([ - ['W'.charCodeAt(0), glmatrix.vec3.fromValues(0, 0, PERSPECTIVE_MOVEMENT_SPEED)], - ['A'.charCodeAt(0), glmatrix.vec3.fromValues(PERSPECTIVE_MOVEMENT_SPEED, 0, 0)], - ['S'.charCodeAt(0), glmatrix.vec3.fromValues(0, 0, -PERSPECTIVE_MOVEMENT_SPEED)], - ['D'.charCodeAt(0), glmatrix.vec3.fromValues(-PERSPECTIVE_MOVEMENT_SPEED, 0, 0)], -]); - -const PERSPECTIVE_MOVEMENT_INTERVAL_DELAY: number = 10; - -const PERSPECTIVE_INITIAL_TRANSLATION: glmatrix.vec3 = - glmatrix.vec3.clone([1750.0, 700.0, -1750.0]); -const PERSPECTIVE_INITIAL_ROTATION: glmatrix.vec2 = glmatrix.vec2.clone([Math.PI * 0.25, 0.0]); - -const PERSPECTIVE_OUTER_COLLISION_EXTENT: number = 3000.0; -const PERSPECTIVE_HITBOX_RADIUS: number = 1.0; - -const KEYCODES = ["W", "A", "S", "D"].map(x => x.charCodeAt(0)); - -export interface OrthographicCameraOptions { - fixed?: boolean; - minScale?: number; - maxScale?: number; - scaleBounds?: boolean; - ignoreBounds?: boolean; -} - -export interface PerspectiveCameraOptions { - innerCollisionExtent?: number; -} - -interface PerspectiveMovementVectors { - [keyCode: number]: glmatrix.vec3; -} - -interface PerspectiveMovementKeys { - [keyCode: number]: boolean; -} - -export interface CameraView { - readonly width: number; - readonly height: number; - readonly classList: DOMTokenList | null; - - addEventListener(type: K, - listener: (this: HTMLCanvasElement, - ev: HTMLElementEventMap[K]) => - any, - useCapture?: boolean): void; - getBoundingClientRect(): ClientRect; -} - -export abstract class Camera { - protected canvas: CameraView; - - constructor(canvas: CameraView) { - this.canvas = canvas; - } - - abstract zoom(scale: number): void; - abstract zoomIn(): void; - abstract zoomOut(): void; - abstract rotate(newAngle: number): void; -} - -export class OrthographicCamera extends Camera { - onPan: (() => void) | null; - onZoom: (() => void) | null; - onRotate: (() => void) | null; - - translation!: glmatrix.vec2; - scale!: number; - rotationAngle!: number; - - private _bounds: glmatrix.vec4; - - private readonly fixed: boolean; - private readonly minScale: number; - private readonly maxScale: number; - private readonly scaleBounds: boolean; - private readonly ignoreBounds: boolean; - - constructor(canvas: CameraView, options?: OrthographicCameraOptions) { - super(canvas); - - if (options == null) - options = {}; - - this.fixed = !!options.fixed; - this.minScale = _.defaultTo(options.minScale, ORTHOGRAPHIC_DEFAULT_MIN_SCALE); - this.maxScale = _.defaultTo(options.maxScale, ORTHOGRAPHIC_DEFAULT_MAX_SCALE); - this.scaleBounds = !!options.scaleBounds; - this.ignoreBounds = !!options.ignoreBounds; - - this.reset(); - - this._bounds = glmatrix.vec4.create(); - - if (!this.fixed) { - this.canvas.addEventListener('wheel', event => this.onWheel(event), false); - this.canvas.addEventListener('mousedown', event => this.onMouseDown(event), false); - this.canvas.addEventListener('mouseup', event => this.onMouseUp(event), false); - this.canvas.addEventListener('mousemove', event => this.onMouseMove(event), false); - if (this.canvas.classList != null) - this.canvas.classList.add('pf-draggable'); - } else { - if (this.canvas.classList != null) - this.canvas.classList.remove('pf-draggable'); - } - - this.onPan = null; - this.onZoom = null; - this.onRotate = null; - } - - onWheel(event: MouseWheelEvent): void { - if (this.canvas == null) - throw new Error("onWheel() with no canvas?!"); - - event.preventDefault(); - - if (!event.ctrlKey) { - const delta = glmatrix.vec2.fromValues(-event.deltaX, event.deltaY); - if (event.deltaMode === event.DOM_DELTA_LINE) - glmatrix.vec2.scale(delta, delta, PIXELS_PER_LINE); - this.pan(delta); - return; - } - - // 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 scale = 1.0 - event.deltaY * window.devicePixelRatio * ORTHOGRAPHIC_ZOOM_SPEED; - this.doZoom(scale, mouseLocation); - } - - zoomToFit(): void { - const size = this.objectSize(); - this.scale = Math.min(this.canvas.width / size[0], this.canvas.height / size[1]); - this.center(); - } - - center(): void { - const upperLeft = glmatrix.vec2.clone([this._bounds[0], this._bounds[1]]); - const lowerRight = glmatrix.vec2.clone([this._bounds[2], this._bounds[3]]); - - this.translation = glmatrix.vec2.create(); - glmatrix.vec2.lerp(this.translation, upperLeft, lowerRight, 0.5); - glmatrix.vec2.scale(this.translation, this.translation, -this.scale); - this.translation[0] += this.canvas.width * 0.5; - this.translation[1] += this.canvas.height * 0.5; - } - - zoomToSize(newSize: number): void { - this.reset(); - - const size = this.objectSize(); - const length = Math.max(size[0], size[1]); - this.zoom(newSize / length); - } - - zoom(scale: number): void { - this.doZoom(scale, this.centerPoint); - } - - zoomIn(): void { - this.doZoom(ORTHOGRAPHIC_ZOOM_IN_FACTOR, this.centerPoint); - } - - zoomOut(): void { - this.doZoom(ORTHOGRAPHIC_ZOOM_OUT_FACTOR, this.centerPoint); - } - - rotate(newAngle: number): void { - this.rotationAngle = newAngle; - - if (this.onRotate != null) - this.onRotate(); - } - - private objectSize(): glmatrix.vec2 { - const upperLeft = glmatrix.vec2.clone([this._bounds[0], this._bounds[1]]); - const lowerRight = glmatrix.vec2.clone([this._bounds[2], this._bounds[3]]); - const width = this._bounds[2] - this._bounds[0]; - const height = Math.abs(this._bounds[1] - this._bounds[3]); - return glmatrix.vec2.clone([width, height]); - } - - private reset(): void { - this.translation = glmatrix.vec2.create(); - this.scale = 1.0; - this.rotationAngle = 0.0; - } - - private onMouseDown(event: MouseEvent): void { - if (this.canvas.classList != null) - this.canvas.classList.add('pf-grabbing'); - } - - private onMouseUp(event: MouseEvent): void { - if (this.canvas.classList != null) - this.canvas.classList.remove('pf-grabbing'); - } - - private onMouseMove(event: MouseEvent): void { - if ((event.buttons & 1) !== 0) - this.pan(glmatrix.vec2.fromValues(event.movementX, -event.movementY)); - } - - private pan(delta: glmatrix.vec2): void { - // Pan event. - glmatrix.vec2.scale(delta, delta, window.devicePixelRatio); - glmatrix.vec2.add(this.translation, this.translation, delta); - - this.clampViewport(); - - if (this.onPan != null) - this.onPan(); - } - - private clampViewport() { - if (this.ignoreBounds) - return; - - const bounds = glmatrix.vec4.clone(this.bounds); - if (!this.scaleBounds) - glmatrix.vec4.scale(bounds, bounds, this.scale); - - for (let axis = 0; axis < 2; axis++) { - const viewportLength = axis === 0 ? this.canvas.width : this.canvas.height; - const axisBounds = [bounds[axis + 0], bounds[axis + 2]]; - const boundsLength = axisBounds[1] - axisBounds[0]; - if (viewportLength < boundsLength) { - // Viewport must be inside bounds. - this.translation[axis] = _.clamp(this.translation[axis], - viewportLength - axisBounds[1], - -axisBounds[0]); - } else { - // Bounds must be inside viewport. - this.translation[axis] = _.clamp(this.translation[axis], - -axisBounds[0], - viewportLength - axisBounds[1]); - } - } - } - - private doZoom(scale: number, point: glmatrix.vec2): void { - const absoluteTranslation = glmatrix.vec2.create(); - glmatrix.vec2.sub(absoluteTranslation, this.translation, point); - glmatrix.vec2.scale(absoluteTranslation, absoluteTranslation, 1.0 / this.scale); - - this.scale = _.clamp(this.scale * scale, this.minScale, this.maxScale); - - glmatrix.vec2.scale(absoluteTranslation, absoluteTranslation, this.scale); - glmatrix.vec2.add(this.translation, absoluteTranslation, point); - - this.clampViewport(); - - if (this.onZoom != null) - this.onZoom(); - } - - private get centerPoint(): glmatrix.vec2 { - return glmatrix.vec2.clone([this.canvas.width * 0.5, this.canvas.height * 0.5]); - } - - get bounds(): glmatrix.vec4 { - const bounds = glmatrix.vec4.clone(this._bounds); - if (this.scaleBounds) - glmatrix.vec4.scale(bounds, bounds, this.scale); - return bounds; - } - - set bounds(newBounds: glmatrix.vec4) { - this._bounds = glmatrix.vec4.clone(newBounds); - } -} - -export class PerspectiveCamera extends Camera { - canvas!: HTMLCanvasElement; - - onChange: (() => void) | null; - - translation: glmatrix.vec3; - - /// Yaw and pitch Euler angles. - rotation: glmatrix.vec2; - - private movementDelta: glmatrix.vec3; - // If W, A, S, D are pressed - private wasdPress: PerspectiveMovementKeys; - private movementInterval: number | null; - - private readonly innerCollisionExtent: number; - - private vrRotationMatrix: glmatrix.mat4 | null; - - constructor(canvas: HTMLCanvasElement, options?: PerspectiveCameraOptions) { - super(canvas); - - if (options == null) - options = {}; - this.innerCollisionExtent = options.innerCollisionExtent || 0.0; - - this.translation = glmatrix.vec3.clone(PERSPECTIVE_INITIAL_TRANSLATION); - this.rotation = glmatrix.vec2.clone(PERSPECTIVE_INITIAL_ROTATION); - this.movementDelta = glmatrix.vec3.create(); - this.movementInterval = null; - - this.canvas.addEventListener('mousedown', event => this.onMouseDown(event), false); - this.canvas.addEventListener('mouseup', event => this.onMouseUp(event), false); - this.canvas.addEventListener('mousemove', event => this.onMouseMove(event), false); - - window.addEventListener('keydown', event => this.onKeyDown(event), false); - window.addEventListener('keyup', event => this.onKeyUp(event), false); - - this.onChange = null; - this.vrRotationMatrix = null; - - this.wasdPress = _.fromPairs([ - ['W'.charCodeAt(0), false], - ['A'.charCodeAt(0), false], - ['S'.charCodeAt(0), false], - ['D'.charCodeAt(0), false], - ]); - } - - zoom(scale: number): void { - // TODO(pcwalton) - } - - zoomIn(): void { - // TODO(pcwalton) - } - - zoomOut(): void { - // TODO(pcwalton) - } - - rotate(newAngle: number): void { - // TODO(pcwalton) - } - - setView(rotation: glmatrix.mat4, pose: VRPose): void { - this.vrRotationMatrix = rotation; - } - - private onMouseDown(event: MouseEvent): void { - if (document.pointerLockElement !== this.canvas) { - this.canvas.requestPointerLock(); - return; - } - - this.movementDelta = glmatrix.vec3.fromValues(0.0, 0.0, PERSPECTIVE_MOVEMENT_SPEED); - if (event.button !== 1) - this.movementDelta[0] = -this.movementDelta[0]; - - this.startMoving(); - } - - private onMouseUp(event: MouseEvent): void { - this.stopMoving(); - } - - private onMouseMove(event: MouseEvent): void { - if (document.pointerLockElement !== this.canvas) - return; - - this.rotation[0] += event.movementX * PERSPECTIVE_ROTATION_SPEED; - - const trialYRotation = this.rotation[1] + event.movementY * PERSPECTIVE_ROTATION_SPEED; - this.rotation[1] = _.clamp(trialYRotation, -Math.PI * 0.5, Math.PI * 0.5); - - if (this.onChange != null) - this.onChange(); - } - - private onKeyDown(event: KeyboardEvent): void { - if (PERSPECTIVE_MOVEMENT_VECTORS.hasOwnProperty(event.keyCode)) { - // keyDown will be repeated on prolonged holds of the key, - // don't do extra computation in that case - if (this.wasdPress[event.keyCode]) - return; - this.wasdPress[event.keyCode] = true; - this.updateMovementDelta(); - this.startMoving(); - } - } - - private onKeyUp(event: KeyboardEvent): void { - if (PERSPECTIVE_MOVEMENT_VECTORS.hasOwnProperty(event.keyCode)) { - this.wasdPress[event.keyCode] = false; - if (this.updateMovementDelta()) { - this.stopMoving(); - } - } - } - - // Updates the movementDelta vector based on what keys are currently pressed - // Returns true if the vector is now empty - private updateMovementDelta(): boolean { - this.movementDelta = glmatrix.vec3.create(); - let empty = true; - for (const key of KEYCODES) { - if (this.wasdPress[key]) { - glmatrix.vec3.add(this.movementDelta, - this.movementDelta, - PERSPECTIVE_MOVEMENT_VECTORS[key]); - empty = false; - } - } - return empty; - } - - private startMoving(): void { - if (this.movementInterval == null) - this.movementInterval = window.setInterval(() => this.move(), - PERSPECTIVE_MOVEMENT_INTERVAL_DELAY); - } - - private stopMoving(): void { - if (this.movementInterval != null) { - window.clearInterval(this.movementInterval); - this.movementInterval = null; - this.movementDelta = glmatrix.vec3.create(); - } - } - - private move() { - const invRotationMatrix = glmatrix.mat4.create(); - glmatrix.mat4.invert(invRotationMatrix, this.rotationMatrix); - - const delta = glmatrix.vec3.clone(this.movementDelta); - glmatrix.vec3.transformMat4(delta, delta, invRotationMatrix); - - const trialTranslation = glmatrix.vec3.create(); - glmatrix.vec3.add(trialTranslation, this.translation, delta); - - // TODO(pcwalton): Sliding… - const absoluteTrialTranslationX = Math.abs(trialTranslation[0]); - const absoluteTrialTranslationZ = Math.abs(trialTranslation[2]); - if (absoluteTrialTranslationX < this.innerCollisionExtent + PERSPECTIVE_HITBOX_RADIUS && - absoluteTrialTranslationZ < this.innerCollisionExtent + PERSPECTIVE_HITBOX_RADIUS) { - return; - } - - if (absoluteTrialTranslationX > PERSPECTIVE_OUTER_COLLISION_EXTENT - - PERSPECTIVE_HITBOX_RADIUS) { - trialTranslation[0] = Math.sign(trialTranslation[0]) * - (PERSPECTIVE_OUTER_COLLISION_EXTENT - PERSPECTIVE_HITBOX_RADIUS); - } - if (absoluteTrialTranslationZ > PERSPECTIVE_OUTER_COLLISION_EXTENT - - PERSPECTIVE_HITBOX_RADIUS) { - trialTranslation[2] = Math.sign(trialTranslation[2]) * - (PERSPECTIVE_OUTER_COLLISION_EXTENT - PERSPECTIVE_HITBOX_RADIUS); - } - - this.translation = trialTranslation; - - if (this.onChange != null) - this.onChange(); - } - - get rotationMatrix(): glmatrix.mat4 { - if (this.vrRotationMatrix != null) { - // This actually is a rotation + translation matrix, but it works - return this.vrRotationMatrix; - } - const matrix = glmatrix.mat4.create(); - glmatrix.mat4.fromXRotation(matrix, this.rotation[1]); - glmatrix.mat4.rotateY(matrix, matrix, this.rotation[0]); - return matrix; - } -} diff --git a/demo/client/src/file-picker.ts b/demo/client/src/file-picker.ts deleted file mode 100644 index cd0d3b07..00000000 --- a/demo/client/src/file-picker.ts +++ /dev/null @@ -1,43 +0,0 @@ -// pathfinder/client/src/file-picker.ts -// -// Copyright © 2017 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. - -import {expectNotNull} from "./utils"; - -export class FilePickerView { - static create(): FilePickerView | null { - const element = document.getElementById('pf-file-select') as (HTMLInputElement | null); - return element == null ? null : new FilePickerView(element); - } - - onFileLoaded: ((fileData: ArrayBuffer) => void) | null; - - private readonly element: HTMLInputElement; - - private constructor(element: HTMLInputElement) { - this.element = element; - this.onFileLoaded = null; - element.addEventListener('change', event => this.loadFile(event), false); - } - - open() { - this.element.click(); - } - - private loadFile(event: Event) { - const element = event.target as HTMLInputElement; - const file = expectNotNull(element.files, "No file selected!")[0]; - const reader = new FileReader; - reader.addEventListener('loadend', () => { - if (this.onFileLoaded != null) - this.onFileLoaded(reader.result); - }, false); - reader.readAsArrayBuffer(file); - } -} diff --git a/demo/client/src/gl-utils.ts b/demo/client/src/gl-utils.ts deleted file mode 100644 index 735f79b2..00000000 --- a/demo/client/src/gl-utils.ts +++ /dev/null @@ -1,133 +0,0 @@ -// pathfinder/client/src/gl-utils.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; -import * as _ from 'lodash'; - -import {assert, UINT32_SIZE, unwrapNull} from './utils'; -import {ColorAlphaFormat, DemoView} from './view'; - -export type WebGLVertexArrayObject = any; - -export interface AttributeMap { - [attributeName: string]: number; -} - -export interface UniformMap { - [uniformName: string]: WebGLUniformLocation; -} - -export interface EXTDisjointTimerQuery { - readonly QUERY_COUNTER_BITS_EXT: GLenum; - readonly CURRENT_QUERY_EXT: GLenum; - readonly QUERY_RESULT_EXT: GLenum; - readonly QUERY_RESULT_AVAILABLE_EXT: GLenum; - readonly TIME_ELAPSED_EXT: GLenum; - readonly TIMESTAMP_EXT: GLenum; - readonly GPU_DISJOINT_EXT: GLenum; - createQueryEXT(): WebGLQuery; - deleteQueryEXT(query: WebGLQuery): void; - isQueryEXT(query: any): GLboolean; - beginQueryEXT(target: GLenum, query: WebGLQuery): void; - endQueryEXT(target: GLenum): void; - queryCounterEXT(query: WebGLQuery, target: GLenum): void; - getQueryEXT(target: GLenum, pname: GLenum): any; - getQueryObjectEXT(query: WebGLQuery, pname: GLenum): any; -} - -export class WebGLQuery {} - -export const QUAD_ELEMENTS: Uint8Array = new Uint8Array([0, 1, 2, 1, 3, 2]); - -export function createFramebufferColorTexture(gl: WebGLRenderingContext, - size: glmatrix.vec2, - colorAlphaFormat: ColorAlphaFormat, - filter?: number): - WebGLTexture { - // Firefox seems to have a bug whereby textures don't get marked as initialized when cleared - // if they're anything other than the first attachment of an FBO. To work around this, supply - // zero data explicitly when initializing the texture. - let format, type, internalFormat, bitDepth, zeroes; - if (colorAlphaFormat === 'RGBA8') { - format = internalFormat = gl.RGBA; - type = gl.UNSIGNED_BYTE; - bitDepth = 32; - zeroes = new Uint8Array(size[0] * size[1] * 4); - } else { - format = internalFormat = gl.RGBA; - type = gl.UNSIGNED_SHORT_5_5_5_1; - bitDepth = 16; - zeroes = new Uint16Array(size[0] * size[1] * 4); - } - - const texture = unwrapNull(gl.createTexture()); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, size[0], size[1], 0, format, type, zeroes); - setTextureParameters(gl, _.defaultTo(filter, gl.NEAREST)); - return texture; -} - -export function createFramebufferDepthTexture(gl: WebGLRenderingContext, size: glmatrix.vec2): - WebGLTexture { - const texture = unwrapNull(gl.createTexture()); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D(gl.TEXTURE_2D, - 0, - gl.DEPTH_COMPONENT, - size[0], - size[1], - 0, - gl.DEPTH_COMPONENT, - gl.UNSIGNED_INT, - null); - setTextureParameters(gl, gl.NEAREST); - return texture; -} - -export function setTextureParameters(gl: WebGLRenderingContext, filter: number) { - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter); -} - -export function createFramebuffer(gl: WebGLRenderingContext, - colorAttachment: WebGLTexture, - depthAttachment: WebGLTexture | null): - WebGLFramebuffer { - const framebuffer = unwrapNull(gl.createFramebuffer()); - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); - - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0, - gl.TEXTURE_2D, - colorAttachment, - 0); - - if (depthAttachment != null) { - gl.framebufferTexture2D(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - depthAttachment, - 0); - assert(gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE) === - gl.TEXTURE, - "Failed to attach depth texture!"); - - } - - assert(gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE, - "Framebuffer was incomplete!"); - return framebuffer; -} diff --git a/demo/client/src/mesh-debugger.ts b/demo/client/src/mesh-debugger.ts deleted file mode 100644 index 40947ca4..00000000 --- a/demo/client/src/mesh-debugger.ts +++ /dev/null @@ -1,564 +0,0 @@ -// pathfinder/client/src/mesh-debugger.ts -// -// 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. - -import * as glmatrix from 'gl-matrix'; -import * as opentype from "opentype.js"; - -import {Font} from 'opentype.js'; -import {AppController} from "./app-controller"; -import {OrthographicCamera} from "./camera"; -import {FilePickerView} from './file-picker'; -import {B_QUAD_UPPER_RIGHT_VERTEX_OFFSET, PathfinderMeshPack} from "./meshes"; -import {B_QUAD_LOWER_LEFT_VERTEX_OFFSET, B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET} from "./meshes"; -import {B_QUAD_LOWER_RIGHT_VERTEX_OFFSET} from "./meshes"; -import {B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET, PathfinderPackedMeshes} from "./meshes"; -import {B_QUAD_SIZE, B_QUAD_UPPER_LEFT_VERTEX_OFFSET} from "./meshes"; -import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader'; -import {BUILTIN_FONT_URI, TextRun} from "./text"; -import {GlyphStore, PathfinderFont, TextFrame} from "./text"; -import {assert, UINT32_MAX, UINT32_SIZE, unwrapNull} from "./utils"; -import {PathfinderView} from "./view"; - -const CHARACTER: string = 'A'; - -const FONT: string = 'eb-garamond'; - -const POINT_LABEL_FONT: string = "sans-serif"; -const POINT_LABEL_FONT_SIZE: number = 12.0; -const POINT_LABEL_OFFSET: glmatrix.vec2 = glmatrix.vec2.fromValues(12.0, 12.0); -const POINT_RADIUS: number = 2.0; - -const SEGMENT_POINT_RADIUS: number = 3.0; -const SEGMENT_STROKE_WIDTH: number = 1.0; -const SEGMENT_CONTROL_POINT_STROKE_WIDTH: number = 1.0; - -const NORMAL_LENGTHS: NormalStyleParameter = { - bVertex: 10.0, - edge: 14.0, -}; - -const NORMAL_ARROWHEAD_LENGTH: number = 4.0; -const NORMAL_ARROWHEAD_ANGLE: number = Math.PI * 5.0 / 6.0; - -const SEGMENT_POINT_FILL_STYLE: string = "rgb(0, 0, 128)"; - -const LIGHT_STROKE_STYLE: string = "rgb(192, 192, 192)"; -const LINE_STROKE_STYLE: string = "rgb(0, 128, 0)"; -const CURVE_STROKE_STYLE: string = "rgb(128, 0, 0)"; -const SEGMENT_LINE_STROKE_STYLE: string = "rgb(128, 192, 128)"; -const SEGMENT_CONTROL_POINT_FILL_STYLE: string = "rgb(255, 255, 255)"; -const SEGMENT_CONTROL_POINT_STROKE_STYLE: string = "rgb(0, 0, 128)"; -const SEGMENT_CONTROL_POINT_HULL_STROKE_STYLE: string = "rgba(128, 128, 128, 0.5)"; - -const NORMAL_STROKE_STYLES: NormalStyleParameter = { - bVertex: '#e6aa00', - edge: '#cc5500', -}; - -const BUILTIN_URIS = { - font: BUILTIN_FONT_URI, - svg: BUILTIN_SVG_URI, -}; - -const SVG_SCALE: number = 1.0; - -type FileType = 'font' | 'svg'; - -type NormalType = 'edge' | 'bVertex'; - -interface NormalStyleParameter { - edge: T; - bVertex: T; -} - -interface NormalsTable { - lowerCurve: T; - lowerLine: T; - upperCurve: T; - upperLine: T; -} - -class MeshDebuggerAppController extends AppController { - meshes: PathfinderPackedMeshes | null = null; - - protected readonly defaultFile: string = FONT; - - private file: PathfinderFont | SVGLoader | null = null; - private fileType!: FileType; - private fileData: ArrayBuffer | null = null; - - private openModal!: HTMLElement; - private openFileSelect!: HTMLSelectElement; - private fontPathSelectGroup!: HTMLElement; - private fontPathSelect!: HTMLSelectElement; - - private filePicker!: FilePickerView; - private view!: MeshDebuggerView; - - start() { - super.start(); - - this.fileType = 'font'; - - this.view = new MeshDebuggerView(this); - - this.filePicker = unwrapNull(FilePickerView.create()); - this.filePicker.onFileLoaded = fileData => this.fileLoaded(fileData, null); - - this.openModal = unwrapNull(document.getElementById('pf-open-modal')); - this.fontPathSelectGroup = - unwrapNull(document.getElementById('pf-font-path-select-group')); - this.fontPathSelect = unwrapNull(document.getElementById('pf-font-path-select')) as - HTMLSelectElement; - - this.openFileSelect = unwrapNull(document.getElementById('pf-open-file-select')) as - HTMLSelectElement; - this.openFileSelect.addEventListener('click', () => this.openSelectedFile(), false); - - const openButton = unwrapNull(document.getElementById('pf-open-button')); - openButton.addEventListener('click', () => this.showOpenDialog(), false); - - const openOKButton = unwrapNull(document.getElementById('pf-open-ok-button')); - openOKButton.addEventListener('click', () => this.loadPath(), false); - - this.loadInitialFile(BUILTIN_FONT_URI); - } - - protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void { - while (this.fontPathSelect.lastChild != null) - this.fontPathSelect.removeChild(this.fontPathSelect.lastChild); - - this.fontPathSelectGroup.classList.remove('pf-display-none'); - - if (this.fileType === 'font') - this.fontLoaded(fileData, builtinName); - else if (this.fileType === 'svg') - this.svgLoaded(fileData); - } - - protected loadPath(opentypeGlyph?: opentype.Glyph | null) { - window.jQuery(this.openModal).modal('hide'); - - let promise: Promise; - - if (this.file instanceof PathfinderFont && this.fileData != null) { - if (opentypeGlyph == null) { - const glyphIndex = parseInt(this.fontPathSelect.selectedOptions[0].value, 10); - opentypeGlyph = this.file.opentypeFont.glyphs.get(glyphIndex); - } - - const glyphStorage = new GlyphStore(this.file, [(opentypeGlyph as any).index]); - promise = glyphStorage.partition().then(result => result.meshes); - } else if (this.file instanceof SVGLoader) { - promise = this.file.partition(this.fontPathSelect.selectedIndex); - } else { - return; - } - - promise.then(meshes => { - this.meshes = new PathfinderPackedMeshes(meshes); - this.view.attachMeshes(); - }); - } - - private showOpenDialog(): void { - window.jQuery(this.openModal).modal(); - } - - private openSelectedFile(): void { - const selectedOption = this.openFileSelect.selectedOptions[0] as HTMLOptionElement; - const optionValue = selectedOption.value; - - this.fontPathSelectGroup.classList.add('pf-display-none'); - - const results = unwrapNull(/^([a-z]+)-(.*)$/.exec(optionValue)); - this.fileType = results[1] as FileType; - - const filename = results[2]; - if (filename === 'custom') - this.filePicker.open(); - else - this.fetchFile(results[2], BUILTIN_URIS[this.fileType]); - } - - private fontLoaded(fileData: ArrayBuffer, builtinName: string | null): void { - this.file = new PathfinderFont(fileData, builtinName); - this.fileData = fileData; - - const glyphCount = this.file.opentypeFont.numGlyphs; - for (let glyphIndex = 1; glyphIndex < glyphCount; glyphIndex++) { - const newOption = document.createElement('option'); - newOption.value = "" + glyphIndex; - const glyphName = this.file.opentypeFont.glyphIndexToName(glyphIndex); - newOption.appendChild(document.createTextNode(glyphName)); - this.fontPathSelect.appendChild(newOption); - } - - // Automatically load a path if this is the initial pageload. - if (this.meshes == null) - this.loadPath(this.file.opentypeFont.charToGlyph(CHARACTER)); - } - - private svgLoaded(fileData: ArrayBuffer): void { - this.file = new SVGLoader; - this.file.scale = SVG_SCALE; - this.file.loadFile(fileData); - - const pathCount = this.file.pathInstances.length; - for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) { - const newOption = document.createElement('option'); - newOption.value = "" + pathIndex; - newOption.appendChild(document.createTextNode(`Path ${pathIndex}`)); - this.fontPathSelect.appendChild(newOption); - } - } -} - -class MeshDebuggerView extends PathfinderView { - camera: OrthographicCamera; - - private appController: MeshDebuggerAppController; - - private drawControl: boolean = true; - private drawNormals: boolean = true; - private drawVertices: boolean = true; - private drawSegments: boolean = false; - - constructor(appController: MeshDebuggerAppController) { - super(); - - this.appController = appController; - this.camera = new OrthographicCamera(this.canvas, { ignoreBounds: true }); - - this.camera.onPan = () => this.setDirty(); - this.camera.onZoom = () => this.setDirty(); - - window.addEventListener('keypress', event => this.onKeyPress(event), false); - - this.resizeToFit(true); - } - - attachMeshes() { - this.setDirty(); - } - - redraw() { - super.redraw(); - - const meshes = this.appController.meshes; - if (meshes == null) - return; - - const context = unwrapNull(this.canvas.getContext('2d')); - context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - context.save(); - context.translate(this.camera.translation[0], - this.canvas.height - this.camera.translation[1]); - context.scale(this.camera.scale, this.camera.scale); - - const invScaleFactor = window.devicePixelRatio / this.camera.scale; - context.font = `12px ${POINT_LABEL_FONT}`; - context.lineWidth = invScaleFactor; - - const bQuadVertexPositions = new Float32Array(meshes.bQuadVertexPositions); - - const normals: NormalsTable = { - lowerCurve: new Float32Array(0), - lowerLine: new Float32Array(0), - upperCurve: new Float32Array(0), - upperLine: new Float32Array(0), - }; - - // Draw B-quads. - for (let bQuadIndex = 0; bQuadIndex < meshes.bQuadVertexPositions.length; bQuadIndex++) { - const bQuadStartOffset = (B_QUAD_SIZE * bQuadIndex) / UINT32_SIZE; - - const upperLeftPosition = getPosition(bQuadVertexPositions, bQuadIndex, 0); - const upperControlPointPosition = getPosition(bQuadVertexPositions, bQuadIndex, 1); - const upperRightPosition = getPosition(bQuadVertexPositions, bQuadIndex, 2); - const lowerRightPosition = getPosition(bQuadVertexPositions, bQuadIndex, 3); - const lowerControlPointPosition = getPosition(bQuadVertexPositions, bQuadIndex, 4); - const lowerLeftPosition = getPosition(bQuadVertexPositions, bQuadIndex, 5); - - if (this.drawVertices) { - drawVertexIfNecessary(context, upperLeftPosition, invScaleFactor); - drawVertexIfNecessary(context, upperRightPosition, invScaleFactor); - drawVertexIfNecessary(context, lowerLeftPosition, invScaleFactor); - drawVertexIfNecessary(context, lowerRightPosition, invScaleFactor); - } - - context.beginPath(); - context.moveTo(upperLeftPosition[0], -upperLeftPosition[1]); - if (upperControlPointPosition != null) { - context.strokeStyle = CURVE_STROKE_STYLE; - context.quadraticCurveTo(upperControlPointPosition[0], - -upperControlPointPosition[1], - upperRightPosition[0], - -upperRightPosition[1]); - } else { - context.strokeStyle = LINE_STROKE_STYLE; - context.lineTo(upperRightPosition[0], -upperRightPosition[1]); - } - context.stroke(); - - context.strokeStyle = LIGHT_STROKE_STYLE; - context.beginPath(); - context.moveTo(upperRightPosition[0], -upperRightPosition[1]); - context.lineTo(lowerRightPosition[0], -lowerRightPosition[1]); - context.stroke(); - - context.beginPath(); - context.moveTo(lowerRightPosition[0], -lowerRightPosition[1]); - if (lowerControlPointPosition != null) { - context.strokeStyle = CURVE_STROKE_STYLE; - context.quadraticCurveTo(lowerControlPointPosition[0], - -lowerControlPointPosition[1], - lowerLeftPosition[0], - -lowerLeftPosition[1]); - } else { - context.strokeStyle = LINE_STROKE_STYLE; - context.lineTo(lowerLeftPosition[0], -lowerLeftPosition[1]); - } - context.stroke(); - - context.strokeStyle = LIGHT_STROKE_STYLE; - context.beginPath(); - context.moveTo(lowerLeftPosition[0], -lowerLeftPosition[1]); - context.lineTo(upperLeftPosition[0], -upperLeftPosition[1]); - context.stroke(); - } - - // Draw segments. - if (this.drawVertices) { - drawSegmentVertices(context, - new Float32Array(meshes.stencilSegments), - new Float32Array(meshes.stencilNormals), - meshes.count('stencilSegments'), - [0, 2], - 1, - 3, - invScaleFactor, - this.drawControl, - this.drawNormals, - this.drawSegments); - } - context.restore(); - } - - private onKeyPress(event: KeyboardEvent): void { - if (event.key === "c") { - this.drawControl = !this.drawControl; - } else if (event.key === "n") { - this.drawNormals = !this.drawNormals; - } else if (event.key === "v") { - this.drawVertices = !this.drawVertices; - } else if (event.key === "r") { - // Reset - this.drawControl = true; - this.drawNormals = true; - this.drawVertices = true; - } - this.setDirty(); - } -} - -function getPosition(positions: Float32Array, bQuadIndex: number, vertexIndex: number): - glmatrix.vec2 { - return glmatrix.vec2.clone([ - positions[(bQuadIndex * 6 + vertexIndex) * 2 + 0], - positions[(bQuadIndex * 6 + vertexIndex) * 2 + 1], - ]); -} - -function getNormal(normals: Float32Array, bQuadIndex: number, vertexIndex: number): number { - return normals[bQuadIndex * 6 + vertexIndex]; -} - -function getNormals(normals: NormalsTable, - normalIndices: NormalsTable, - isCurve: boolean, - side: 'upper' | 'lower'): - { left: number, right: number } { - const key: keyof NormalsTable = (side + (isCurve ? 'Curve' : 'Line')) as keyof - NormalsTable; - const startOffset = normalIndices[key]; - normalIndices[key]++; - return { - left: normals[key][startOffset * 2 + 0], - right: normals[key][startOffset * 2 + 1], - }; -} - -function drawSegmentVertices(context: CanvasRenderingContext2D, - segments: Float32Array, - normals: Float32Array, - segmentCount: number, - endpointOffsets: number[], - controlPointOffset: number | null, - segmentSize: number, - invScaleFactor: number, - drawControl: boolean, - drawNormals: boolean, - drawSegments: boolean) { - for (let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex++) { - const positionStartOffset = segmentSize * 2 * segmentIndex; - const normalStartOffset = segmentSize * 2 * segmentIndex; - - const position0 = - glmatrix.vec2.clone([segments[positionStartOffset + endpointOffsets[0] * 2 + 0], - segments[positionStartOffset + endpointOffsets[0] * 2 + 1]]); - const position1 = - glmatrix.vec2.clone([segments[positionStartOffset + endpointOffsets[1] * 2 + 0], - segments[positionStartOffset + endpointOffsets[1] * 2 + 1]]); - - let controlPoint: glmatrix.vec2 | null; - if (controlPointOffset != null) { - controlPoint = - glmatrix.vec2.clone([segments[positionStartOffset + controlPointOffset * 2 + 0], - segments[positionStartOffset + controlPointOffset * 2 + 1]]); - } else { - controlPoint = null; - } - - const normal0 = - glmatrix.vec2.clone([normals[normalStartOffset + endpointOffsets[0] * 2 + 0], - normals[normalStartOffset + endpointOffsets[0] * 2 + 1]]); - const normal1 = - glmatrix.vec2.clone([normals[normalStartOffset + endpointOffsets[1] * 2 + 0], - normals[normalStartOffset + endpointOffsets[1] * 2 + 1]]); - - let normalControlPoint: glmatrix.vec2 | null; - if (controlPointOffset != null) { - normalControlPoint = - glmatrix.vec2.clone([normals[normalStartOffset + controlPointOffset * 2 + 0], - normals[normalStartOffset + controlPointOffset * 2 + 1]]); - } else { - normalControlPoint = null; - } - - if (drawNormals) { - drawNormal(context, position0, normal0, invScaleFactor, 'edge'); - drawNormal(context, position1, normal1, invScaleFactor, 'edge'); - if (drawControl && controlPoint != null && normalControlPoint != null) - drawNormal(context, controlPoint, normalControlPoint, invScaleFactor, 'edge'); - } - - drawSegmentVertex(context, position0, invScaleFactor); - drawSegmentVertex(context, position1, invScaleFactor); - if (drawControl && controlPoint != null) { - context.save(); - context.strokeStyle = SEGMENT_CONTROL_POINT_HULL_STROKE_STYLE; - context.setLineDash([2 * invScaleFactor, 2 * invScaleFactor]); - context.beginPath(); - context.moveTo(position0[0], -position0[1]); - context.lineTo(controlPoint[0], -controlPoint[1]); - context.lineTo(position1[0], -position1[1]); - context.stroke(); - context.restore(); - drawSegmentControlPoint(context, controlPoint, invScaleFactor); - } - - // TODO(pcwalton): Draw the curves too. - if (drawSegments) { - context.save(); - context.strokeStyle = SEGMENT_LINE_STROKE_STYLE; - context.beginPath(); - context.moveTo(position0[0], -position0[1]); - context.lineTo(position1[0], -position1[1]); - context.stroke(); - context.restore(); - } - } -} - -function drawVertexIfNecessary(context: CanvasRenderingContext2D, - position: Float32Array, - invScaleFactor: number) { - context.beginPath(); - context.moveTo(position[0], -position[1]); - context.arc(position[0], -position[1], POINT_RADIUS * invScaleFactor, 0, 2.0 * Math.PI); - context.fill(); -} - -function drawSegmentVertex(context: CanvasRenderingContext2D, - position: glmatrix.vec2, - invScaleFactor: number) { - context.save(); - context.fillStyle = SEGMENT_POINT_FILL_STYLE; - context.lineWidth = invScaleFactor * SEGMENT_STROKE_WIDTH; - context.beginPath(); - context.arc(position[0], - -position[1], - SEGMENT_POINT_RADIUS * invScaleFactor, - 0, - 2.0 * Math.PI); - context.fill(); - context.restore(); -} - -function drawSegmentControlPoint(context: CanvasRenderingContext2D, - position: glmatrix.vec2, - invScaleFactor: number) { - context.save(); - context.strokeStyle = SEGMENT_CONTROL_POINT_STROKE_STYLE; - context.fillStyle = SEGMENT_CONTROL_POINT_FILL_STYLE; - context.lineWidth = invScaleFactor * SEGMENT_CONTROL_POINT_STROKE_WIDTH; - context.beginPath(); - context.arc(position[0], - -position[1], - SEGMENT_POINT_RADIUS * invScaleFactor, - 0, - 2.0 * Math.PI); - context.fill(); - context.stroke(); - context.restore(); -} - -function drawNormal(context: CanvasRenderingContext2D, - position: glmatrix.vec2, - normalVector: glmatrix.vec2, - invScaleFactor: number, - normalType: NormalType) { - const length = invScaleFactor * NORMAL_LENGTHS[normalType]; - const arrowheadLength = invScaleFactor * NORMAL_ARROWHEAD_LENGTH; - const endpoint = glmatrix.vec2.clone([position[0] + length * normalVector[0], - -position[1] + length * -normalVector[1]]); - - context.save(); - context.strokeStyle = NORMAL_STROKE_STYLES[normalType]; - context.beginPath(); - context.moveTo(position[0], -position[1]); - context.lineTo(endpoint[0], endpoint[1]); - context.lineTo(endpoint[0] + arrowheadLength * - (Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[0] + - Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[1]), - endpoint[1] + arrowheadLength * - (Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[0] - - Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[1])); - context.stroke(); - context.beginPath(); - context.moveTo(endpoint[0], endpoint[1]); - context.lineTo(endpoint[0] + arrowheadLength * - (Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[0] - - Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[1]), - endpoint[1] - arrowheadLength * - (Math.cos(NORMAL_ARROWHEAD_ANGLE) * normalVector[1] + - Math.sin(NORMAL_ARROWHEAD_ANGLE) * normalVector[0])); - context.stroke(); - context.restore(); -} - -function main() { - const controller = new MeshDebuggerAppController; - window.addEventListener('load', () => controller.start(), false); -} - -main(); diff --git a/demo/client/src/meshes.ts b/demo/client/src/meshes.ts deleted file mode 100644 index adcadf60..00000000 --- a/demo/client/src/meshes.ts +++ /dev/null @@ -1,391 +0,0 @@ -// pathfinder/client/src/meshes.ts -// -// 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. - -import * as base64js from 'base64-js'; - -import * as _ from 'lodash'; -import {expectNotNull, FLOAT32_SIZE, panic, PathfinderError, Range, UINT16_SIZE} from './utils'; -import {UINT32_MAX, UINT32_SIZE, UINT8_SIZE, unwrapNull, unwrapUndef} from './utils'; - -interface BufferTypeFourCCTable { - [fourCC: string]: keyof MeshLike; -} - -interface PathRangeTypeFourCCTable { - [fourCC: string]: keyof PathRanges; -} - -interface RangeToCountTable { - [rangeKey: string]: keyof MeshDataCounts; -} - -type PathIDBufferTable = Partial>; - -interface ArrayLike { - readonly length: number; -} - -interface VertexCopyResult { - originalStartIndex: number; - originalEndIndex: number; - expandedStartIndex: number; - expandedEndIndex: number; -} - -type PrimitiveType = 'Uint16' | 'Uint32' | 'Float32'; - -type PrimitiveTypeArray = Float32Array | Uint16Array | Uint32Array; - -type MeshBufferType = keyof MeshLike; - -type PackedMeshBufferType = keyof PackedMeshLike; - -interface MeshBufferTypeDescriptor { - type: PrimitiveType; - size: number; -} - -const PRIMITIVE_TYPE_ARRAY_CONSTRUCTORS = { - Float32: Float32Array, - Uint16: Uint16Array, - Uint32: Uint32Array, -}; - -export const B_QUAD_SIZE: number = 4 * 8; -export const B_QUAD_UPPER_LEFT_VERTEX_OFFSET: number = 4 * 0; -export const B_QUAD_UPPER_RIGHT_VERTEX_OFFSET: number = 4 * 1; -export const B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET: number = 4 * 2; -export const B_QUAD_LOWER_LEFT_VERTEX_OFFSET: number = 4 * 4; -export const B_QUAD_LOWER_RIGHT_VERTEX_OFFSET: number = 4 * 5; -export const B_QUAD_LOWER_CONTROL_POINT_VERTEX_OFFSET: number = 4 * 6; -export const B_QUAD_UPPER_INDICES_OFFSET: number = B_QUAD_UPPER_LEFT_VERTEX_OFFSET; -export const B_QUAD_LOWER_INDICES_OFFSET: number = B_QUAD_LOWER_LEFT_VERTEX_OFFSET; - -const B_QUAD_FIELD_COUNT: number = B_QUAD_SIZE / UINT32_SIZE; - -// FIXME(pcwalton): This duplicates information below in `MESH_TYPES`. -const INDEX_SIZE: number = 4; -const B_QUAD_VERTEX_POSITION_SIZE: number = 12 * 4; -const B_VERTEX_POSITION_SIZE: number = 4 * 2; - -const MESH_TYPES: PackedMeshLike = { - bBoxPathIDs: { type: 'Uint16', size: 1 }, - bBoxes: { type: 'Float32', size: 20 }, - bQuadVertexInteriorIndices: { type: 'Uint32', size: 1 }, - bQuadVertexPositionPathIDs: { type: 'Uint16', size: 1 }, - bQuadVertexPositions: { type: 'Float32', size: 2 }, - stencilNormals: { type: 'Float32', size: 6 }, - stencilSegmentPathIDs: { type: 'Uint16', size: 1 }, - stencilSegments: { type: 'Float32', size: 6 }, -}; - -const BUFFER_TYPES: PackedMeshLike = { - bBoxPathIDs: 'ARRAY_BUFFER', - bBoxes: 'ARRAY_BUFFER', - bQuadVertexInteriorIndices: 'ELEMENT_ARRAY_BUFFER', - bQuadVertexPositionPathIDs: 'ARRAY_BUFFER', - bQuadVertexPositions: 'ARRAY_BUFFER', - stencilNormals: 'ARRAY_BUFFER', - stencilSegmentPathIDs: 'ARRAY_BUFFER', - stencilSegments: 'ARRAY_BUFFER', -}; - -const EDGE_BUFFER_NAMES = ['UpperLine', 'UpperCurve', 'LowerLine', 'LowerCurve']; - -const RIFF_FOURCC: string = 'RIFF'; - -const MESH_PACK_FOURCC: string = 'PFMP'; - -const MESH_FOURCC: string = 'mesh'; - -// Must match the FourCCs in `pathfinder_partitioner::mesh_library::MeshLibrary::serialize_into()`. -const BUFFER_TYPE_FOURCCS: BufferTypeFourCCTable = { - bbox: 'bBoxes', - bqii: 'bQuadVertexInteriorIndices', - bqvp: 'bQuadVertexPositions', - snor: 'stencilNormals', - sseg: 'stencilSegments', -}; - -const RANGE_TO_COUNT_TABLE: RangeToCountTable = { - bBoxPathRanges: 'bBoxCount', - bQuadVertexInteriorIndexPathRanges: 'bQuadVertexInteriorIndexCount', - bQuadVertexPositionPathRanges: 'bQuadVertexPositionCount', - stencilSegmentPathRanges: 'stencilSegmentCount', -}; - -const INDEX_TYPE_DESCRIPTOR_TABLE: {[P in MeshBufferType]?: IndexTypeDescriptor} = { - bQuadVertexInteriorIndices: { - bufferType: 'bQuadVertexPositions', - }, -}; - -const PATH_ID_BUFFER_TABLE: PathIDBufferTable = { - bBoxes: 'bBoxPathIDs', - bQuadVertexPositions: 'bQuadVertexPositionPathIDs', - stencilSegments: 'stencilSegmentPathIDs', -}; - -const PATH_RANGE_TO_BUFFER_TYPE_TABLE: {[P in keyof PathRanges]: MeshBufferType} = { - bBoxPathRanges: 'bBoxes', - bQuadVertexInteriorIndexPathRanges: 'bQuadVertexInteriorIndices', - bQuadVertexPositionPathRanges: 'bQuadVertexPositions', - stencilSegmentPathRanges: 'stencilSegments', -}; - -type BufferType = 'ARRAY_BUFFER' | 'ELEMENT_ARRAY_BUFFER'; - -export interface MeshBuilder { - bQuadVertexPositions: T; - bQuadVertexInteriorIndices: T; - bBoxes: T; - stencilSegments: T; - stencilNormals: T; -} - -export interface PackedMeshBuilder extends MeshBuilder { - bBoxPathIDs: T; - bQuadVertexPositionPathIDs: T; - stencilSegmentPathIDs: T; -} - -export type MeshLike = { - readonly [P in keyof MeshBuilder]: T; -}; - -export type PackedMeshLike = { - readonly [P in keyof PackedMeshBuilder]: T; -}; - -interface PathRanges { - readonly bBoxPathRanges: Range[]; - readonly bQuadVertexInteriorIndexPathRanges: Range[]; - readonly bQuadVertexPositionPathRanges: Range[]; - readonly stencilSegmentPathRanges: Range[]; -} - -interface MeshDataCounts { - readonly bQuadVertexPositionCount: number; - readonly bQuadVertexInteriorIndexCount: number; - readonly bBoxCount: number; - readonly stencilSegmentCount: number; -} - -interface IndexTypeDescriptor { - bufferType: MeshBufferType; -} - -export class PathfinderMeshPack { - meshes: PathfinderMesh[]; - - constructor(meshes: ArrayBuffer) { - this.meshes = []; - - // RIFF encoded data. - if (toFourCC(meshes, 0) !== RIFF_FOURCC) - panic("Supplied array buffer is not a mesh library (no RIFF header)!"); - if (toFourCC(meshes, 8) !== MESH_PACK_FOURCC) - panic("Supplied array buffer is not a mesh library (no PFMP header)!"); - - let offset = 12; - while (offset < meshes.byteLength) { - const fourCC = toFourCC(meshes, offset); - const chunkLength = readUInt32(meshes, offset + 4); - const startOffset = offset + 8; - const endOffset = startOffset + chunkLength; - - if (fourCC === MESH_FOURCC) - this.meshes.push(new PathfinderMesh(meshes.slice(startOffset, endOffset))); - - offset = endOffset; - } - } -} - -export class PathfinderMesh implements MeshLike { - bQuadVertexPositions!: ArrayBuffer; - bQuadVertexInteriorIndices!: ArrayBuffer; - bBoxes!: ArrayBuffer; - stencilSegments!: ArrayBuffer; - stencilNormals!: ArrayBuffer; - - constructor(data: ArrayBuffer) { - let offset = 0; - while (offset < data.byteLength) { - const fourCC = toFourCC(data, offset); - const chunkLength = readUInt32(data, offset + 4); - const startOffset = offset + 8; - const endOffset = startOffset + chunkLength; - - if (BUFFER_TYPE_FOURCCS.hasOwnProperty(fourCC)) - this[BUFFER_TYPE_FOURCCS[fourCC]] = data.slice(startOffset, endOffset); - - offset = endOffset; - } - - for (const type of Object.keys(BUFFER_TYPE_FOURCCS) as Array>) { - if (this[type] == null) - this[type] = new ArrayBuffer(0); - } - } -} - -export class PathfinderPackedMeshes implements PackedMeshLike, PathRanges { - readonly bBoxes!: Float32Array; - readonly bQuadVertexInteriorIndices!: Uint32Array; - readonly bQuadVertexPositions!: Float32Array; - readonly stencilSegments!: Float32Array; - readonly stencilNormals!: Float32Array; - - readonly bBoxPathIDs!: Uint16Array; - readonly bQuadVertexPositionPathIDs!: Uint16Array; - readonly stencilSegmentPathIDs!: Uint16Array; - - readonly bBoxPathRanges!: Range[]; - readonly bQuadVertexInteriorIndexPathRanges!: Range[]; - readonly bQuadVertexPositionPathRanges!: Range[]; - readonly stencilSegmentPathRanges!: Range[]; - - /// NB: Mesh indices are 1-indexed. - constructor(meshPack: PathfinderMeshPack, meshIndices?: number[]) { - if (meshIndices == null) - meshIndices = meshPack.meshes.map((value, index) => index + 1); - - const meshData: PackedMeshBuilder = { - bBoxPathIDs: [], - bBoxes: [], - bQuadVertexInteriorIndices: [], - bQuadVertexPositionPathIDs: [], - bQuadVertexPositions: [], - stencilNormals: [], - stencilSegmentPathIDs: [], - stencilSegments: [], - }; - const pathRanges: PathRanges = { - bBoxPathRanges: [], - bQuadVertexInteriorIndexPathRanges: [], - bQuadVertexPositionPathRanges: [], - stencilSegmentPathRanges: [], - }; - - for (let destMeshIndex = 0; destMeshIndex < meshIndices.length; destMeshIndex++) { - const srcMeshIndex = meshIndices[destMeshIndex]; - const mesh = meshPack.meshes[srcMeshIndex - 1]; - - for (const pathRangeType of Object.keys(pathRanges) as Array) { - const bufferType = PATH_RANGE_TO_BUFFER_TYPE_TABLE[pathRangeType]; - const startIndex = bufferCount(meshData, bufferType); - pathRanges[pathRangeType].push(new Range(startIndex, startIndex)); - } - - for (const indexType of Object.keys(BUFFER_TYPES) as MeshBufferType[]) { - if (BUFFER_TYPES[indexType] !== 'ELEMENT_ARRAY_BUFFER') - continue; - const indexTypeDescriptor = unwrapUndef(INDEX_TYPE_DESCRIPTOR_TABLE[indexType]); - const offset = bufferCount(meshData, indexTypeDescriptor.bufferType); - for (const index of new Uint32Array(mesh[indexType])) - meshData[indexType].push(index + offset); - } - for (const bufferType of Object.keys(BUFFER_TYPES) as MeshBufferType[]) { - if (BUFFER_TYPES[bufferType] !== 'ARRAY_BUFFER') - continue; - meshData[bufferType].push(...new Float32Array(mesh[bufferType])); - - const pathIDBufferType = PATH_ID_BUFFER_TABLE[bufferType]; - if (pathIDBufferType != null) { - const length = bufferCount(meshData, bufferType); - while (meshData[pathIDBufferType].length < length) - meshData[pathIDBufferType].push(destMeshIndex + 1); - } - } - - for (const pathRangeType of Object.keys(PATH_RANGE_TO_BUFFER_TYPE_TABLE) as - Array) { - const bufferType = PATH_RANGE_TO_BUFFER_TYPE_TABLE[pathRangeType]; - const endIndex = bufferCount(meshData, bufferType); - unwrapUndef(_.last(pathRanges[pathRangeType])).end = endIndex; - } - } - - for (const bufferType of Object.keys(BUFFER_TYPES) as PackedMeshBufferType[]) { - const arrayCtor = PRIMITIVE_TYPE_ARRAY_CONSTRUCTORS[MESH_TYPES[bufferType].type]; - this[bufferType] = (new arrayCtor(meshData[bufferType])) as any; - } - _.assign(this, pathRanges); - } - - count(bufferType: MeshBufferType): number { - return bufferCount(this, bufferType); - } -} - -export class PathfinderPackedMeshBuffers implements PackedMeshLike, PathRanges { - readonly bBoxes!: WebGLBuffer; - readonly bQuadVertexInteriorIndices!: WebGLBuffer; - readonly bQuadVertexPositions!: WebGLBuffer; - readonly stencilSegments!: WebGLBuffer; - readonly stencilNormals!: WebGLBuffer; - - readonly bBoxPathIDs!: WebGLBuffer; - readonly bQuadVertexPositionPathIDs!: WebGLBuffer; - readonly stencilSegmentPathIDs!: WebGLBuffer; - - readonly bBoxPathRanges!: Range[]; - readonly bQuadVertexInteriorIndexPathRanges!: Range[]; - readonly bQuadVertexPositionPathRanges!: Range[]; - readonly stencilSegmentPathRanges!: Range[]; - - constructor(gl: WebGLRenderingContext, packedMeshes: PathfinderPackedMeshes) { - for (const bufferName of Object.keys(BUFFER_TYPES) as PackedMeshBufferType[]) { - const bufferType = gl[BUFFER_TYPES[bufferName]]; - const buffer = expectNotNull(gl.createBuffer(), "Failed to create buffer!"); - gl.bindBuffer(bufferType, buffer); - gl.bufferData(bufferType, packedMeshes[bufferName], gl.STATIC_DRAW); - this[bufferName] = buffer; - } - - for (const rangeName of Object.keys(PATH_RANGE_TO_BUFFER_TYPE_TABLE) as - Array) { - this[rangeName] = packedMeshes[rangeName]; - } - } -} - -function bufferCount(mesh: MeshLike, bufferType: MeshBufferType): number { - return mesh[bufferType].length / MESH_TYPES[bufferType].size; -} - -function sizeOfPrimitive(primitiveType: PrimitiveType): number { - switch (primitiveType) { - case 'Uint16': return UINT16_SIZE; - case 'Uint32': return UINT32_SIZE; - case 'Float32': return FLOAT32_SIZE; - } -} - -function toFourCC(buffer: ArrayBuffer, position: number): string { - let result = ""; - const bytes = new Uint8Array(buffer, position, 4); - for (const byte of bytes) - result += String.fromCharCode(byte); - return result; -} - -export function parseServerTiming(headers: Headers): number { - if (!headers.has('Server-Timing')) - return 0.0; - const timing = headers.get('Server-Timing')!; - const matches = /^Partitioning\s*=\s*([0-9.]+)$/.exec(timing); - return matches != null ? parseFloat(matches[1]) / 1000.0 : 0.0; -} - -function readUInt32(buffer: ArrayBuffer, offset: number): number { - return (new Uint32Array(buffer.slice(offset, offset + 4)))[0]; -} diff --git a/demo/client/src/reference-test.ts b/demo/client/src/reference-test.ts deleted file mode 100644 index 7782946a..00000000 --- a/demo/client/src/reference-test.ts +++ /dev/null @@ -1,917 +0,0 @@ -// pathfinder/client/src/reference-test.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; -import * as imageSSIM from 'image-ssim'; -import * as _ from 'lodash'; -import * as papaparse from 'papaparse'; - -import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy'; -import {SubpixelAAType} from './aa-strategy'; -import {DemoAppController, setSwitchInputsValue} from "./app-controller"; -import {SUBPIXEL_GRANULARITY} from './atlas'; -import {OrthographicCamera} from './camera'; -import {UniformMap} from './gl-utils'; -import {B_QUAD_UPPER_CONTROL_POINT_VERTEX_OFFSET, PathfinderMeshPack} from './meshes'; -import {PathfinderPackedMeshBuffers, PathfinderPackedMeshes} from './meshes'; -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 {SVGRenderer} from './svg-renderer'; -import {BUILTIN_FONT_URI, computeStemDarkeningAmount, ExpandedMeshData, GlyphStore} from "./text"; -import {Hint} from "./text"; -import {PathfinderFont, TextFrame, TextRun} from "./text"; -import {MAX_SUBPIXEL_AA_FONT_SIZE} from './text-renderer'; -import {unwrapNull} from "./utils"; -import {DemoView} from "./view"; -import {AdaptiveStencilMeshAAAStrategy} from './xcaa-strategy'; - -const FONT: string = 'open-sans'; -const TEXT_COLOR: number[] = [0, 0, 0, 255]; - -const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { - none: NoAAStrategy, - ssaa: SSAAStrategy, - xcaa: AdaptiveStencilMeshAAAStrategy, -}; - -const RENDER_REFERENCE_URIS: PerTestType = { - font: "/render-reference/text", - svg: "/render-reference/svg", -}; - -const TEST_DATA_URI: string = "/test-data/reference-test-text.csv"; - -const SSIM_TOLERANCE: number = 0.01; -const SSIM_WINDOW_SIZE: number = 8; - -const FILES: PerTestType = { - font: [ - { id: 'open-sans', title: "Open Sans" }, - { id: 'eb-garamond', title: "EB Garamond" }, - { id: 'nimbus-sans', title: "Nimbus Sans" }, - ], - svg: [ - { id: 'tiger', title: "Ghostscript Tiger" }, - ], -}; - -interface ReferenceTestGroup { - font: string; - tests: ReferenceTestCase[]; -} - -interface ReferenceTestCase { - size: number; - character: string; - aaMode: keyof AntialiasingStrategyTable; - subpixel: boolean; - referenceRenderer: ReferenceRenderer; - expectedSSIM: number; -} - -interface PerTestType { - font: T; - svg: T; -} - -interface File { - id: string; - title: string; -} - -type ReferenceRenderer = 'core-graphics' | 'freetype'; - -interface AntialiasingStrategyTable { - none: typeof NoAAStrategy; - ssaa: typeof SSAAStrategy; - xcaa: typeof AdaptiveStencilMeshAAAStrategy; -} - -class ReferenceTestAppController extends DemoAppController { - font: PathfinderFont | null = null; - textRun: TextRun | null = null; - - svgLoader!: SVGLoader; - builtinSvgName: string | null = null; - - referenceCanvas!: HTMLCanvasElement; - - tests!: Promise; - - currentTestType!: 'font' | 'svg'; - - protected readonly defaultFile: string = FONT; - - protected get builtinFileURI(): string { - if (this.currentTestType === 'font') - return BUILTIN_FONT_URI; - return BUILTIN_SVG_URI; - } - - private glyphStore!: GlyphStore; - private baseMeshes!: PathfinderMeshPack; - private expandedMeshes!: ExpandedMeshData; - - private fontSizeInput!: HTMLInputElement; - private characterInput!: HTMLInputElement; - private referenceRendererSelect!: HTMLSelectElement; - - private differenceCanvas!: HTMLCanvasElement; - - private aaLevelGroup!: HTMLElement; - - private customTabs!: PerTestType; - private customTestForms!: PerTestType; - private selectFileGroups!: PerTestType; - private runTestsButtons!: PerTestType; - private ssimGroups!: PerTestType; - private ssimLabels!: PerTestType; - private resultsTables!: PerTestType; - - private currentTestGroupIndex: number | null = null; - private currentTestCaseIndex: number | null = null; - private currentGlobalTestCaseIndex: number | null = null; - - get currentFontSize(): number { - return parseInt(this.fontSizeInput.value, 10); - } - - set currentFontSize(newFontSize: number) { - this.fontSizeInput.value = "" + newFontSize; - } - - get currentCharacter(): string { - return this.characterInput.value; - } - - set currentCharacter(newCharacter: string) { - this.characterInput.value = newCharacter; - } - - get currentReferenceRenderer(): ReferenceRenderer { - return this.referenceRendererSelect.value as ReferenceRenderer; - } - - set currentReferenceRenderer(newReferenceRenderer: ReferenceRenderer) { - this.referenceRendererSelect.value = newReferenceRenderer; - } - - start(): void { - this.referenceRendererSelect = - unwrapNull(document.getElementById('pf-font-reference-renderer')) as HTMLSelectElement; - this.referenceRendererSelect.addEventListener('change', () => { - this.view.then(view => this.runSingleTest(view)); - }, false); - - super.start(); - - this.currentTestGroupIndex = null; - this.currentTestCaseIndex = null; - this.currentGlobalTestCaseIndex = null; - - this.referenceCanvas = unwrapNull(document.getElementById('pf-reference-canvas')) as - HTMLCanvasElement; - - this.fontSizeInput = unwrapNull(document.getElementById('pf-font-size')) as - HTMLInputElement; - this.fontSizeInput.addEventListener('change', () => { - this.view.then(view => this.runSingleTest(view)); - }, false); - - this.characterInput = unwrapNull(document.getElementById('pf-character')) as - HTMLInputElement; - this.characterInput.addEventListener('change', () => { - this.view.then(view => this.runSingleTest(view)); - }, false); - - this.aaLevelGroup = unwrapNull(document.getElementById('pf-aa-level-group')) as - HTMLElement; - - this.differenceCanvas = unwrapNull(document.getElementById('pf-difference-canvas')) as - HTMLCanvasElement; - - this.customTabs = { - font: unwrapNull(document.getElementById('pf-font-custom-test-tab')) as HTMLElement, - svg: unwrapNull(document.getElementById('pf-svg-custom-test-tab')) as HTMLElement, - }; - this.customTestForms = { - font: unwrapNull(document.getElementById('pf-font-custom-form')) as HTMLFormElement, - svg: unwrapNull(document.getElementById('pf-svg-custom-form')) as HTMLFormElement, - }; - this.selectFileGroups = { - font: unwrapNull(document.getElementById('pf-font-select-file-group')) as HTMLElement, - svg: unwrapNull(document.getElementById('pf-svg-select-file-group')) as HTMLElement, - }; - this.runTestsButtons = { - font: unwrapNull(document.getElementById('pf-run-font-tests-button')) as - HTMLButtonElement, - svg: unwrapNull(document.getElementById('pf-run-svg-tests-button')) as - HTMLButtonElement, - }; - this.ssimGroups = { - font: unwrapNull(document.getElementById('pf-font-ssim-group')) as HTMLElement, - svg: unwrapNull(document.getElementById('pf-svg-ssim-group')) as HTMLElement, - }; - this.ssimLabels = { - font: unwrapNull(document.getElementById('pf-font-ssim-label')) as HTMLElement, - svg: unwrapNull(document.getElementById('pf-svg-ssim-label')) as HTMLElement, - }; - this.resultsTables = { - font: unwrapNull(document.getElementById('pf-font-results-table')) as HTMLTableElement, - svg: unwrapNull(document.getElementById('pf-svg-results-table')) as HTMLTableElement, - }; - - this.customTabs.font.addEventListener('click', - () => this.showCustomTabPane('font'), - false); - this.customTabs.svg.addEventListener('click', () => this.showCustomTabPane('svg'), false); - - this.runTestsButtons.font.addEventListener('click', () => { - this.view.then(view => this.runTests()); - }, false); - this.runTestsButtons.svg.addEventListener('click', () => { - this.view.then(view => this.runTests()); - }, false); - - this.currentTestType = 'font'; - - this.loadTestData(); - this.populateResultsTable(); - this.populateFilesSelect(); - - this.loadInitialFile(this.builtinFileURI); - } - - runNextTestIfNecessary(view: ReferenceTestView, tests: ReferenceTestGroup[]): void { - if (this.currentTestGroupIndex == null || this.currentTestCaseIndex == null || - this.currentGlobalTestCaseIndex == null) { - return; - } - - this.currentTestCaseIndex++; - this.currentGlobalTestCaseIndex++; - if (this.currentTestCaseIndex === tests[this.currentTestGroupIndex].tests.length) { - this.currentTestCaseIndex = 0; - this.currentTestGroupIndex++; - if (this.currentTestGroupIndex === tests.length) { - // Done running tests. - this.currentTestCaseIndex = null; - this.currentTestGroupIndex = null; - this.currentGlobalTestCaseIndex = null; - this.view.then(view => view.suppressAutomaticRedraw = false); - return; - } - } - - this.loadFontForTestGroupIfNecessary(tests).then(() => { - this.setOptionsForCurrentTest(tests).then(() => this.runSingleTest(view)); - }); - } - - recordSSIMResult(tests: ReferenceTestGroup[], ssimResult: imageSSIM.IResult): void { - const formattedSSIM: string = "" + (Math.round(ssimResult.ssim * 1000.0) / 1000.0); - this.ssimLabels[this.currentTestType].textContent = formattedSSIM; - - if (this.currentTestGroupIndex == null || this.currentTestCaseIndex == null || - this.currentGlobalTestCaseIndex == null) { - return; - } - - const testGroup = tests[this.currentTestGroupIndex]; - const expectedSSIM = testGroup.tests[this.currentTestCaseIndex].expectedSSIM; - const passed = Math.abs(expectedSSIM - ssimResult.ssim) <= SSIM_TOLERANCE; - - const resultsBody: Element = unwrapNull(this.resultsTables[this.currentTestType] - .lastElementChild); - let resultsRow = unwrapNull(resultsBody.firstElementChild); - for (let rowIndex = 0; rowIndex < this.currentGlobalTestCaseIndex; rowIndex++) - resultsRow = unwrapNull(resultsRow.nextElementSibling); - - const passCell = unwrapNull(resultsRow.firstElementChild); - const resultsCell = unwrapNull(resultsRow.lastElementChild); - resultsCell.textContent = formattedSSIM; - passCell.textContent = passed ? "✓" : "✗"; - - resultsRow.classList.remove('table-success', 'table-danger'); - resultsRow.classList.add(passed ? 'table-success' : 'table-danger'); - } - - drawDifferenceImage(differenceImage: imageSSIM.IImage): void { - const canvas = this.differenceCanvas; - const context = unwrapNull(canvas.getContext('2d')); - context.fillStyle = 'white'; - context.fillRect(0, 0, canvas.width, canvas.height); - - const data = new Uint8ClampedArray(differenceImage.data); - const imageData = new ImageData(data, differenceImage.width, differenceImage.height); - context.putImageData(imageData, 0, 0); - } - - protected createView(areaLUT: HTMLImageElement, - gammaLUT: HTMLImageElement, - commonShaderSource: string, - shaderSources: ShaderMap): - ReferenceTestView { - return new ReferenceTestView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources); - } - - protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null): void { - switch (this.currentTestType) { - case 'font': - this.textFileLoaded(fileData, builtinName); - break; - case 'svg': - this.svgFileLoaded(fileData, builtinName); - break; - } - - // Don't automatically run the test unless this is a custom test. - if (this.currentGlobalTestCaseIndex == null) - this.view.then(view => this.runSingleTest(view)); - } - - private textFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void { - const font = new PathfinderFont(fileData, builtinName); - this.font = font; - } - - private svgFileLoaded(fileData: ArrayBuffer, builtinName: string | null): void { - this.builtinSvgName = builtinName; - this.svgLoader = new SVGLoader; - this.svgLoader.loadFile(fileData); - } - - private populateFilesSelect(): void { - const selectFileElement = unwrapNull(this.selectFileElement); - while (selectFileElement.lastChild != null) - selectFileElement.removeChild(selectFileElement.lastChild); - - for (const file of FILES[this.currentTestType]) { - const option = document.createElement('option'); - option.value = file.id; - option.appendChild(document.createTextNode(file.title)); - selectFileElement.appendChild(option); - } - } - - private loadTestData(): void { - this.tests = window.fetch(TEST_DATA_URI) - .then(response => response.text()) - .then(testDataText => { - const fontNames = []; - const groups: {[font: string]: ReferenceTestCase[]} = {}; - - const testData = papaparse.parse(testDataText, { - comments: "#", - header: true, - skipEmptyLines: true, - }); - - for (const row of testData.data) { - if (!groups.hasOwnProperty(row.Font)) { - fontNames.push(row.Font); - groups[row.Font] = []; - } - groups[row.Font].push({ - aaMode: row['AA Mode'] as keyof AntialiasingStrategyTable, - character: row.Character, - expectedSSIM: parseFloat(row['Expected SSIM']), - referenceRenderer: row['Reference Renderer'], - size: parseInt(row.Size, 10), - subpixel: !!row.Subpixel, - }); - } - - return fontNames.map(fontName => { - return { - font: fontName, - tests: groups[fontName], - }; - }); - }); - } - - private populateResultsTable(): void { - this.tests.then(tests => { - const resultsBody: Element = unwrapNull(this.resultsTables[this.currentTestType] - .lastElementChild); - for (const testGroup of tests) { - for (const test of testGroup.tests) { - const row = document.createElement('tr'); - addCell(row, ""); - addCell(row, testGroup.font); - addCell(row, test.character); - addCell(row, "" + test.size); - addCell(row, "" + test.aaMode); - addCell(row, test.subpixel ? "Y" : "N"); - addCell(row, test.referenceRenderer); - addCell(row, "" + test.expectedSSIM); - addCell(row, ""); - resultsBody.appendChild(row); - } - } - }); - } - - private runSingleTest(view: ReferenceTestView): void { - if (this.currentTestType === 'font') - this.setUpTextRun(); - - this.loadReference(view).then(() => this.loadRendering()); - } - - private runTests(): void { - this.view.then(view => { - view.suppressAutomaticRedraw = true; - this.tests.then(tests => { - this.currentTestGroupIndex = 0; - this.currentTestCaseIndex = 0; - this.currentGlobalTestCaseIndex = 0; - - this.loadFontForTestGroupIfNecessary(tests).then(() => { - this.setOptionsForCurrentTest(tests).then(() => this.runSingleTest(view)); - }); - }); - }); - } - - private loadFontForTestGroupIfNecessary(tests: ReferenceTestGroup[]): Promise { - return new Promise(resolve => { - if (this.currentTestGroupIndex == null) { - resolve(); - return; - } - - this.fetchFile(tests[this.currentTestGroupIndex].font, BUILTIN_FONT_URI).then(() => { - resolve(); - }); - }); - } - - private setOptionsForCurrentTest(tests: ReferenceTestGroup[]): Promise { - if (this.currentTestGroupIndex == null || this.currentTestCaseIndex == null) - return new Promise(resolve => resolve()); - - const currentTestCase = tests[this.currentTestGroupIndex].tests[this.currentTestCaseIndex]; - this.currentFontSize = currentTestCase.size; - this.currentCharacter = currentTestCase.character; - this.currentReferenceRenderer = currentTestCase.referenceRenderer; - - const aaLevelSelect = unwrapNull(this.aaLevelSelect); - aaLevelSelect.selectedIndex = _.findIndex(aaLevelSelect.options, option => { - return option.value.startsWith(currentTestCase.aaMode); - }); - - const subpixelAASelect = unwrapNull(this.subpixelAASelect); - subpixelAASelect.selectedIndex = currentTestCase.subpixel ? 1 : 0; - return this.updateAALevel(); - } - - private setUpTextRun(): void { - const font = unwrapNull(this.font); - - const textRun = new TextRun(this.currentCharacter, [0, 0], font); - textRun.layout(); - this.textRun = textRun; - - this.glyphStore = new GlyphStore(font, [textRun.glyphIDs[0]]); - } - - private loadRendering(): void { - switch (this.currentTestType) { - case 'font': - this.loadTextRendering(); - break; - case 'svg': - this.loadSVGRendering(); - break; - } - } - - private loadTextRendering(): void { - this.glyphStore.partition().then(result => { - const textRun = unwrapNull(this.textRun); - - this.baseMeshes = result.meshes; - - const textFrame = new TextFrame([textRun], unwrapNull(this.font)); - const expandedMeshes = textFrame.expandMeshes(this.baseMeshes, [textRun.glyphIDs[0]]); - this.expandedMeshes = expandedMeshes; - - this.view.then(view => { - view.recreateRenderer(); - view.attachMeshes([expandedMeshes.meshes]); - view.redraw(); - }); - }); - } - - private loadSVGRendering(): void { - this.svgLoader.partition().then(meshes => { - this.view.then(view => { - view.recreateRenderer(); - view.attachMeshes([new PathfinderPackedMeshes(meshes)]); - view.initCameraBounds(this.svgLoader.svgViewBox); - }); - }); - } - - private loadReference(view: ReferenceTestView): Promise { - let request; - switch (this.currentTestType) { - case 'font': - request = { - face: { - Builtin: unwrapNull(this.font).builtinFontName, - }, - fontIndex: 0, - glyph: this.glyphStore.glyphIDs[0], - pointSize: this.currentFontSize, - renderer: this.currentReferenceRenderer, - }; - break; - case 'svg': - // TODO(pcwalton): Custom SVGs. - request = { - name: unwrapNull(this.builtinSvgName), - renderer: 'pixman', - scale: 1.0, - }; - break; - } - - return window.fetch(RENDER_REFERENCE_URIS[this.currentTestType], { - body: JSON.stringify(request), - headers: {'Content-Type': 'application/json'} as any, - method: 'POST', - }).then(response => response.blob()).then(blob => { - const imgElement = document.createElement('img'); - imgElement.src = URL.createObjectURL(blob); - imgElement.addEventListener('load', () => { - const canvas = this.referenceCanvas; - const context = unwrapNull(canvas.getContext('2d')); - context.fillStyle = 'white'; - context.fillRect(0, 0, canvas.width, canvas.height); - context.drawImage(imgElement, 0, 0); - }, false); - }); - } - - private showCustomTabPane(testType: 'font' | 'svg'): void { - this.currentTestType = testType; - - const selectFileElement = unwrapNull(this.selectFileElement); - const aaLevelGroup = unwrapNull(this.aaLevelGroup); - - const customTestForm = this.customTestForms[testType]; - const selectFileGroup = this.selectFileGroups[testType]; - const ssimGroup = this.ssimGroups[testType]; - - unwrapNull(selectFileElement.parentNode).removeChild(selectFileElement); - unwrapNull(aaLevelGroup.parentNode).removeChild(aaLevelGroup); - - selectFileGroup.appendChild(selectFileElement); - customTestForm.insertBefore(aaLevelGroup, ssimGroup); - - this.populateFilesSelect(); - } -} - -class ReferenceTestView extends DemoView { - renderer!: ReferenceTestTextRenderer | ReferenceTestSVGRenderer; - readonly appController: ReferenceTestAppController; - - get camera(): OrthographicCamera { - return this.renderer.camera; - } - - constructor(appController: ReferenceTestAppController, - areaLUT: HTMLImageElement, - gammaLUT: HTMLImageElement, - commonShaderSource: string, - shaderSources: ShaderMap) { - super(areaLUT, gammaLUT, commonShaderSource, shaderSources); - - this.appController = appController; - this.recreateRenderer(); - - this.resizeToFit(true); - } - - recreateRenderer(): void { - switch (this.appController.currentTestType) { - case 'svg': - this.renderer = new ReferenceTestSVGRenderer(this); - break; - case 'font': - this.renderer = new ReferenceTestTextRenderer(this); - break; - } - } - - initCameraBounds(viewBox: glmatrix.vec4): void { - if (this.renderer instanceof ReferenceTestSVGRenderer) - this.renderer.initCameraBounds(viewBox); - } - - protected renderingFinished(): void { - const gl = this.renderContext.gl; - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - // TODO(pcwalton): Get the SVG pixel rect. - let pixelRect: glmatrix.vec4 = glmatrix.vec4.create(); - if (this.renderer instanceof ReferenceTestTextRenderer) - pixelRect = this.renderer.getPixelRectForGlyphAt(0); - - const canvasHeight = this.canvas.height; - const width = pixelRect[2] - pixelRect[0], height = pixelRect[3] - pixelRect[1]; - const originY = Math.max(canvasHeight - height, 0); - const flippedBuffer = new Uint8Array(width * height * 4); - gl.readPixels(0, originY, width, height, gl.RGBA, gl.UNSIGNED_BYTE, flippedBuffer); - - const buffer = new Uint8Array(width * height * 4); - for (let y = 0; y < height; y++) { - const destRowStart = y * width * 4; - const srcRowStart = (height - y - 1) * width * 4; - buffer.set(flippedBuffer.slice(srcRowStart, srcRowStart + width * 4), - destRowStart); - } - - const renderedImage = createSSIMImage(buffer, pixelRect); - - this.appController.tests.then(tests => { - const referenceImage = createSSIMImage(this.appController.referenceCanvas, - pixelRect); - const ssimResult = imageSSIM.compare(referenceImage, renderedImage, SSIM_WINDOW_SIZE); - const differenceImage = generateDifferenceImage(referenceImage, renderedImage); - this.appController.recordSSIMResult(tests, ssimResult); - this.appController.drawDifferenceImage(differenceImage); - this.appController.runNextTestIfNecessary(this, tests); - }); - } -} - -class ReferenceTestTextRenderer extends Renderer { - renderContext!: ReferenceTestView; - camera: OrthographicCamera; - - needsStencil: boolean = false; - isMulticolor: boolean = false; - - get destFramebuffer(): WebGLFramebuffer | null { - return null; - } - - get bgColor(): glmatrix.vec4 { - return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]); - } - - get fgColor(): glmatrix.vec4 { - return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]); - } - - get destAllocatedSize(): glmatrix.vec2 { - const canvas = this.renderContext.canvas; - return glmatrix.vec2.clone([canvas.width, canvas.height]); - } - - get destUsedSize(): glmatrix.vec2 { - return this.destAllocatedSize; - } - - get emboldenAmount(): glmatrix.vec2 { - return this.stemDarkeningAmount; - } - - get allowSubpixelAA(): boolean { - const appController = this.renderContext.appController; - return appController.currentFontSize <= MAX_SUBPIXEL_AA_FONT_SIZE; - } - - protected get objectCount(): number { - return this.meshBuffers == null ? 0 : this.meshBuffers.length; - } - - 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(); - 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 get pixelsPerUnit(): number { - const appController = this.renderContext.appController; - const font = unwrapNull(appController.font); - return appController.currentFontSize / font.opentypeFont.unitsPerEm; - } - - private get stemDarkeningAmount(): glmatrix.vec2 { - const appController = this.renderContext.appController; - return computeStemDarkeningAmount(appController.currentFontSize, this.pixelsPerUnit); - } - - constructor(renderContext: ReferenceTestView) { - super(renderContext); - - this.camera = new OrthographicCamera(renderContext.canvas, { fixed: true }); - this.camera.onPan = () => renderContext.setDirty(); - this.camera.onZoom = () => renderContext.setDirty(); - } - - attachMeshes(meshes: PathfinderPackedMeshes[]): void { - super.attachMeshes(meshes); - - this.uploadPathColors(1); - this.uploadPathTransforms(1); - } - - pathCountForObject(objectIndex: number): number { - return 1; - } - - pathBoundingRects(objectIndex: number): Float32Array { - const appController = this.renderContext.appController; - const font = unwrapNull(appController.font); - - const boundingRects = new Float32Array(2 * 4); - - const glyphID = unwrapNull(appController.textRun).glyphIDs[0]; - - const metrics = unwrapNull(font.metricsForGlyph(glyphID)); - - boundingRects[4 + 0] = metrics.xMin; - boundingRects[4 + 1] = metrics.yMin; - boundingRects[4 + 2] = metrics.xMax; - boundingRects[4 + 3] = metrics.yMax; - - return boundingRects; - } - - setHintsUniform(uniforms: UniformMap): void { - this.renderContext.gl.uniform4f(uniforms.uHints, 0, 0, 0, 0); - } - - getPixelRectForGlyphAt(glyphIndex: number): glmatrix.vec4 { - const textRun = unwrapNull(this.renderContext.appController.textRun); - return textRun.pixelRectForGlyphAt(glyphIndex); - } - - pathTransformsForObject(objectIndex: number): PathTransformBuffers { - const appController = this.renderContext.appController; - const canvas = this.renderContext.canvas; - const font = unwrapNull(appController.font); - const hint = new Hint(font, this.pixelsPerUnit, true); - - const pathTransforms = this.createPathTransformBuffers(1); - - const textRun = unwrapNull(appController.textRun); - const glyphID = textRun.glyphIDs[0]; - textRun.recalculatePixelRects(this.pixelsPerUnit, - 0.0, - hint, - glmatrix.vec2.create(), - SUBPIXEL_GRANULARITY, - glmatrix.vec4.create()); - const pixelRect = textRun.pixelRectForGlyphAt(0); - - const x = -pixelRect[0] / this.pixelsPerUnit; - const y = (canvas.height - (pixelRect[3] - pixelRect[1])) / this.pixelsPerUnit; - - pathTransforms.st.set([1, 1, x, y], 1 * 4); - - return pathTransforms; - } - - protected createAAStrategy(aaType: AntialiasingStrategyName, - aaLevel: number, - subpixelAA: SubpixelAAType): - AntialiasingStrategy { - return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA); - } - - protected compositeIfNecessary(): void {} - - protected pathColorsForObject(objectIndex: number): Uint8Array { - const pathColors = new Uint8Array(4 * 2); - pathColors.set(TEXT_COLOR, 1 * 4); - return pathColors; - } - - protected directCurveProgramName(): keyof ShaderMap { - return 'directCurve'; - } - - protected directInteriorProgramName(): keyof ShaderMap { - return 'directInterior'; - } -} - -class ReferenceTestSVGRenderer extends SVGRenderer { - renderContext!: ReferenceTestView; - - protected get loader(): SVGLoader { - return this.renderContext.appController.svgLoader; - } - - protected get canvas(): HTMLCanvasElement { - return this.renderContext.canvas; - } - - constructor(renderContext: ReferenceTestView) { - super(renderContext, { sizeToFit: false, fixed: true }); - } -} - -function createSSIMImage(image: HTMLCanvasElement | Uint8Array, rect: glmatrix.vec4): - imageSSIM.IImage { - const size = glmatrix.vec2.clone([rect[2] - rect[0], rect[3] - rect[1]]); - - let data; - if (image instanceof HTMLCanvasElement) { - const context = unwrapNull(image.getContext('2d')); - data = new Uint8Array(context.getImageData(0, 0, size[0], size[1]).data); - } else { - data = image; - } - - return { - channels: imageSSIM.Channels.RGBAlpha, - data: data, - height: size[1], - width: size[0], - }; -} - -function generateDifferenceImage(referenceImage: imageSSIM.IImage, - renderedImage: imageSSIM.IImage): - imageSSIM.IImage { - const differenceImage = new Uint8Array(referenceImage.width * referenceImage.height * 4); - for (let y = 0; y < referenceImage.height; y++) { - const rowStart = y * referenceImage.width * 4; - for (let x = 0; x < referenceImage.width; x++) { - const pixelStart = rowStart + x * 4; - - let differenceSum = 0; - for (let channel = 0; channel < 3; channel++) { - differenceSum += Math.abs(referenceImage.data[pixelStart + channel] - - renderedImage.data[pixelStart + channel]); - } - - if (differenceSum === 0) { - // Lighten to indicate no difference. - for (let channel = 0; channel < 4; channel++) { - differenceImage[pixelStart + channel] = - Math.floor(referenceImage.data[pixelStart + channel] / 2) + 128; - } - continue; - } - - // Draw differences in red. - const differenceMean = differenceSum / 3; - differenceImage[pixelStart + 0] = 127 + Math.round(differenceMean / 2); - differenceImage[pixelStart + 1] = differenceImage[pixelStart + 2] = 0; - differenceImage[pixelStart + 3] = 255; - } - } - return { - channels: referenceImage.channels, - data: differenceImage, - height: referenceImage.height, - width: referenceImage.width, - }; -} - -function addCell(row: HTMLTableRowElement, text: string): void { - const tableCell = document.createElement('td'); - tableCell.textContent = text; - row.appendChild(tableCell); -} - -function main() { - const controller = new ReferenceTestAppController; - window.addEventListener('load', () => controller.start(), false); -} - -main(); diff --git a/demo/client/src/renderer.ts b/demo/client/src/renderer.ts deleted file mode 100644 index de6cde78..00000000 --- a/demo/client/src/renderer.ts +++ /dev/null @@ -1,847 +0,0 @@ -// pathfinder/client/src/renderer.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; -import * as _ from 'lodash'; - -import {AntialiasingStrategy, AntialiasingStrategyName, DirectRenderingMode} from './aa-strategy'; -import {GammaCorrectionMode} from './aa-strategy'; -import {TileInfo} from './aa-strategy'; -import {NoAAStrategy, StemDarkeningMode, SubpixelAAType} from './aa-strategy'; -import {AAOptions} from './app-controller'; -import PathfinderBufferTexture from "./buffer-texture"; -import {UniformMap, WebGLQuery} from './gl-utils'; -import {PathfinderPackedMeshBuffers, PathfinderPackedMeshes} from "./meshes"; -import {ShaderMap} from './shader-loader'; -import {FLOAT32_SIZE, Range, UINT16_SIZE, UINT32_SIZE, unwrapNull, unwrapUndef} from './utils'; -import {RenderContext, Timings} from "./view"; - -const MAX_PATHS: number = 65535; - -const MAX_VERTICES: number = 4 * 1024 * 1024; - -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 interface PathTransformBuffers { - st: T; - ext: T; -} - -export abstract class Renderer { - readonly renderContext: RenderContext; - - readonly pathTransformBufferTextures: Array>; - - meshBuffers: PathfinderPackedMeshBuffers[] | null; - meshes: PathfinderPackedMeshes[] | null; - - lastTimings: Timings; - - inVR: boolean = false; - - get emboldenAmount(): glmatrix.vec2 { - return glmatrix.vec2.create(); - } - - get bgColor(): glmatrix.vec4 { - return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]); - } - - get fgColor(): glmatrix.vec4 | null { - return null; - } - - get backgroundColor(): glmatrix.vec4 { - return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]); - } - - get meshesAttached(): boolean { - return this.meshBuffers != null && this.meshes != null; - } - - abstract get isMulticolor(): boolean; - abstract get needsStencil(): boolean; - abstract get allowSubpixelAA(): boolean; - - abstract get destFramebuffer(): WebGLFramebuffer | null; - abstract get destAllocatedSize(): glmatrix.vec2; - abstract get destUsedSize(): glmatrix.vec2; - - protected antialiasingStrategy: AntialiasingStrategy | null; - protected pathColorsBufferTextures: PathfinderBufferTexture[]; - - protected gammaCorrectionMode: GammaCorrectionMode; - - protected get pathIDsAreInstanced(): boolean { - return false; - } - - protected abstract get objectCount(): number; - protected abstract get usedSizeFactor(): glmatrix.vec2; - protected abstract get worldTransform(): glmatrix.mat4; - - private implicitCoverInteriorVAO: WebGLVertexArrayObjectOES | null = null; - private implicitCoverCurveVAO: WebGLVertexArrayObjectOES | null = null; - - private gammaLUTTexture: WebGLTexture | null = null; - private areaLUTTexture: WebGLTexture | null = null; - - private instancedPathIDVBO: WebGLBuffer | null = null; - private vertexIDVBO: WebGLBuffer | null = null; - private timerQueryPollInterval: number | null = null; - - constructor(renderContext: RenderContext) { - this.renderContext = renderContext; - - this.meshes = null; - this.meshBuffers = null; - - this.lastTimings = { rendering: 0, compositing: 0 }; - - this.gammaCorrectionMode = 'on'; - - this.pathTransformBufferTextures = []; - this.pathColorsBufferTextures = []; - - if (this.pathIDsAreInstanced) - this.initInstancedPathIDVBO(); - - this.initVertexIDVBO(); - this.initLUTTexture('gammaLUT', 'gammaLUTTexture'); - this.initLUTTexture('areaLUT', 'areaLUTTexture'); - - this.antialiasingStrategy = new NoAAStrategy(0, 'none'); - this.antialiasingStrategy.init(this); - this.antialiasingStrategy.setFramebufferSize(this); - } - - attachMeshes(meshes: PathfinderPackedMeshes[]): void { - const renderContext = this.renderContext; - this.meshes = meshes; - this.meshBuffers = meshes.map(meshes => { - return new PathfinderPackedMeshBuffers(renderContext.gl, meshes); - }); - unwrapNull(this.antialiasingStrategy).attachMeshes(this); - } - - abstract pathBoundingRects(objectIndex: number): Float32Array; - abstract setHintsUniform(uniforms: UniformMap): void; - abstract pathTransformsForObject(objectIndex: number): PathTransformBuffers; - - redrawVR(frame: VRFrameData): void { - this.redraw(); - } - - setDrawViewport() { - const renderContext = this.renderContext; - const gl = renderContext.gl; - gl.viewport(0, 0, this.destAllocatedSize[0], this.destAllocatedSize[1]); - } - - setClipPlanes(display: VRDisplay) { - } - - redraw(): void { - const renderContext = this.renderContext; - - if (this.meshBuffers == null) - return; - - this.clearDestFramebuffer(false); - - // Start timing rendering. - if (this.timerQueryPollInterval == null && - renderContext.timerQueryExt != null && - renderContext.atlasRenderingTimerQuery != null) { - renderContext.timerQueryExt.beginQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT, - renderContext.atlasRenderingTimerQuery); - } - - const antialiasingStrategy = unwrapNull(this.antialiasingStrategy); - antialiasingStrategy.prepareForRendering(this); - - // Draw "scenery" (used in the 3D view). - this.drawSceneryIfNecessary(); - - const passCount = antialiasingStrategy.passCount; - for (let pass = 0; pass < passCount; pass++) { - if (antialiasingStrategy.directRenderingMode !== 'none') - antialiasingStrategy.prepareForDirectRendering(this); - - const objectCount = this.objectCount; - for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) { - if (antialiasingStrategy.directRenderingMode !== 'none') { - // Prepare for direct rendering. - antialiasingStrategy.prepareToRenderObject(this, objectIndex); - - // Clear. - this.clearForDirectRendering(objectIndex); - - // Perform direct rendering (Loop-Blinn). - this.directlyRenderObject(pass, objectIndex); - } - - // Antialias. - antialiasingStrategy.antialiasObject(this, objectIndex); - - // End the timer, and start a new one. - // FIXME(pcwalton): This is kinda bogus for multipass. - if (this.timerQueryPollInterval == null && - objectIndex === objectCount - 1 && - pass === passCount - 1 && - renderContext.timerQueryExt != null && - renderContext.compositingTimerQuery != null) { - renderContext.timerQueryExt - .endQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT); - renderContext.timerQueryExt - .beginQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT, - renderContext.compositingTimerQuery); - } - - // Perform post-antialiasing tasks. - antialiasingStrategy.finishAntialiasingObject(this, objectIndex); - - antialiasingStrategy.resolveAAForObject(this, objectIndex); - } - - antialiasingStrategy.resolve(pass, this); - } - - // Draw the glyphs with the resolved atlas to the default framebuffer. - this.compositeIfNecessary(); - - // Finish timing. - this.finishTiming(); - } - - setAntialiasingOptions(aaType: AntialiasingStrategyName, - aaLevel: number, - aaOptions: AAOptions): - void { - this.gammaCorrectionMode = aaOptions.gammaCorrection; - - this.antialiasingStrategy = this.createAAStrategy(aaType, - aaLevel, - aaOptions.subpixelAA, - aaOptions.stemDarkening); - - this.antialiasingStrategy.init(this); - if (this.meshes != null) - this.antialiasingStrategy.attachMeshes(this); - this.antialiasingStrategy.setFramebufferSize(this); - - this.renderContext.setDirty(); - } - - canvasResized(): void { - if (this.antialiasingStrategy != null) - this.antialiasingStrategy.init(this); - } - - setFramebufferSizeUniform(uniforms: UniformMap): void { - const gl = this.renderContext.gl; - gl.uniform2i(uniforms.uFramebufferSize, - this.destAllocatedSize[0], - this.destAllocatedSize[1]); - } - - setTransformAndTexScaleUniformsForDest(uniforms: UniformMap, tileInfo?: TileInfo): void { - const renderContext = this.renderContext; - const usedSize = this.usedSizeFactor; - - let tileSize, tilePosition; - if (tileInfo == null) { - tileSize = glmatrix.vec2.clone([1.0, 1.0]); - tilePosition = glmatrix.vec2.create(); - } else { - tileSize = tileInfo.size; - tilePosition = tileInfo.position; - } - - const transform = glmatrix.mat4.create(); - glmatrix.mat4.fromTranslation(transform, [ - -1.0 + tilePosition[0] / tileSize[0] * 2.0, - -1.0 + tilePosition[1] / tileSize[1] * 2.0, - 0.0, - ]); - glmatrix.mat4.scale(transform, transform, [2.0 * usedSize[0], 2.0 * usedSize[1], 1.0]); - glmatrix.mat4.scale(transform, transform, [1.0 / tileSize[0], 1.0 / tileSize[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 gl = renderContext.gl; - - const usedSize = this.usedSizeFactor; - gl.uniform4f(uniforms.uTransformST, 2.0 * usedSize[0], 2.0 * usedSize[1], -1.0, -1.0); - gl.uniform2f(uniforms.uTexScale, usedSize[0], usedSize[1]); - } - - setTransformUniform(uniforms: UniformMap, pass: number, objectIndex: number): void { - const transform = this.computeTransform(pass, objectIndex); - this.renderContext.gl.uniformMatrix4fv(uniforms.uTransform, false, transform); - } - - setTransformSTUniform(uniforms: UniformMap, objectIndex: number): void { - // FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an ST matrix is ugly and fragile. - // Refactor. - const renderContext = this.renderContext; - const gl = renderContext.gl; - - const transform = this.computeTransform(0, objectIndex); - - gl.uniform4f(uniforms.uTransformST, - transform[0], - transform[5], - transform[12], - transform[13]); - } - - affineTransform(objectIndex: number): glmatrix.mat2d { - // FIXME(pcwalton): Lossy conversion from a 4x4 matrix to an affine matrix is ugly and - // fragile. Refactor. - const transform = this.computeTransform(0, objectIndex); - return glmatrix.mat2d.fromValues(transform[0], transform[1], - transform[4], transform[5], - transform[12], transform[13]); - - } - - setTransformAffineUniforms(uniforms: UniformMap, objectIndex: number): void { - const renderContext = this.renderContext; - const gl = renderContext.gl; - - const transform = this.affineTransform(objectIndex); - gl.uniform4f(uniforms.uTransformST, - transform[0], - transform[3], - transform[4], - transform[5]); - gl.uniform2f(uniforms.uTransformExt, transform[1], transform[2]); - } - - uploadPathColors(objectCount: number): void { - const renderContext = this.renderContext; - for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) { - const pathColors = this.pathColorsForObject(objectIndex); - - let pathColorsBufferTexture; - if (objectIndex >= this.pathColorsBufferTextures.length) { - pathColorsBufferTexture = new PathfinderBufferTexture(renderContext.gl, - 'uPathColors'); - this.pathColorsBufferTextures[objectIndex] = pathColorsBufferTexture; - } else { - pathColorsBufferTexture = this.pathColorsBufferTextures[objectIndex]; - } - - pathColorsBufferTexture.upload(renderContext.gl, pathColors); - } - } - - uploadPathTransforms(objectCount: number): void { - const renderContext = this.renderContext; - const gl = renderContext.gl; - - for (let objectIndex = 0; objectIndex < objectCount; objectIndex++) { - const pathTransforms = this.pathTransformsForObject(objectIndex); - - let pathTransformBufferTextures; - if (objectIndex >= this.pathTransformBufferTextures.length) { - pathTransformBufferTextures = { - ext: new PathfinderBufferTexture(gl, 'uPathTransformExt'), - st: new PathfinderBufferTexture(gl, 'uPathTransformST'), - }; - this.pathTransformBufferTextures[objectIndex] = pathTransformBufferTextures; - } else { - pathTransformBufferTextures = this.pathTransformBufferTextures[objectIndex]; - } - - pathTransformBufferTextures.st.upload(gl, pathTransforms.st); - pathTransformBufferTextures.ext.upload(gl, pathTransforms.ext); - } - } - - setPathColorsUniform(objectIndex: number, uniforms: UniformMap, textureUnit: number): void { - const gl = this.renderContext.gl; - const meshIndex = this.meshIndexForObject(objectIndex); - this.pathColorsBufferTextures[meshIndex].bind(gl, uniforms, textureUnit); - } - - setEmboldenAmountUniform(objectIndex: number, uniforms: UniformMap): void { - const gl = this.renderContext.gl; - const emboldenAmount = this.emboldenAmount; - gl.uniform2f(uniforms.uEmboldenAmount, emboldenAmount[0], emboldenAmount[1]); - } - - meshIndexForObject(objectIndex: number): number { - return objectIndex; - } - - pathRangeForObject(objectIndex: number): Range { - if (this.meshBuffers == null) - return new Range(0, 0); - const bVertexPathRanges = this.meshBuffers[objectIndex].bQuadVertexPositionPathRanges; - return new Range(1, bVertexPathRanges.length + 1); - } - - bindAreaLUT(textureUnit: number, uniforms: UniformMap): void { - const renderContext = this.renderContext; - const gl = renderContext.gl; - - gl.activeTexture(gl.TEXTURE0 + textureUnit); - gl.bindTexture(gl.TEXTURE_2D, this.areaLUTTexture); - gl.uniform1i(uniforms.uAreaLUT, textureUnit); - } - - protected clearColorForObject(objectIndex: number): glmatrix.vec4 | null { - return null; - } - - protected bindGammaLUT(bgColor: glmatrix.vec3, textureUnit: number, uniforms: UniformMap): - void { - const renderContext = this.renderContext; - const gl = renderContext.gl; - - gl.activeTexture(gl.TEXTURE0 + textureUnit); - gl.bindTexture(gl.TEXTURE_2D, this.gammaLUTTexture); - gl.uniform1i(uniforms.uGammaLUT, textureUnit); - - gl.uniform3f(uniforms.uBGColor, bgColor[0], bgColor[1], bgColor[2]); - } - - protected abstract createAAStrategy(aaType: AntialiasingStrategyName, - aaLevel: number, - subpixelAA: SubpixelAAType, - stemDarkening: StemDarkeningMode): - AntialiasingStrategy; - protected abstract compositeIfNecessary(): void; - protected abstract pathColorsForObject(objectIndex: number): Uint8Array; - - protected abstract directCurveProgramName(): keyof ShaderMap; - protected abstract directInteriorProgramName(renderingMode: DirectRenderingMode): - keyof ShaderMap; - - protected drawSceneryIfNecessary(): void {} - - protected clearDestFramebuffer(force: boolean): void { - const renderContext = this.renderContext; - const gl = renderContext.gl; - - const clearColor = this.backgroundColor; - gl.bindFramebuffer(gl.FRAMEBUFFER, this.destFramebuffer); - gl.depthMask(true); - gl.viewport(0, 0, this.destAllocatedSize[0], this.destAllocatedSize[1]); - gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - gl.clearDepth(0.0); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - protected clearForDirectRendering(objectIndex: number): void { - const renderingMode = unwrapNull(this.antialiasingStrategy).directRenderingMode; - const renderContext = this.renderContext; - const gl = renderContext.gl; - - const clearColor = this.clearColorForObject(objectIndex); - if (clearColor == null) - return; - - gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - gl.clearDepth(0.0); - gl.depthMask(true); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - protected getModelviewTransform(pathIndex: number): glmatrix.mat4 { - return glmatrix.mat4.create(); - } - - /// If non-instanced, returns instance 0. An empty range skips rendering the object entirely. - protected instanceRangeForObject(objectIndex: number): Range { - return new Range(0, 1); - } - - /// Called whenever new GPU timing statistics are available. - protected newTimingsReceived(): void {} - - protected createPathTransformBuffers(pathCount: number): PathTransformBuffers { - pathCount += 1; - return { - ext: new Float32Array((pathCount + (pathCount & 1)) * 2), - st: new Float32Array(pathCount * 4), - }; - } - - private directlyRenderObject(pass: number, objectIndex: number): void { - if (this.meshBuffers == null || this.meshes == null) - return; - - const renderContext = this.renderContext; - const gl = renderContext.gl; - - const antialiasingStrategy = unwrapNull(this.antialiasingStrategy); - const renderingMode = antialiasingStrategy.directRenderingMode; - const objectCount = this.objectCount; - - const instanceRange = this.instanceRangeForObject(objectIndex); - if (instanceRange.isEmpty) - return; - - const pathRange = this.pathRangeForObject(objectIndex); - const meshIndex = this.meshIndexForObject(objectIndex); - - const meshes = this.meshBuffers![meshIndex]; - const meshData = this.meshes![meshIndex]; - - // Set up implicit cover state. - gl.depthFunc(gl.GREATER); - gl.depthMask(true); - gl.enable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - gl.cullFace(gl.BACK); - gl.frontFace(gl.CCW); - gl.enable(gl.CULL_FACE); - - // Set up the implicit cover interior VAO. - const directInteriorProgramName = this.directInteriorProgramName(renderingMode); - const directInteriorProgram = renderContext.shaderPrograms[directInteriorProgramName]; - if (this.implicitCoverInteriorVAO == null) { - this.implicitCoverInteriorVAO = renderContext.vertexArrayObjectExt - .createVertexArrayOES(); - } - renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.implicitCoverInteriorVAO); - this.initImplicitCoverInteriorVAO(objectIndex, instanceRange, renderingMode); - - // Draw direct interior parts. - if (renderingMode === 'conservative') - this.setTransformAffineUniforms(directInteriorProgram.uniforms, objectIndex); - else - this.setTransformUniform(directInteriorProgram.uniforms, pass, objectIndex); - this.setFramebufferSizeUniform(directInteriorProgram.uniforms); - this.setHintsUniform(directInteriorProgram.uniforms); - this.setPathColorsUniform(objectIndex, directInteriorProgram.uniforms, 0); - this.setEmboldenAmountUniform(objectIndex, directInteriorProgram.uniforms); - this.pathTransformBufferTextures[meshIndex].st.bind(gl, directInteriorProgram.uniforms, 1); - this.pathTransformBufferTextures[meshIndex] - .ext - .bind(gl, directInteriorProgram.uniforms, 2); - const bQuadInteriorRange = getMeshIndexRange(meshes.bQuadVertexInteriorIndexPathRanges, - pathRange); - if (!this.pathIDsAreInstanced) { - gl.drawElements(gl.TRIANGLES, - bQuadInteriorRange.length, - gl.UNSIGNED_INT, - bQuadInteriorRange.start * UINT32_SIZE); - } else { - renderContext.instancedArraysExt - .drawElementsInstancedANGLE(gl.TRIANGLES, - bQuadInteriorRange.length, - gl.UNSIGNED_INT, - 0, - instanceRange.length); - } - - gl.disable(gl.CULL_FACE); - - // Render curves, if applicable. - if (renderingMode !== 'conservative') { - // Set up direct curve state. - gl.depthMask(false); - gl.enable(gl.BLEND); - gl.blendEquation(gl.FUNC_ADD); - gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE); - - // Set up the direct curve VAO. - // - // TODO(pcwalton): Cache these. - const directCurveProgramName = this.directCurveProgramName(); - const directCurveProgram = renderContext.shaderPrograms[directCurveProgramName]; - if (this.implicitCoverCurveVAO == null) { - this.implicitCoverCurveVAO = renderContext.vertexArrayObjectExt - .createVertexArrayOES(); - } - renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.implicitCoverCurveVAO); - this.initImplicitCoverCurveVAO(objectIndex, instanceRange); - - // Draw direct curve parts. - this.setTransformUniform(directCurveProgram.uniforms, pass, objectIndex); - this.setFramebufferSizeUniform(directCurveProgram.uniforms); - this.setHintsUniform(directCurveProgram.uniforms); - this.setPathColorsUniform(objectIndex, directCurveProgram.uniforms, 0); - this.setEmboldenAmountUniform(objectIndex, directCurveProgram.uniforms); - this.pathTransformBufferTextures[meshIndex] - .st - .bind(gl, directCurveProgram.uniforms, 1); - this.pathTransformBufferTextures[meshIndex] - .ext - .bind(gl, directCurveProgram.uniforms, 2); - const coverCurveRange = getMeshIndexRange(meshes.bQuadVertexPositionPathRanges, - pathRange); - if (!this.pathIDsAreInstanced) { - gl.drawArrays(gl.TRIANGLES, coverCurveRange.start * 6, coverCurveRange.length * 6); - } else { - renderContext.instancedArraysExt - .drawArraysInstancedANGLE(gl.TRIANGLES, - 0, - coverCurveRange.length * 6, - instanceRange.length); - } - } - - renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); - - // Finish direct rendering. Right now, this performs compositing if necessary. - antialiasingStrategy.finishDirectlyRenderingObject(this, objectIndex); - } - - private finishTiming(): void { - const renderContext = this.renderContext; - - if (this.timerQueryPollInterval != null || - renderContext.timerQueryExt == null || - renderContext.atlasRenderingTimerQuery == null || - renderContext.compositingTimerQuery == null) { - return; - } - - renderContext.timerQueryExt.endQueryEXT(renderContext.timerQueryExt.TIME_ELAPSED_EXT); - - this.timerQueryPollInterval = window.setInterval(() => { - if (renderContext.timerQueryExt == null || - renderContext.atlasRenderingTimerQuery == null || - renderContext.compositingTimerQuery == null) { - return; - } - - for (const queryName of ['atlasRenderingTimerQuery', 'compositingTimerQuery'] as - Array<'atlasRenderingTimerQuery' | 'compositingTimerQuery'>) { - if (renderContext.timerQueryExt - .getQueryObjectEXT(renderContext[queryName] as WebGLQuery, - 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 initLUTTexture(imageName: 'gammaLUT' | 'areaLUT', - textureName: 'gammaLUTTexture' | 'areaLUTTexture'): - void { - const renderContext = this.renderContext; - const gl = renderContext.gl; - - const image = renderContext[imageName]; - const texture = unwrapNull(gl.createTexture()); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, gl.LUMINANCE, gl.UNSIGNED_BYTE, image); - const filter = imageName === 'gammaLUT' ? gl.NEAREST : gl.LINEAR; - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - - this[textureName] = texture; - } - - private initImplicitCoverCurveVAO(objectIndex: number, instanceRange: Range): void { - if (this.meshBuffers == null) - return; - - const renderContext = this.renderContext; - const gl = renderContext.gl; - - const meshIndex = this.meshIndexForObject(objectIndex); - const meshes = this.meshBuffers[meshIndex]; - const meshData = unwrapNull(this.meshes)[meshIndex]; - - const directCurveProgramName = this.directCurveProgramName(); - const directCurveProgram = renderContext.shaderPrograms[directCurveProgramName]; - gl.useProgram(directCurveProgram.program); - gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositions); - gl.vertexAttribPointer(directCurveProgram.attributes.aPosition, 2, gl.FLOAT, false, 0, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexIDVBO); - gl.vertexAttribPointer(directCurveProgram.attributes.aVertexID, 1, gl.FLOAT, false, 0, 0); - - if (this.pathIDsAreInstanced) - gl.bindBuffer(gl.ARRAY_BUFFER, this.instancedPathIDVBO); - else - gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositionPathIDs); - gl.vertexAttribPointer(directCurveProgram.attributes.aPathID, - 1, - gl.UNSIGNED_SHORT, - false, - 0, - instanceRange.start * UINT16_SIZE); - if (this.pathIDsAreInstanced) { - renderContext.instancedArraysExt - .vertexAttribDivisorANGLE(directCurveProgram.attributes.aPathID, 1); - } - - gl.enableVertexAttribArray(directCurveProgram.attributes.aPosition); - gl.enableVertexAttribArray(directCurveProgram.attributes.aVertexID); - gl.enableVertexAttribArray(directCurveProgram.attributes.aPathID); - } - - private initImplicitCoverInteriorVAO(objectIndex: number, - instanceRange: Range, - renderingMode: DirectRenderingMode): - void { - if (this.meshBuffers == null) - return; - - const renderContext = this.renderContext; - const gl = renderContext.gl; - - const meshIndex = this.meshIndexForObject(objectIndex); - const meshes = this.meshBuffers[meshIndex]; - - const directInteriorProgramName = this.directInteriorProgramName(renderingMode); - const directInteriorProgram = renderContext.shaderPrograms[directInteriorProgramName]; - gl.useProgram(directInteriorProgram.program); - gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositions); - gl.vertexAttribPointer(directInteriorProgram.attributes.aPosition, - 2, - gl.FLOAT, - false, - 0, - 0); - - if (this.pathIDsAreInstanced) - gl.bindBuffer(gl.ARRAY_BUFFER, this.instancedPathIDVBO); - else - gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bQuadVertexPositionPathIDs); - gl.vertexAttribPointer(directInteriorProgram.attributes.aPathID, - 1, - gl.UNSIGNED_SHORT, - false, - 0, - instanceRange.start * UINT16_SIZE); - if (this.pathIDsAreInstanced) { - renderContext.instancedArraysExt - .vertexAttribDivisorANGLE(directInteriorProgram.attributes.aPathID, 1); - } - - if (directInteriorProgramName === 'conservativeInterior') { - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexIDVBO); - gl.vertexAttribPointer(directInteriorProgram.attributes.aVertexID, - 1, - gl.FLOAT, - false, - 0, - 0); - } - - gl.enableVertexAttribArray(directInteriorProgram.attributes.aPosition); - gl.enableVertexAttribArray(directInteriorProgram.attributes.aPathID); - if (directInteriorProgramName === 'conservativeInterior') - gl.enableVertexAttribArray(directInteriorProgram.attributes.aVertexID); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, meshes.bQuadVertexInteriorIndices); - } - - private initInstancedPathIDVBO(): void { - const renderContext = this.renderContext; - const gl = renderContext.gl; - - const pathIDs = new Uint16Array(MAX_PATHS); - for (let pathIndex = 0; pathIndex < MAX_PATHS; pathIndex++) - pathIDs[pathIndex] = pathIndex + 1; - - this.instancedPathIDVBO = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this.instancedPathIDVBO); - gl.bufferData(gl.ARRAY_BUFFER, pathIDs, gl.STATIC_DRAW); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - } - - private initVertexIDVBO(): void { - const renderContext = this.renderContext; - const gl = renderContext.gl; - - const vertexIDs = new Float32Array(MAX_VERTICES); - for (let vertexID = 0; vertexID < MAX_VERTICES; vertexID++) - vertexIDs[vertexID] = vertexID; - - this.vertexIDVBO = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexIDVBO); - gl.bufferData(gl.ARRAY_BUFFER, vertexIDs, gl.STATIC_DRAW); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - } - - private computeTransform(pass: number, objectIndex: number): glmatrix.mat4 { - let transform; - if (this.antialiasingStrategy == null) - transform = glmatrix.mat4.create(); - else - transform = this.antialiasingStrategy.worldTransformForPass(this, pass); - - glmatrix.mat4.mul(transform, transform, this.worldTransform); - glmatrix.mat4.mul(transform, transform, this.getModelviewTransform(objectIndex)); - return transform; - } -} - -function getMeshIndexRange(indexRanges: Range[], pathRange: Range): Range { - if (indexRanges.length === 0) - return new Range(0, 0); - - const lastIndexRange = unwrapUndef(_.last(indexRanges)); - const descending = indexRanges[0].start > lastIndexRange.start; - - pathRange = new Range(pathRange.start - 1, pathRange.end - 1); - - let startIndex; - if (pathRange.start >= indexRanges.length) - startIndex = lastIndexRange.end; - else if (!descending) - startIndex = indexRanges[pathRange.start].start; - else - startIndex = indexRanges[pathRange.start].end; - - let endIndex; - if (descending) - endIndex = indexRanges[pathRange.end - 1].start; - else if (pathRange.end >= indexRanges.length) - endIndex = lastIndexRange.end; - else - endIndex = indexRanges[pathRange.end].start; - - if (descending) { - const tmp = startIndex; - startIndex = endIndex; - endIndex = tmp; - } - - return new Range(startIndex, endIndex); -} diff --git a/demo/client/src/shader-loader.ts b/demo/client/src/shader-loader.ts deleted file mode 100644 index ad2e9e66..00000000 --- a/demo/client/src/shader-loader.ts +++ /dev/null @@ -1,189 +0,0 @@ -// pathfinder/demo/client/src/shader-loader.ts -// -// Copyright © 2017 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. - -import {AttributeMap, UniformMap} from './gl-utils'; -import {expectNotNull, PathfinderError, unwrapNull} from './utils'; - -export interface ShaderMap { - blitLinear: T; - blitGamma: T; - conservativeInterior: T; - demo3DDistantGlyph: T; - demo3DMonument: T; - directCurve: T; - directInterior: T; - direct3DCurve: T; - direct3DInterior: T; - mcaa: T; - ssaaSubpixelResolve: T; - stencilAAA: T; - xcaaMonoResolve: T; - xcaaMonoSubpixelResolve: T; -} - -export interface UnlinkedShaderProgram { - vertex: WebGLShader; - fragment: WebGLShader; -} - -const COMMON_SHADER_URL: string = '/glsl/gles2/common.inc.glsl'; - -export const SHADER_NAMES: Array> = [ - 'blitLinear', - 'blitGamma', - 'conservativeInterior', - 'directCurve', - 'directInterior', - 'direct3DCurve', - 'direct3DInterior', - 'ssaaSubpixelResolve', - 'mcaa', - 'stencilAAA', - 'xcaaMonoResolve', - 'xcaaMonoSubpixelResolve', - 'demo3DDistantGlyph', - 'demo3DMonument', -]; - -const SHADER_URLS: ShaderMap = { - blitGamma: { - fragment: "/glsl/gles2/blit-gamma.fs.glsl", - vertex: "/glsl/gles2/blit.vs.glsl", - }, - blitLinear: { - fragment: "/glsl/gles2/blit-linear.fs.glsl", - vertex: "/glsl/gles2/blit.vs.glsl", - }, - conservativeInterior: { - fragment: "/glsl/gles2/direct-interior.fs.glsl", - vertex: "/glsl/gles2/conservative-interior.vs.glsl", - }, - demo3DDistantGlyph: { - fragment: "/glsl/gles2/demo-3d-distant-glyph.fs.glsl", - vertex: "/glsl/gles2/demo-3d-distant-glyph.vs.glsl", - }, - demo3DMonument: { - fragment: "/glsl/gles2/demo-3d-monument.fs.glsl", - vertex: "/glsl/gles2/demo-3d-monument.vs.glsl", - }, - direct3DCurve: { - fragment: "/glsl/gles2/direct-curve.fs.glsl", - vertex: "/glsl/gles2/direct-3d-curve.vs.glsl", - }, - direct3DInterior: { - fragment: "/glsl/gles2/direct-interior.fs.glsl", - vertex: "/glsl/gles2/direct-3d-interior.vs.glsl", - }, - directCurve: { - fragment: "/glsl/gles2/direct-curve.fs.glsl", - vertex: "/glsl/gles2/direct-curve.vs.glsl", - }, - directInterior: { - fragment: "/glsl/gles2/direct-interior.fs.glsl", - vertex: "/glsl/gles2/direct-interior.vs.glsl", - }, - mcaa: { - fragment: "/glsl/gles2/mcaa.fs.glsl", - vertex: "/glsl/gles2/mcaa.vs.glsl", - }, - ssaaSubpixelResolve: { - fragment: "/glsl/gles2/ssaa-subpixel-resolve.fs.glsl", - vertex: "/glsl/gles2/blit.vs.glsl", - }, - stencilAAA: { - fragment: "/glsl/gles2/stencil-aaa.fs.glsl", - vertex: "/glsl/gles2/stencil-aaa.vs.glsl", - }, - xcaaMonoResolve: { - fragment: "/glsl/gles2/xcaa-mono-resolve.fs.glsl", - vertex: "/glsl/gles2/xcaa-mono-resolve.vs.glsl", - }, - xcaaMonoSubpixelResolve: { - fragment: "/glsl/gles2/xcaa-mono-subpixel-resolve.fs.glsl", - vertex: "/glsl/gles2/xcaa-mono-subpixel-resolve.vs.glsl", - }, -}; - -export interface ShaderProgramSource { - vertex: string; - fragment: string; -} - -interface ShaderProgramURLs { - vertex: string; - fragment: string; -} - -export class ShaderLoader { - common!: Promise; - shaders!: Promise>; - - load(): void { - this.common = window.fetch(COMMON_SHADER_URL).then(response => response.text()); - - const shaderKeys = Object.keys(SHADER_URLS) as Array>; - const promises = []; - for (const shaderKey of shaderKeys) { - promises.push(Promise.all([ - window.fetch(SHADER_URLS[shaderKey].vertex).then(response => response.text()), - window.fetch(SHADER_URLS[shaderKey].fragment).then(response => response.text()), - ]).then(results => ({ vertex: results[0], fragment: results[1] }))); - } - - this.shaders = Promise.all(promises).then(promises => { - const shaderMap: Partial> = {}; - for (let keyIndex = 0; keyIndex < shaderKeys.length; keyIndex++) - shaderMap[shaderKeys[keyIndex]] = promises[keyIndex]; - return shaderMap as ShaderMap; - }); - } -} - -export class PathfinderShaderProgram { - readonly uniforms: UniformMap; - readonly attributes: AttributeMap; - readonly program: WebGLProgram; - readonly programName: string; - - constructor(gl: WebGLRenderingContext, - programName: string, - unlinkedShaderProgram: UnlinkedShaderProgram) { - this.programName = programName; - - this.program = expectNotNull(gl.createProgram(), "Failed to create shader program!"); - for (const compiledShader of Object.values(unlinkedShaderProgram)) - gl.attachShader(this.program, compiledShader); - gl.linkProgram(this.program); - - if (gl.getProgramParameter(this.program, gl.LINK_STATUS) === 0) { - const infoLog = gl.getProgramInfoLog(this.program); - throw new PathfinderError(`Failed to link program "${programName}":\n${infoLog}`); - } - - const uniformCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS); - const attributeCount = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES); - - const uniforms: UniformMap = {}; - const attributes: AttributeMap = {}; - - for (let uniformIndex = 0; uniformIndex < uniformCount; uniformIndex++) { - const uniformName = unwrapNull(gl.getActiveUniform(this.program, uniformIndex)).name; - uniforms[uniformName] = expectNotNull(gl.getUniformLocation(this.program, uniformName), - `Didn't find uniform "${uniformName}"!`); - } - for (let attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) { - const attributeName = unwrapNull(gl.getActiveAttrib(this.program, attributeIndex)).name; - attributes[attributeName] = attributeIndex; - } - - this.uniforms = uniforms; - this.attributes = attributes; - } -} diff --git a/demo/client/src/ssaa-strategy.ts b/demo/client/src/ssaa-strategy.ts deleted file mode 100644 index 61d9aa40..00000000 --- a/demo/client/src/ssaa-strategy.ts +++ /dev/null @@ -1,202 +0,0 @@ -// pathfinder/demo/client/src/ssaa-strategy.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; - -import {AntialiasingStrategy, DirectRenderingMode, SubpixelAAType, TileInfo} from './aa-strategy'; -import {createFramebuffer, createFramebufferColorTexture} from './gl-utils'; -import {createFramebufferDepthTexture, setTextureParameters} from './gl-utils'; -import {Renderer} from './renderer'; -import {unwrapNull} from './utils'; -import {DemoView} from './view'; - -export default class SSAAStrategy extends AntialiasingStrategy { - get passCount(): number { - switch (this.level) { - case 16: - return 4; - case 8: - return 2; - } - return 1; - } - - private level: number; - - private destFramebufferSize: glmatrix.vec2; - private supersampledFramebufferSize: glmatrix.vec2; - private supersampledColorTexture!: WebGLTexture; - private supersampledDepthTexture!: WebGLTexture; - private supersampledFramebuffer!: WebGLFramebuffer; - - constructor(level: number, subpixelAA: SubpixelAAType) { - super(subpixelAA); - - this.level = level; - this.subpixelAA = subpixelAA; - this.destFramebufferSize = glmatrix.vec2.create(); - this.supersampledFramebufferSize = glmatrix.vec2.create(); - } - - attachMeshes(renderer: Renderer): void {} - - setFramebufferSize(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - this.destFramebufferSize = glmatrix.vec2.clone(renderer.destAllocatedSize); - - this.supersampledFramebufferSize = glmatrix.vec2.create(); - glmatrix.vec2.mul(this.supersampledFramebufferSize, - this.destFramebufferSize, - this.supersampleScale); - - this.supersampledColorTexture = - createFramebufferColorTexture(gl, - this.supersampledFramebufferSize, - renderContext.colorAlphaFormat, - gl.LINEAR); - - this.supersampledDepthTexture = - createFramebufferDepthTexture(gl, this.supersampledFramebufferSize); - - this.supersampledFramebuffer = createFramebuffer(gl, - this.supersampledColorTexture, - this.supersampledDepthTexture); - - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - } - - get transform(): glmatrix.mat4 { - const scale = glmatrix.vec2.create(); - glmatrix.vec2.div(scale, this.supersampledFramebufferSize, this.destFramebufferSize); - - const transform = glmatrix.mat4.create(); - glmatrix.mat4.fromScaling(transform, [scale[0], scale[1], 1.0]); - return transform; - } - - prepareForRendering(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - const framebufferSize = this.supersampledFramebufferSize; - const usedSize = this.usedSupersampledFramebufferSize(renderer); - gl.bindFramebuffer(gl.FRAMEBUFFER, this.supersampledFramebuffer); - gl.viewport(0, 0, framebufferSize[0], framebufferSize[1]); - gl.scissor(0, 0, usedSize[0], usedSize[1]); - gl.enable(gl.SCISSOR_TEST); - - const clearColor = renderer.backgroundColor; - gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); - gl.clearDepth(0.0); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - prepareForDirectRendering(renderer: Renderer): void {} - - prepareToRenderObject(renderer: Renderer, objectIndex: number): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - gl.bindFramebuffer(gl.FRAMEBUFFER, this.supersampledFramebuffer); - gl.viewport(0, - 0, - this.supersampledFramebufferSize[0], - this.supersampledFramebufferSize[1]); - gl.disable(gl.SCISSOR_TEST); - } - - finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void {} - - antialiasObject(renderer: Renderer): void {} - - finishAntialiasingObject(renderer: Renderer, objectIndex: number): void {} - - resolveAAForObject(renderer: Renderer): void {} - - resolve(pass: number, renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - gl.bindFramebuffer(gl.FRAMEBUFFER, renderer.destFramebuffer); - renderer.setDrawViewport(); - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - - // Set up the blit program VAO. - let resolveProgram; - if (this.subpixelAA !== 'none') - resolveProgram = renderContext.shaderPrograms.ssaaSubpixelResolve; - else - resolveProgram = renderContext.shaderPrograms.blitLinear; - gl.useProgram(resolveProgram.program); - renderContext.initQuadVAO(resolveProgram.attributes); - - // Resolve framebuffer. - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this.supersampledColorTexture); - gl.uniform1i(resolveProgram.uniforms.uSource, 0); - gl.uniform2i(resolveProgram.uniforms.uSourceDimensions, - this.supersampledFramebufferSize[0], - this.supersampledFramebufferSize[1]); - const tileInfo = this.tileInfoForPass(pass); - renderer.setTransformAndTexScaleUniformsForDest(resolveProgram.uniforms, tileInfo); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer); - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0); - } - - worldTransformForPass(renderer: Renderer, pass: number): glmatrix.mat4 { - const tileInfo = this.tileInfoForPass(pass); - const usedSize = renderer.destUsedSize; - const transform = glmatrix.mat4.create(); - glmatrix.mat4.fromTranslation(transform, [-1.0, -1.0, 0.0]); - glmatrix.mat4.scale(transform, transform, [tileInfo.size[0], tileInfo.size[1], 1.0]); - glmatrix.mat4.translate(transform, transform, [ - -tileInfo.position[0] / tileInfo.size[0] * 2.0, - -tileInfo.position[1] / tileInfo.size[1] * 2.0, - 0.0, - ]); - glmatrix.mat4.translate(transform, transform, [1.0, 1.0, 0.0]); - return transform; - } - - private tileInfoForPass(pass: number): TileInfo { - const tileSize = this.tileSize; - return { - position: glmatrix.vec2.clone([pass % tileSize[0], Math.floor(pass / tileSize[0])]), - size: tileSize, - }; - } - - get directRenderingMode(): DirectRenderingMode { - return 'color'; - } - - private get supersampleScale(): glmatrix.vec2 { - return glmatrix.vec2.clone([this.subpixelAA !== 'none' ? 3 : 2, this.level === 2 ? 1 : 2]); - } - - private get tileSize(): glmatrix.vec2 { - switch (this.level) { - case 16: - return glmatrix.vec2.clone([2.0, 2.0]); - case 8: - return glmatrix.vec2.clone([2.0, 1.0]); - } - return glmatrix.vec2.clone([1.0, 1.0]); - } - - private usedSupersampledFramebufferSize(renderer: Renderer): glmatrix.vec2 { - const result = glmatrix.vec2.create(); - glmatrix.vec2.mul(result, renderer.destUsedSize, this.supersampleScale); - return result; - } -} diff --git a/demo/client/src/svg-demo.ts b/demo/client/src/svg-demo.ts deleted file mode 100644 index e62d6c6e..00000000 --- a/demo/client/src/svg-demo.ts +++ /dev/null @@ -1,120 +0,0 @@ -// pathfinder/demo/client/src/svg-demo.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; -import * as _ from 'lodash'; - -import {DemoAppController} from './app-controller'; -import {OrthographicCamera} from "./camera"; -import {PathfinderPackedMeshes} from "./meshes"; -import {ShaderMap, ShaderProgramSource} from './shader-loader'; -import {BUILTIN_SVG_URI, SVGLoader} from './svg-loader'; -import {SVGRenderer} from './svg-renderer'; -import {DemoView} from './view'; - -const parseColor = require('parse-color'); - -const SVG_NS: string = "http://www.w3.org/2000/svg"; - -const DEFAULT_FILE: string = 'tiger'; - -class SVGDemoController extends DemoAppController { - loader!: SVGLoader; - - protected readonly builtinFileURI: string = BUILTIN_SVG_URI; - - private meshes!: PathfinderPackedMeshes; - - start() { - super.start(); - - this.loader = new SVGLoader; - - this.loadInitialFile(this.builtinFileURI); - } - - protected fileLoaded(fileData: ArrayBuffer) { - this.loader.loadFile(fileData); - this.loader.partition().then(meshes => { - this.meshes = new PathfinderPackedMeshes(meshes); - this.meshesReceived(); - }); - } - - protected createView(areaLUT: HTMLImageElement, - gammaLUT: HTMLImageElement, - commonShaderSource: string, - shaderSources: ShaderMap): - SVGDemoView { - return new SVGDemoView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources); - } - - protected get defaultFile(): string { - return DEFAULT_FILE; - } - - private meshesReceived(): void { - this.view.then(view => { - view.attachMeshes([this.meshes]); - view.initCameraBounds(this.loader.svgViewBox); - }); - } -} - -class SVGDemoView extends DemoView { - renderer: SVGDemoRenderer; - appController: SVGDemoController; - - get camera(): OrthographicCamera { - return this.renderer.camera; - } - - constructor(appController: SVGDemoController, - areaLUT: HTMLImageElement, - gammaLUT: HTMLImageElement, - commonShaderSource: string, - shaderSources: ShaderMap) { - super(areaLUT, gammaLUT, commonShaderSource, shaderSources); - - this.appController = appController; - this.renderer = new SVGDemoRenderer(this, {sizeToFit: true}); - - this.resizeToFit(true); - } - - initCameraBounds(viewBox: glmatrix.vec4): void { - this.renderer.initCameraBounds(viewBox); - } -} - -class SVGDemoRenderer extends SVGRenderer { - renderContext!: SVGDemoView; - - needsStencil: boolean = false; - - protected get loader(): SVGLoader { - return this.renderContext.appController.loader; - } - - protected get canvas(): HTMLCanvasElement { - return this.renderContext.canvas; - } - - protected newTimingsReceived(): void { - this.renderContext.appController.newTimingsReceived(this.lastTimings); - } -} - -function main() { - const controller = new SVGDemoController; - window.addEventListener('load', () => controller.start(), false); -} - -main(); diff --git a/demo/client/src/svg-loader.ts b/demo/client/src/svg-loader.ts deleted file mode 100644 index a813d1b8..00000000 --- a/demo/client/src/svg-loader.ts +++ /dev/null @@ -1,254 +0,0 @@ -// pathfinder/client/src/svg-loader.ts -// -// Copyright © 2017 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. - -import * as base64js from 'base64-js'; -import * as glmatrix from 'gl-matrix'; -import * as _ from 'lodash'; - -import 'path-data-polyfill.js'; -import {parseServerTiming, PathfinderMeshPack, PathfinderPackedMeshes} from "./meshes"; -import {lerp, panic, Range, unwrapNull, unwrapUndef} from "./utils"; - -export const BUILTIN_SVG_URI: string = "/svg/demo"; - -const parseColor = require('parse-color'); - -const PARTITION_SVG_PATHS_ENDPOINT_URL: string = "/partition-svg-paths"; - -declare class SVGPathSegment { - type: string; - values: number[]; -} - -declare global { - interface SVGPathElement { - getPathData(settings: any): SVGPathSegment[]; - } -} - -type FillRule = 'evenodd' | 'winding'; - -export abstract class SVGPath { - element: SVGPathElement; - color: glmatrix.vec4; - - constructor(element: SVGPathElement, colorProperty: keyof CSSStyleDeclaration) { - this.element = element; - - const style = window.getComputedStyle(element); - this.color = unwrapNull(colorFromStyle(style[colorProperty])); - } -} - -export class SVGFill extends SVGPath { - fillRule: FillRule; - - get pathfinderFillRule(): string { - return { evenodd: 'EvenOdd', winding: 'Winding' }[this.fillRule]; - } - - constructor(element: SVGPathElement) { - super(element, 'fill'); - - const style = window.getComputedStyle(element); - this.fillRule = style.fillRule === 'evenodd' ? 'evenodd' : 'winding'; - } -} - -export class SVGStroke extends SVGPath { - width: number; - - constructor(element: SVGPathElement) { - super(element, 'stroke'); - - const style = window.getComputedStyle(element); - const ctm = unwrapNull(element.getCTM()); - - const strokeWidthString = unwrapNull(style.strokeWidth); - const matches = /^(\d+\.?\d*)(.*)$/.exec(strokeWidthString); - let strokeWidth: number; - if (matches == null) { - strokeWidth = 0.0; - } else { - strokeWidth = unwrapNull(parseFloat(matches[1])); - if (matches[2] === 'px') - strokeWidth *= lerp(ctm.a, ctm.d, 0.5); - } - - this.width = strokeWidth; - } -} - -interface ClipPathIDTable { - [id: string]: number; -} - -export class SVGLoader { - pathInstances: SVGPath[]; - scale: number; - pathBounds: glmatrix.vec4[]; - svgBounds: glmatrix.vec4; - svgViewBox: glmatrix.vec4; - isMonochrome: boolean; - - private svg: SVGSVGElement; - private fileData!: ArrayBuffer; - - private paths: any[]; - private clipPathIDs: ClipPathIDTable; - - constructor() { - this.scale = 1.0; - this.pathInstances = []; - this.pathBounds = []; - this.paths = []; - this.clipPathIDs = {}; - this.svgBounds = glmatrix.vec4.create(); - this.svgViewBox = glmatrix.vec4.create(); - this.svg = unwrapNull(document.getElementById('pf-svg')) as Element as SVGSVGElement; - this.isMonochrome = false; - } - - loadFile(fileData: ArrayBuffer) { - this.fileData = fileData; - - const decoder = new (window as any).TextDecoder('utf-8'); - const fileStringData = decoder.decode(new DataView(this.fileData)); - const svgDocument = (new DOMParser).parseFromString(fileStringData, 'image/svg+xml'); - const svgElement = svgDocument.documentElement as Element as SVGSVGElement; - this.attachSVG(svgElement); - } - - partition(pathIndex?: number | undefined): Promise { - // Make the request. - const paths = pathIndex == null ? this.paths : [this.paths[pathIndex]]; - let time = 0; - return window.fetch(PARTITION_SVG_PATHS_ENDPOINT_URL, { - body: JSON.stringify({ - paths: paths, - viewBoxHeight: this.svgViewBox[3] - this.svgViewBox[1], - viewBoxWidth: this.svgViewBox[2] - this.svgViewBox[0], - }), - headers: {'Content-Type': 'application/json'} as any, - method: 'POST', - }).then(response => { - time = parseServerTiming(response.headers); - return response.arrayBuffer(); - }).then(buffer => new PathfinderMeshPack(buffer)); - } - - private attachSVG(svgElement: SVGSVGElement) { - // Clear out the current document. - let kid; - while ((kid = this.svg.firstChild) != null) - this.svg.removeChild(kid); - - // Add all kids of the incoming SVG document. - while ((kid = svgElement.firstChild) != null) - this.svg.appendChild(kid); - - const viewBox = svgElement.viewBox.baseVal; - this.svg.setAttribute('width', "" + viewBox.width); - this.svg.setAttribute('height', "" + viewBox.height); - - // Scan for geometry elements. - this.pathInstances.length = 0; - this.clipPathIDs = {}; - this.scanElement(this.svg); - - this.paths = []; - - for (const instance of this.pathInstances) { - const element = instance.element; - const svgCTM = unwrapNull(element.getCTM()); - const ctm = glmatrix.mat2d.fromValues(svgCTM.a, -svgCTM.b, - svgCTM.c, -svgCTM.d, - svgCTM.e, viewBox.height - svgCTM.f); - glmatrix.mat2d.scale(ctm, ctm, [this.scale, this.scale]); - - const bottomLeft = glmatrix.vec2.create(); - const topRight = glmatrix.vec2.create(); - - const segments = element.getPathData({ normalize: true }).map(segment => { - const newValues = _.flatMap(_.chunk(segment.values, 2), coords => { - const point = glmatrix.vec2.create(); - glmatrix.vec2.transformMat2d(point, coords, ctm); - - glmatrix.vec2.min(bottomLeft, bottomLeft, point); - glmatrix.vec2.max(topRight, topRight, point); - - return [point[0], point[1]]; - }); - return { - type: segment.type, - values: newValues, - }; - }); - - const pathBounds = glmatrix.vec4.clone([ - bottomLeft[0], bottomLeft[1], - topRight[0], topRight[1], - ]); - - if (instance instanceof SVGFill) { - this.paths.push({ - kind: { Fill: instance.pathfinderFillRule }, - segments: segments, - }); - this.pathBounds.push(pathBounds); - } else if (instance instanceof SVGStroke) { - this.paths.push({ kind: { Stroke: instance.width }, segments: segments }); - this.pathBounds.push(pathBounds); - } - } - - this.isMonochrome = this.pathInstances.every(pathInstance => { - return glmatrix.vec4.equals(pathInstance.color, this.pathInstances[0].color); - }); - - this.svgViewBox = glmatrix.vec4.clone([ - viewBox.x, viewBox.y, viewBox.x + viewBox.width, viewBox.y + viewBox.height, - ]); - } - - private scanElement(element: Element): void { - const style = window.getComputedStyle(element); - - if (element instanceof SVGPathElement) { - if (colorFromStyle(style.fill) != null) - this.addPathInstance(new SVGFill(element)); - if (colorFromStyle(style.stroke) != null) - this.addPathInstance(new SVGStroke(element)); - } - - for (const kid of element.childNodes) { - if (kid instanceof Element) - this.scanElement(kid); - } - } - - private addPathInstance(pathInstance: SVGPath): void { - this.pathInstances.push(pathInstance); - } -} - -function colorFromStyle(style: string | null): glmatrix.vec4 | null { - if (style == null || style === 'none') - return null; - - // TODO(pcwalton): Gradients? - const color = parseColor(style); - if (color.rgba == null) - return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]); - - if (color.rgba[3] === 0.0) - return null; - return glmatrix.vec4.clone(color.rgba); -} diff --git a/demo/client/src/svg-renderer.ts b/demo/client/src/svg-renderer.ts deleted file mode 100644 index 1947e080..00000000 --- a/demo/client/src/svg-renderer.ts +++ /dev/null @@ -1,217 +0,0 @@ -// pathfinder/demo/client/src/svg-renderer.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; - -import {AntialiasingStrategy, AntialiasingStrategyName, DirectRenderingMode} from './aa-strategy'; -import {NoAAStrategy} from './aa-strategy'; -import {SubpixelAAType} from './aa-strategy'; -import {OrthographicCamera} from "./camera"; -import {UniformMap} from './gl-utils'; -import {PathfinderPackedMeshes} from './meshes'; -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 {MCAAStrategy, XCAAStrategy} from './xcaa-strategy'; - -interface AntialiasingStrategyTable { - none: typeof NoAAStrategy; - ssaa: typeof SSAAStrategy; - xcaa: typeof MCAAStrategy; -} - -const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { - none: NoAAStrategy, - ssaa: SSAAStrategy, - xcaa: MCAAStrategy, -}; - -export interface SVGRendererOptions { - sizeToFit?: boolean; - fixed?: boolean; -} - -export abstract class SVGRenderer extends Renderer { - renderContext!: RenderContext; - - camera: OrthographicCamera; - - needsStencil: boolean = false; - - private options: SVGRendererOptions; - - get isMulticolor(): boolean { - return !this.loader.isMonochrome; - } - - get bgColor(): glmatrix.vec4 { - return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]); - } - - get fgColor(): glmatrix.vec4 { - return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]); - } - - 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 backgroundColor(): glmatrix.vec4 { - return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]); - } - - get allowSubpixelAA(): boolean { - return false; - } - - protected get objectCount(): number { - return 1; - } - - protected abstract get loader(): SVGLoader; - protected abstract get canvas(): HTMLCanvasElement; - - constructor(renderContext: RenderContext, options: SVGRendererOptions) { - super(renderContext); - - this.options = options; - - // FIXME(pcwalton): Get the canvas a better way? - this.camera = new OrthographicCamera((this as any).canvas, { - fixed: !!this.options.fixed, - 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: PathfinderPackedMeshes[]): void { - super.attachMeshes(meshes); - this.uploadPathColors(1); - this.uploadPathTransforms(1); - } - - initCameraBounds(svgViewBox: glmatrix.vec4): void { - // The SVG origin is in the upper left, but the camera origin is in the lower left. - this.camera.bounds = svgViewBox; - - if (this.options.sizeToFit) - this.camera.zoomToFit(); - } - - meshIndexForObject(objectIndex: number): number { - return 0; - } - - pathRangeForObject(objectIndex: number): Range { - return new Range(1, this.loader.pathInstances.length + 1); - } - - pathTransformsForObject(objectIndex: number): PathTransformBuffers { - 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 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 { - return 'directCurve'; - } - - protected directInteriorProgramName(renderingMode: DirectRenderingMode): - keyof ShaderMap { - return renderingMode === 'conservative' ? 'conservativeInterior' : '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 = instances[pathIndex].color; - pathColors.set(instances[pathIndex].color, startOffset); - pathColors[startOffset + 3] = color[3] * 255; - } - - return pathColors; - } - - protected createAAStrategy(aaType: AntialiasingStrategyName, - aaLevel: number, - subpixelAA: SubpixelAAType): - AntialiasingStrategy { - return new (ANTIALIASING_STRATEGIES[aaType])(aaLevel, subpixelAA); - } - - protected compositeIfNecessary(): void {} -} diff --git a/demo/client/src/text-demo.ts b/demo/client/src/text-demo.ts deleted file mode 100644 index b6b0e0d4..00000000 --- a/demo/client/src/text-demo.ts +++ /dev/null @@ -1,470 +0,0 @@ -// pathfinder/client/src/text-demo.ts -// -// 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. - -import * as base64js from 'base64-js'; -import * as glmatrix from 'gl-matrix'; -import * as _ from 'lodash'; -import * as opentype from 'opentype.js'; - -import {Metrics} from 'opentype.js'; -import {StemDarkeningMode, SubpixelAAType} from './aa-strategy'; -import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; -import {AAOptions, DemoAppController, setSwitchInputsValue, SwitchInputs} from './app-controller'; -import {Atlas, ATLAS_SIZE, AtlasGlyph, GlyphKey, SUBPIXEL_GRANULARITY} from './atlas'; -import PathfinderBufferTexture from './buffer-texture'; -import {CameraView, OrthographicCamera} from "./camera"; -import {createFramebuffer, createFramebufferColorTexture} from './gl-utils'; -import {createFramebufferDepthTexture, QUAD_ELEMENTS, setTextureParameters} from './gl-utils'; -import {UniformMap} from './gl-utils'; -import {PathfinderMeshPack, PathfinderPackedMeshBuffers, PathfinderPackedMeshes} from './meshes'; -import {PathfinderShaderProgram, ShaderMap, ShaderProgramSource} from './shader-loader'; -import SSAAStrategy from './ssaa-strategy'; -import {calculatePixelRectForGlyph, PathfinderFont} from "./text"; -import {BUILTIN_FONT_URI, calculatePixelXMin, computeStemDarkeningAmount} from "./text"; -import {GlyphStore, Hint, SimpleTextLayout, UnitMetrics} from "./text"; -import {SimpleTextLayoutRenderContext, SimpleTextLayoutRenderer} from './text-renderer'; -import {TextRenderContext, TextRenderer} from './text-renderer'; -import {assert, expectNotNull, panic, PathfinderError, scaleRect, UINT32_SIZE} from './utils'; -import {unwrapNull} from './utils'; -import {DemoView, RenderContext, Timings, TIMINGS} from './view'; - -const DEFAULT_TEXT: string = -`’Twas brillig, and the slithy toves -Did gyre and gimble in the wabe; -All mimsy were the borogoves, -And the mome raths outgrabe. - -“Beware the Jabberwock, my son! -The jaws that bite, the claws that catch! -Beware the Jubjub bird, and shun -The frumious Bandersnatch!” - -He took his vorpal sword in hand: -Long time the manxome foe he sought— -So rested he by the Tumtum tree, -And stood awhile in thought. - -And as in uffish thought he stood, -The Jabberwock, with eyes of flame, -Came whiffling through the tulgey wood, -And burbled as it came! - -One, two! One, two! And through and through -The vorpal blade went snicker-snack! -He left it dead, and with its head -He went galumphing back. - -“And hast thou slain the Jabberwock? -Come to my arms, my beamish boy! -O frabjous day! Callooh! Callay!” -He chortled in his joy. - -’Twas brillig, and the slithy toves -Did gyre and gimble in the wabe; -All mimsy were the borogoves, -And the mome raths outgrabe.`; - -const INITIAL_FONT_SIZE: number = 72.0; - -const DEFAULT_FONT: string = 'open-sans'; - -const B_POSITION_SIZE: number = 8; - -const B_PATH_INDEX_SIZE: number = 2; - -declare global { - interface Window { - jQuery(element: HTMLElement): JQuerySubset; - } -} - -interface TabShownEvent { - target: EventTarget; - relatedTarget: EventTarget; -} - -interface JQuerySubset { - modal(options?: any): void; - on(name: 'shown.bs.tab', handler: (event: TabShownEvent) => void): void; -} - -type Matrix4D = Float32Array; - -type Rect = glmatrix.vec4; - -interface Point2D { - x: number; - y: number; -} - -type Size2D = glmatrix.vec2; - -type ShaderType = number; - -// `opentype.js` monkey patches - -declare module 'opentype.js' { - interface Font { - isSupported(): boolean; - lineHeight(): number; - } - interface Glyph { - getIndex(): number; - } -} - -class TextDemoController extends DemoAppController { - font!: PathfinderFont; - layout!: SimpleTextLayout; - glyphStore!: GlyphStore; - atlasGlyphs!: AtlasGlyph[]; - - private hintingSelect!: HTMLSelectElement; - private emboldenInput!: HTMLInputElement; - - private editTextModal!: HTMLElement; - private editTextArea!: HTMLTextAreaElement; - - private _atlas: Atlas; - - private meshes!: PathfinderPackedMeshes; - - private _fontSize!: number; - private _rotationAngle!: number; - private _emboldenAmount!: number; - - private text: string; - - constructor() { - super(); - this.text = DEFAULT_TEXT; - this._atlas = new Atlas; - } - - start(): void { - this._fontSize = INITIAL_FONT_SIZE; - this._rotationAngle = 0.0; - this._emboldenAmount = 0.0; - - this.hintingSelect = unwrapNull(document.getElementById('pf-hinting-select')) as - HTMLSelectElement; - this.hintingSelect.addEventListener('change', () => this.hintingChanged(), false); - - this.emboldenInput = unwrapNull(document.getElementById('pf-embolden')) as - HTMLInputElement; - this.emboldenInput.addEventListener('input', () => this.emboldenAmountChanged(), false); - - this.editTextModal = unwrapNull(document.getElementById('pf-edit-text-modal')); - this.editTextArea = unwrapNull(document.getElementById('pf-edit-text-area')) as - HTMLTextAreaElement; - - const editTextOkButton = unwrapNull(document.getElementById('pf-edit-text-ok-button')); - editTextOkButton.addEventListener('click', () => this.updateText(), false); - - super.start(); - - this.loadInitialFile(this.builtinFileURI); - } - - showTextEditor(): void { - this.editTextArea.value = this.text; - - window.jQuery(this.editTextModal).modal(); - } - - get emboldenAmount(): number { - return this._emboldenAmount; - } - - protected updateUIForAALevelChange(aaType: AntialiasingStrategyName, aaLevel: number): void { - const gammaCorrectionSwitchInputs = unwrapNull(this.gammaCorrectionSwitchInputs); - const stemDarkeningSwitchInputs = unwrapNull(this.stemDarkeningSwitchInputs); - const emboldenInput = unwrapNull(this.emboldenInput); - const emboldenLabel = getLabelFor(emboldenInput); - - switch (aaType) { - case 'none': - case 'ssaa': - enableSwitchInputs(gammaCorrectionSwitchInputs, false); - enableSwitchInputs(stemDarkeningSwitchInputs, false); - emboldenInput.value = "0"; - emboldenInput.disabled = true; - enableLabel(emboldenLabel, false); - break; - - case 'xcaa': - enableSwitchInputs(gammaCorrectionSwitchInputs, true); - enableSwitchInputs(stemDarkeningSwitchInputs, true); - emboldenInput.value = "0"; - emboldenInput.disabled = false; - enableLabel(emboldenLabel, true); - break; - } - - function enableSwitchInputs(switchInputs: SwitchInputs, enabled: boolean): void { - switchInputs.off.disabled = switchInputs.on.disabled = !enabled; - enableLabel(getLabelFor(switchInputs.on), enabled); - } - - function enableLabel(label: HTMLLabelElement | null, enabled: boolean): void { - if (label == null) - return; - - if (enabled) - label.classList.remove('pf-disabled'); - else - label.classList.add('pf-disabled'); - } - - function getLabelFor(element: HTMLElement): HTMLLabelElement | null { - return document.querySelector(`label[for="${element.id}"]`) as HTMLLabelElement | null; - } - } - - protected createView(areaLUT: HTMLImageElement, - gammaLUT: HTMLImageElement, - commonShaderSource: string, - shaderSources: ShaderMap): - TextDemoView { - return new TextDemoView(this, areaLUT, gammaLUT, commonShaderSource, shaderSources); - } - - protected fileLoaded(fileData: ArrayBuffer, builtinName: string | null) { - const font = new PathfinderFont(fileData, builtinName); - this.recreateLayout(font); - } - - private hintingChanged(): void { - this.view.then(view => view.renderer.updateHinting()); - } - - private emboldenAmountChanged(): void { - this._emboldenAmount = parseFloat(this.emboldenInput.value); - this.view.then(view => view.renderer.updateEmboldenAmount()); - } - - private updateText(): void { - this.text = this.editTextArea.value; - window.jQuery(this.editTextModal).modal('hide'); - - this.recreateLayout(this.font); - } - - private recreateLayout(font: PathfinderFont) { - const newLayout = new SimpleTextLayout(font, this.text); - - let uniqueGlyphIDs = newLayout.textFrame.allGlyphIDs; - uniqueGlyphIDs.sort((a, b) => a - b); - uniqueGlyphIDs = _.sortedUniq(uniqueGlyphIDs); - - const glyphStore = new GlyphStore(font, uniqueGlyphIDs); - glyphStore.partition().then(result => { - const meshes = this.expandMeshes(result.meshes, uniqueGlyphIDs.length); - - this.view.then(view => { - this.font = font; - this.layout = newLayout; - this.glyphStore = glyphStore; - this.meshes = meshes; - - view.attachText(); - view.attachMeshes([this.meshes]); - }); - }); - } - - private expandMeshes(meshes: PathfinderMeshPack, glyphCount: number): PathfinderPackedMeshes { - const pathIDs = []; - for (let glyphIndex = 0; glyphIndex < glyphCount; glyphIndex++) { - for (let subpixel = 0; subpixel < SUBPIXEL_GRANULARITY; subpixel++) - pathIDs.push(glyphIndex + 1); - } - return new PathfinderPackedMeshes(meshes, pathIDs); - } - - get atlas(): 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.renderer.relayoutText()); - } - - get rotationAngle(): number { - return this._rotationAngle; - } - - set rotationAngle(newRotationAngle: number) { - this._rotationAngle = newRotationAngle; - this.view.then(view => view.renderer.relayoutText()); - } - - get unitsPerEm(): number { - return this.font.opentypeFont.unitsPerEm; - } - - get pixelsPerUnit(): number { - return this._fontSize / this.unitsPerEm; - } - - get useHinting(): boolean { - return this.hintingSelect.selectedIndex !== 0; - } - - get pathCount(): number { - return this.glyphStore.glyphIDs.length * SUBPIXEL_GRANULARITY; - } - - protected get builtinFileURI(): string { - return BUILTIN_FONT_URI; - } - - protected get defaultFile(): string { - return DEFAULT_FONT; - } -} - -class TextDemoView extends DemoView implements SimpleTextLayoutRenderContext { - renderer: TextDemoRenderer; - - appController: TextDemoController; - - get cameraView(): CameraView { - return this.canvas; - } - - get atlasGlyphs(): AtlasGlyph[] { - return this.appController.atlasGlyphs; - } - - set atlasGlyphs(newAtlasGlyphs: AtlasGlyph[]) { - this.appController.atlasGlyphs = newAtlasGlyphs; - } - - get atlas(): Atlas { - return this.appController.atlas; - } - - get glyphStore(): GlyphStore { - return this.appController.glyphStore; - } - - get font(): PathfinderFont { - return this.appController.font; - } - - get fontSize(): number { - return this.appController.fontSize; - } - - get pathCount(): number { - return this.appController.pathCount; - } - - get pixelsPerUnit(): number { - return this.appController.pixelsPerUnit; - } - - get useHinting(): boolean { - return this.appController.useHinting; - } - - get layout(): SimpleTextLayout { - return this.appController.layout; - } - - get rotationAngle(): number { - return this.appController.rotationAngle; - } - - get emboldenAmount(): number { - return this.appController.emboldenAmount; - } - - get unitsPerEm(): number { - return this.appController.unitsPerEm; - } - - protected get camera(): OrthographicCamera { - return this.renderer.camera; - } - - constructor(appController: TextDemoController, - areaLUT: HTMLImageElement, - gammaLUT: HTMLImageElement, - commonShaderSource: string, - shaderSources: ShaderMap) { - super(areaLUT, gammaLUT, 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; - } - - newTimingsReceived(newTimings: Timings) { - this.appController.newTimingsReceived(newTimings); - } - - protected onPan(): void { - this.renderer.viewPanned(); - } - - protected onZoom(): void { - this.appController.fontSize = this.renderer.camera.scale * - this.appController.font.opentypeFont.unitsPerEm; - } - - protected onRotate(): void { - this.appController.rotationAngle = this.renderer.camera.rotationAngle; - } - - private set panZoomEventsEnabled(flag: boolean) { - if (flag) { - this.renderer.camera.onPan = () => this.onPan(); - this.renderer.camera.onZoom = () => this.onZoom(); - this.renderer.camera.onRotate = () => this.onRotate(); - } else { - this.renderer.camera.onPan = null; - this.renderer.camera.onZoom = null; - this.renderer.camera.onRotate = null; - } - } -} - -class TextDemoRenderer extends SimpleTextLayoutRenderer { - renderContext!: TextDemoView; -} - -function main(): void { - const controller = new TextDemoController; - window.addEventListener('load', () => controller.start(), false); -} - -main(); diff --git a/demo/client/src/text-renderer.ts b/demo/client/src/text-renderer.ts deleted file mode 100644 index aee4c1aa..00000000 --- a/demo/client/src/text-renderer.ts +++ /dev/null @@ -1,649 +0,0 @@ -// pathfinder/client/src/text-renderer.ts -// -// 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. - -import * as glmatrix from 'gl-matrix'; -import * as _ from 'lodash'; - -import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from './aa-strategy'; -import {StemDarkeningMode, SubpixelAAType} from './aa-strategy'; -import {AAOptions} from './app-controller'; -import {Atlas, ATLAS_SIZE, AtlasGlyph, GlyphKey, SUBPIXEL_GRANULARITY} from './atlas'; -import {CameraView, OrthographicCamera} from './camera'; -import {createFramebuffer, createFramebufferDepthTexture, QUAD_ELEMENTS} from './gl-utils'; -import {UniformMap} from './gl-utils'; -import {PathTransformBuffers, Renderer} from './renderer'; -import {ShaderMap} from './shader-loader'; -import SSAAStrategy from './ssaa-strategy'; -import {calculatePixelRectForGlyph, computeStemDarkeningAmount, GlyphStore, Hint} from "./text"; -import {MAX_STEM_DARKENING_PIXELS_PER_EM, PathfinderFont, SimpleTextLayout} from "./text"; -import {UnitMetrics} from "./text"; -import {unwrapNull} from './utils'; -import {RenderContext, Timings} from "./view"; -import {StencilAAAStrategy} from './xcaa-strategy'; - -interface AntialiasingStrategyTable { - none: typeof NoAAStrategy; - ssaa: typeof SSAAStrategy; - xcaa: typeof StencilAAAStrategy; -} - -const SQRT_1_2: number = 1.0 / Math.sqrt(2.0); - -const MIN_SCALE: number = 0.0025; -const MAX_SCALE: number = 0.5; - -export const MAX_SUBPIXEL_AA_FONT_SIZE: number = 48.0; - -const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = { - none: NoAAStrategy, - ssaa: SSAAStrategy, - xcaa: StencilAAAStrategy, -}; - -export interface TextRenderContext extends RenderContext { - atlasGlyphs: AtlasGlyph[]; - - readonly cameraView: CameraView; - readonly atlas: Atlas; - readonly glyphStore: GlyphStore; - readonly font: PathfinderFont; - readonly fontSize: number; - readonly useHinting: boolean; - - newTimingsReceived(timings: Timings): void; -} - -export abstract class TextRenderer extends Renderer { - renderContext!: TextRenderContext; - - camera: OrthographicCamera; - - atlasFramebuffer!: WebGLFramebuffer; - atlasDepthTexture!: WebGLTexture; - - get isMulticolor(): boolean { - return false; - } - - get needsStencil(): boolean { - return this.renderContext.fontSize <= MAX_STEM_DARKENING_PIXELS_PER_EM; - } - - get destFramebuffer(): WebGLFramebuffer { - return this.atlasFramebuffer; - } - - get destAllocatedSize(): glmatrix.vec2 { - return ATLAS_SIZE; - } - - get destUsedSize(): glmatrix.vec2 { - return this.renderContext.atlas.usedSize; - } - - get emboldenAmount(): glmatrix.vec2 { - const emboldenAmount = glmatrix.vec2.create(); - glmatrix.vec2.add(emboldenAmount, this.extraEmboldenAmount, this.stemDarkeningAmount); - return emboldenAmount; - } - - get bgColor(): glmatrix.vec4 { - return glmatrix.vec4.clone([0.0, 0.0, 0.0, 1.0]); - } - - get fgColor(): glmatrix.vec4 { - return glmatrix.vec4.clone([1.0, 1.0, 1.0, 1.0]); - } - - get rotationAngle(): number { - return 0.0; - } - - get allowSubpixelAA(): boolean { - return this.renderContext.fontSize <= MAX_SUBPIXEL_AA_FONT_SIZE; - } - - protected get pixelsPerUnit(): number { - return this.renderContext.fontSize / this.renderContext.font.opentypeFont.unitsPerEm; - } - - 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 stemDarkeningAmount(): glmatrix.vec2 { - if (this.stemDarkening === 'dark') - return computeStemDarkeningAmount(this.renderContext.fontSize, this.pixelsPerUnit); - return glmatrix.vec2.create(); - } - - protected get usedSizeFactor(): glmatrix.vec2 { - const usedSize = glmatrix.vec2.create(); - glmatrix.vec2.div(usedSize, this.renderContext.atlas.usedSize, ATLAS_SIZE); - return usedSize; - } - - private get pathCount(): number { - return this.renderContext.glyphStore.glyphIDs.length * SUBPIXEL_GRANULARITY; - } - - protected get objectCount(): number { - return this.meshBuffers == null ? 0 : this.meshBuffers.length; - } - - protected get extraEmboldenAmount(): glmatrix.vec2 { - return glmatrix.vec2.create(); - } - - private stemDarkening!: StemDarkeningMode; - private subpixelAA!: SubpixelAAType; - - constructor(renderContext: TextRenderContext) { - super(renderContext); - - this.camera = new OrthographicCamera(this.renderContext.cameraView, { - maxScale: MAX_SCALE, - minScale: MIN_SCALE, - }); - } - - setHintsUniform(uniforms: UniformMap): void { - const renderContext = this.renderContext; - const gl = renderContext.gl; - - const hint = this.createHint(); - gl.uniform4f(uniforms.uHints, - hint.xHeight, - hint.hintedXHeight, - hint.stemHeight, - hint.hintedStemHeight); - } - - pathBoundingRects(objectIndex: number): Float32Array { - const pathCount = this.pathCount; - const atlasGlyphs = this.renderContext.atlasGlyphs; - const pixelsPerUnit = this.pixelsPerUnit; - const rotationAngle = this.rotationAngle; - const font = this.renderContext.font; - const hint = this.createHint(); - - const boundingRects = new Float32Array((pathCount + 1) * 4); - - for (const glyph of atlasGlyphs) { - const atlasGlyphMetrics = font.metricsForGlyph(glyph.glyphKey.id); - if (atlasGlyphMetrics == null) - continue; - const atlasUnitMetrics = new UnitMetrics(atlasGlyphMetrics, 0.0, this.emboldenAmount); - - const pathID = glyph.pathID; - boundingRects[pathID * 4 + 0] = atlasUnitMetrics.left; - boundingRects[pathID * 4 + 1] = atlasUnitMetrics.descent; - boundingRects[pathID * 4 + 2] = atlasUnitMetrics.right; - boundingRects[pathID * 4 + 3] = atlasUnitMetrics.ascent; - } - - return boundingRects; - } - - pathTransformsForObject(objectIndex: number): PathTransformBuffers { - const pathCount = this.pathCount; - const atlasGlyphs = this.renderContext.atlasGlyphs; - const pixelsPerUnit = this.pixelsPerUnit; - const rotationAngle = this.rotationAngle; - - // FIXME(pcwalton): This is a hack that tries to preserve the vertical extents of the glyph - // after stem darkening. It's better than nothing, but we should really do better. - // - // This hack seems to produce *better* results than what macOS does on sans-serif fonts; - // the ascenders and x-heights of the glyphs are pixel snapped, while they aren't on macOS. - // But we should really figure out what macOS does… - const ascender = this.renderContext.font.opentypeFont.ascender; - const emboldenAmount = this.emboldenAmount; - const stemDarkeningYScale = (ascender + emboldenAmount[1]) / ascender; - - const stemDarkeningOffset = glmatrix.vec2.clone(emboldenAmount); - glmatrix.vec2.scale(stemDarkeningOffset, stemDarkeningOffset, pixelsPerUnit); - glmatrix.vec2.scale(stemDarkeningOffset, stemDarkeningOffset, SQRT_1_2); - glmatrix.vec2.mul(stemDarkeningOffset, stemDarkeningOffset, [1, stemDarkeningYScale]); - - const transform = glmatrix.mat2d.create(); - const transforms = this.createPathTransformBuffers(pathCount); - - for (const glyph of atlasGlyphs) { - const pathID = glyph.pathID; - const atlasOrigin = glyph.calculateSubpixelOrigin(pixelsPerUnit); - - glmatrix.mat2d.identity(transform); - glmatrix.mat2d.translate(transform, transform, atlasOrigin); - glmatrix.mat2d.translate(transform, transform, stemDarkeningOffset); - glmatrix.mat2d.rotate(transform, transform, rotationAngle); - glmatrix.mat2d.scale(transform, - transform, - [pixelsPerUnit, pixelsPerUnit * stemDarkeningYScale]); - - transforms.st[pathID * 4 + 0] = transform[0]; - transforms.st[pathID * 4 + 1] = transform[3]; - transforms.st[pathID * 4 + 2] = transform[4]; - transforms.st[pathID * 4 + 3] = transform[5]; - - transforms.ext[pathID * 2 + 0] = transform[1]; - transforms.ext[pathID * 2 + 1] = transform[2]; - } - - return transforms; - } - - protected createAtlasFramebuffer(): void { - const atlasColorTexture = this.renderContext.atlas.ensureTexture(this.renderContext); - this.atlasDepthTexture = createFramebufferDepthTexture(this.renderContext.gl, ATLAS_SIZE); - this.atlasFramebuffer = createFramebuffer(this.renderContext.gl, - atlasColorTexture, - this.atlasDepthTexture); - - // Allow the antialiasing strategy to set up framebuffers as necessary. - if (this.antialiasingStrategy != null) - this.antialiasingStrategy.setFramebufferSize(this); - } - - 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 clearForDirectRendering(): void {} - - protected buildAtlasGlyphs(atlasGlyphs: AtlasGlyph[]): void { - const renderContext = this.renderContext; - const font = renderContext.font; - const pixelsPerUnit = this.pixelsPerUnit; - const rotationAngle = this.rotationAngle; - const hint = this.createHint(); - - atlasGlyphs.sort((a, b) => a.glyphKey.sortKey - b.glyphKey.sortKey); - atlasGlyphs = _.sortedUniqBy(atlasGlyphs, glyph => glyph.glyphKey.sortKey); - if (atlasGlyphs.length === 0) - return; - - renderContext.atlasGlyphs = atlasGlyphs; - renderContext.atlas.layoutGlyphs(atlasGlyphs, - font, - pixelsPerUnit, - rotationAngle, - hint, - this.emboldenAmount); - - this.uploadPathTransforms(1); - this.uploadPathColors(1); - } - - protected pathColorsForObject(objectIndex: number): Uint8Array { - const pathCount = this.pathCount; - - const pathColors = new Uint8Array(4 * (pathCount + 1)); - - for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) { - for (let channel = 0; channel < 3; channel++) - pathColors[(pathIndex + 1) * 4 + channel] = 0xff; // RGB - pathColors[(pathIndex + 1) * 4 + 3] = 0xff; // alpha - } - - return pathColors; - } - - protected newTimingsReceived(): void { - this.renderContext.newTimingsReceived(this.lastTimings); - } - - protected createHint(): Hint { - return new Hint(this.renderContext.font, - this.pixelsPerUnit, - this.renderContext.useHinting); - } - - protected directCurveProgramName(): keyof ShaderMap { - return 'directCurve'; - } - - protected directInteriorProgramName(): keyof ShaderMap { - return 'directInterior'; - } -} - -export interface SimpleTextLayoutRenderContext extends TextRenderContext { - readonly layout: SimpleTextLayout; - readonly rotationAngle: number; - readonly emboldenAmount: number; - readonly unitsPerEm: number; -} - -export abstract class SimpleTextLayoutRenderer extends TextRenderer { - abstract get renderContext(): SimpleTextLayoutRenderContext; - - glyphPositionsBuffer!: WebGLBuffer; - glyphTexCoordsBuffer!: WebGLBuffer; - glyphElementsBuffer!: WebGLBuffer; - - private glyphBounds!: Float32Array; - - get layout(): SimpleTextLayout { - return this.renderContext.layout; - } - - get backgroundColor(): glmatrix.vec4 { - return glmatrix.vec4.create(); - } - - get rotationAngle(): number { - return this.renderContext.rotationAngle; - } - - protected get extraEmboldenAmount(): glmatrix.vec2 { - const emboldenLength = this.renderContext.emboldenAmount * this.renderContext.unitsPerEm; - return glmatrix.vec2.clone([emboldenLength, emboldenLength]); - } - - prepareToAttachText(): void { - if (this.atlasFramebuffer == null) - this.createAtlasFramebuffer(); - - this.layoutText(); - } - - finishAttachingText(): void { - this.buildGlyphs(); - this.renderContext.setDirty(); - } - - setAntialiasingOptions(aaType: AntialiasingStrategyName, - aaLevel: number, - aaOptions: AAOptions): - void { - super.setAntialiasingOptions(aaType, aaLevel, aaOptions); - - // Need to relayout because changing AA options can cause font dilation to change... - this.layoutText(); - this.buildGlyphs(); - this.renderContext.setDirty(); - } - - relayoutText(): void { - this.layoutText(); - this.buildGlyphs(); - this.renderContext.setDirty(); - } - - updateHinting(): void { - // Need to relayout the text because the pixel bounds of the glyphs can change from this... - this.layoutText(); - this.buildGlyphs(); - this.renderContext.setDirty(); - } - - updateEmboldenAmount(): void { - // Likewise, need to relayout the text. - this.layoutText(); - this.buildGlyphs(); - this.renderContext.setDirty(); - } - - viewPanned(): void { - this.buildGlyphs(); - this.renderContext.setDirty(); - } - - protected compositeIfNecessary(): void { - const renderContext = this.renderContext; - const gl = renderContext.gl; - - // Set up composite state. - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - gl.viewport(0, 0, renderContext.cameraView.width, renderContext.cameraView.height); - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.SCISSOR_TEST); - gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT); - gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE); - gl.enable(gl.BLEND); - - // Clear. - gl.clearColor(1.0, 1.0, 1.0, 1.0); - gl.clear(gl.COLOR_BUFFER_BIT); - - // Set the appropriate program. - const programName = this.gammaCorrectionMode === 'off' ? 'blitLinear' : 'blitGamma'; - const blitProgram = this.renderContext.shaderPrograms[programName]; - - // Set up the composite VAO. - const attributes = blitProgram.attributes; - gl.useProgram(blitProgram.program); - gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer); - gl.vertexAttribPointer(attributes.aPosition, 2, gl.FLOAT, false, 0, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer); - gl.vertexAttribPointer(attributes.aTexCoord, 2, gl.FLOAT, false, 0, 0); - gl.enableVertexAttribArray(attributes.aPosition); - gl.enableVertexAttribArray(attributes.aTexCoord); - gl.bindBuffer(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.cameraView.width, - 2.0 / this.renderContext.cameraView.height, - 1.0, - ]); - glmatrix.mat4.translate(transform, - transform, - [this.camera.translation[0], this.camera.translation[1], 0.0]); - - // Blit. - gl.uniformMatrix4fv(blitProgram.uniforms.uTransform, false, transform); - gl.activeTexture(gl.TEXTURE0); - const destTexture = this.renderContext - .atlas - .ensureTexture(this.renderContext); - gl.bindTexture(gl.TEXTURE_2D, destTexture); - gl.uniform1i(blitProgram.uniforms.uSource, 0); - this.setIdentityTexScaleUniform(blitProgram.uniforms); - this.bindGammaLUT(glmatrix.vec3.clone([1.0, 1.0, 1.0]), 1, blitProgram.uniforms); - const totalGlyphCount = this.layout.textFrame.totalGlyphCount; - gl.drawElements(gl.TRIANGLES, totalGlyphCount * 6, gl.UNSIGNED_INT, 0); - } - - private layoutText(): void { - const renderContext = this.renderContext; - const gl = renderContext.gl; - - this.layout.layoutRuns(); - - const textBounds = this.layout.textFrame.bounds; - this.camera.bounds = textBounds; - - const totalGlyphCount = this.layout.textFrame.totalGlyphCount; - const glyphPositions = new Float32Array(totalGlyphCount * 8); - const glyphIndices = new Uint32Array(totalGlyphCount * 6); - - const hint = this.createHint(); - const pixelsPerUnit = this.pixelsPerUnit; - const rotationAngle = this.rotationAngle; - - let globalGlyphIndex = 0; - for (const run of this.layout.textFrame.runs) { - run.recalculatePixelRects(pixelsPerUnit, - rotationAngle, - hint, - this.emboldenAmount, - SUBPIXEL_GRANULARITY, - textBounds); - - for (let glyphIndex = 0; - glyphIndex < run.glyphIDs.length; - glyphIndex++, globalGlyphIndex++) { - const rect = run.pixelRectForGlyphAt(glyphIndex); - glyphPositions.set([ - rect[0], rect[3], - rect[2], rect[3], - rect[0], rect[1], - rect[2], rect[1], - ], globalGlyphIndex * 8); - - for (let glyphIndexIndex = 0; - glyphIndexIndex < QUAD_ELEMENTS.length; - glyphIndexIndex++) { - glyphIndices[glyphIndexIndex + globalGlyphIndex * 6] = - QUAD_ELEMENTS[glyphIndexIndex] + 4 * globalGlyphIndex; - } - } - } - - this.glyphPositionsBuffer = unwrapNull(gl.createBuffer()); - gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphPositionsBuffer); - gl.bufferData(gl.ARRAY_BUFFER, glyphPositions, gl.STATIC_DRAW); - this.glyphElementsBuffer = unwrapNull(gl.createBuffer()); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glyphElementsBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, glyphIndices, gl.STATIC_DRAW); - } - - private buildGlyphs(): void { - const font = this.renderContext.font; - const glyphStore = this.renderContext.glyphStore; - const pixelsPerUnit = this.pixelsPerUnit; - const rotationAngle = this.rotationAngle; - - const textFrame = this.layout.textFrame; - const textBounds = textFrame.bounds; - const hint = this.createHint(); - - // Only build glyphs in view. - const translation = this.camera.translation; - const canvasRect = glmatrix.vec4.clone([ - -translation[0], - -translation[1], - -translation[0] + this.renderContext.cameraView.width, - -translation[1] + this.renderContext.cameraView.height, - ]); - - const atlasGlyphs = []; - for (const run of textFrame.runs) { - for (let glyphIndex = 0; glyphIndex < run.glyphIDs.length; glyphIndex++) { - const pixelRect = run.pixelRectForGlyphAt(glyphIndex); - if (!rectsIntersect(pixelRect, canvasRect)) - continue; - - const glyphID = run.glyphIDs[glyphIndex]; - const glyphStoreIndex = glyphStore.indexOfGlyphWithID(glyphID); - if (glyphStoreIndex == null) - continue; - - const subpixel = run.subpixelForGlyphAt(glyphIndex, - pixelsPerUnit, - rotationAngle, - hint, - SUBPIXEL_GRANULARITY, - textBounds); - const glyphKey = new GlyphKey(glyphID, subpixel); - atlasGlyphs.push(new AtlasGlyph(glyphStoreIndex, glyphKey)); - } - } - - this.buildAtlasGlyphs(atlasGlyphs); - - // TODO(pcwalton): Regenerate the IBOs to include only the glyphs we care about. - this.setGlyphTexCoords(); - } - - private setGlyphTexCoords(): void { - const gl = this.renderContext.gl; - - const textFrame = this.layout.textFrame; - const textBounds = textFrame.bounds; - - const font = this.renderContext.font; - const atlasGlyphs = this.renderContext.atlasGlyphs; - - const hint = this.createHint(); - const pixelsPerUnit = this.pixelsPerUnit; - const rotationAngle = this.rotationAngle; - - const atlasGlyphKeys = atlasGlyphs.map(atlasGlyph => atlasGlyph.glyphKey.sortKey); - - this.glyphBounds = new Float32Array(textFrame.totalGlyphCount * 8); - - let globalGlyphIndex = 0; - for (const run of textFrame.runs) { - for (let glyphIndex = 0; - glyphIndex < run.glyphIDs.length; - glyphIndex++, globalGlyphIndex++) { - const textGlyphID = run.glyphIDs[glyphIndex]; - - const subpixel = run.subpixelForGlyphAt(glyphIndex, - pixelsPerUnit, - rotationAngle, - hint, - SUBPIXEL_GRANULARITY, - textBounds); - - const glyphKey = new GlyphKey(textGlyphID, subpixel); - - const atlasGlyphIndex = _.sortedIndexOf(atlasGlyphKeys, glyphKey.sortKey); - if (atlasGlyphIndex < 0) - continue; - - // Set texture coordinates. - const atlasGlyph = atlasGlyphs[atlasGlyphIndex]; - const atlasGlyphMetrics = font.metricsForGlyph(atlasGlyph.glyphKey.id); - if (atlasGlyphMetrics == null) - continue; - - const atlasGlyphUnitMetrics = new UnitMetrics(atlasGlyphMetrics, - rotationAngle, - this.emboldenAmount); - - const atlasGlyphPixelOrigin = - atlasGlyph.calculateSubpixelOrigin(pixelsPerUnit); - const atlasGlyphRect = calculatePixelRectForGlyph(atlasGlyphUnitMetrics, - atlasGlyphPixelOrigin, - pixelsPerUnit, - hint); - const atlasGlyphBL = atlasGlyphRect.slice(0, 2) as glmatrix.vec2; - const atlasGlyphTR = atlasGlyphRect.slice(2, 4) as glmatrix.vec2; - glmatrix.vec2.div(atlasGlyphBL, atlasGlyphBL, ATLAS_SIZE); - glmatrix.vec2.div(atlasGlyphTR, atlasGlyphTR, ATLAS_SIZE); - - this.glyphBounds.set([ - atlasGlyphBL[0], atlasGlyphTR[1], - atlasGlyphTR[0], atlasGlyphTR[1], - atlasGlyphBL[0], atlasGlyphBL[1], - atlasGlyphTR[0], atlasGlyphBL[1], - ], globalGlyphIndex * 8); - } - } - - this.glyphTexCoordsBuffer = unwrapNull(gl.createBuffer()); - gl.bindBuffer(gl.ARRAY_BUFFER, this.glyphTexCoordsBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glyphBounds, gl.STATIC_DRAW); - } - - private setIdentityTexScaleUniform(uniforms: UniformMap): void { - this.renderContext.gl.uniform2f(uniforms.uTexScale, 1.0, 1.0); - } -} - -/// The separating axis theorem. -function rectsIntersect(a: glmatrix.vec4, b: glmatrix.vec4): boolean { - return a[2] > b[0] && a[3] > b[1] && a[0] < b[2] && a[1] < b[3]; -} diff --git a/demo/client/src/text.ts b/demo/client/src/text.ts deleted file mode 100644 index 268a9556..00000000 --- a/demo/client/src/text.ts +++ /dev/null @@ -1,453 +0,0 @@ -// pathfinder/client/src/text.ts -// -// Copyright © 2017 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. - -import * as base64js from 'base64-js'; -import * as glmatrix from 'gl-matrix'; -import * as _ from 'lodash'; -import * as opentype from "opentype.js"; -import {Metrics} from 'opentype.js'; - -import {B_QUAD_SIZE, parseServerTiming, PathfinderMeshPack} from "./meshes"; -import {PathfinderPackedMeshes} from "./meshes"; -import {assert, lerp, panic, UINT32_MAX, UINT32_SIZE, unwrapNull} from "./utils"; - -export const BUILTIN_FONT_URI: string = "/otf/demo"; - -const SQRT_2: number = Math.sqrt(2.0); - -// Should match macOS 10.13 High Sierra. -// -// We multiply by sqrt(2) to compensate for the fact that dilation amounts are relative to the -// pixel square on macOS and relative to the vertex normal in Pathfinder. -const STEM_DARKENING_FACTORS: glmatrix.vec2 = glmatrix.vec2.clone([ - 0.0121 * SQRT_2, - 0.0121 * 1.25 * SQRT_2, -]); - -// Likewise. -const MAX_STEM_DARKENING_AMOUNT: glmatrix.vec2 = glmatrix.vec2.clone([0.3 * SQRT_2, 0.3 * SQRT_2]); - -// This value is a subjective cutoff. Above this ppem value, no stem darkening is performed. -export const MAX_STEM_DARKENING_PIXELS_PER_EM: number = 72.0; - -const PARTITION_FONT_ENDPOINT_URI: string = "/partition-font"; - -export interface ExpandedMeshData { - meshes: PathfinderPackedMeshes; -} - -export interface PartitionResult { - meshes: PathfinderMeshPack; - time: number; -} - -export interface PixelMetrics { - left: number; - right: number; - ascent: number; - descent: number; -} - -opentype.Font.prototype.isSupported = function() { - return (this as any).supported; -}; - -opentype.Font.prototype.lineHeight = function() { - const os2Table = this.tables.os2; - return os2Table.sTypoAscender - os2Table.sTypoDescender + os2Table.sTypoLineGap; -}; - -export class PathfinderFont { - readonly opentypeFont: opentype.Font; - readonly data: ArrayBuffer; - readonly builtinFontName: string | null; - - private metricsCache: Metrics[]; - - constructor(data: ArrayBuffer, builtinFontName: string | null) { - this.data = data; - this.builtinFontName = builtinFontName != null ? builtinFontName : null; - - this.opentypeFont = opentype.parse(data); - if (!this.opentypeFont.isSupported()) - panic("Unsupported font!"); - - this.metricsCache = []; - } - - metricsForGlyph(glyphID: number): Metrics | null { - if (this.metricsCache[glyphID] == null) - this.metricsCache[glyphID] = this.opentypeFont.glyphs.get(glyphID).getMetrics(); - return this.metricsCache[glyphID]; - } -} - -export class TextRun { - readonly glyphIDs: number[]; - advances: number[]; - readonly origin: number[]; - - private readonly font: PathfinderFont; - private pixelRects: glmatrix.vec4[]; - - constructor(text: number[] | string, origin: number[], font: PathfinderFont) { - if (typeof(text) === 'string') { - this.glyphIDs = font.opentypeFont - .stringToGlyphs(text) - .map(glyph => (glyph as any).index); - } else { - this.glyphIDs = text; - } - - this.origin = origin; - this.advances = []; - this.font = font; - this.pixelRects = []; - } - - layout() { - this.advances = []; - let currentX = 0; - for (const glyphID of this.glyphIDs) { - this.advances.push(currentX); - currentX += this.font.opentypeFont.glyphs.get(glyphID).advanceWidth; - } - } - - calculatePixelOriginForGlyphAt(index: number, - pixelsPerUnit: number, - rotationAngle: number, - hint: Hint, - textFrameBounds: glmatrix.vec4): - glmatrix.vec2 { - const textFrameCenter = glmatrix.vec2.clone([ - 0.5 * (textFrameBounds[0] + textFrameBounds[2]), - 0.5 * (textFrameBounds[1] + textFrameBounds[3]), - ]); - - const transform = glmatrix.mat2d.create(); - glmatrix.mat2d.fromTranslation(transform, textFrameCenter); - glmatrix.mat2d.rotate(transform, transform, -rotationAngle); - glmatrix.vec2.negate(textFrameCenter, textFrameCenter); - glmatrix.mat2d.translate(transform, transform, textFrameCenter); - - const textGlyphOrigin = glmatrix.vec2.create(); - glmatrix.vec2.add(textGlyphOrigin, [this.advances[index], 0.0], this.origin); - glmatrix.vec2.transformMat2d(textGlyphOrigin, textGlyphOrigin, transform); - - glmatrix.vec2.scale(textGlyphOrigin, textGlyphOrigin, pixelsPerUnit); - return textGlyphOrigin; - } - - pixelRectForGlyphAt(index: number): glmatrix.vec4 { - return this.pixelRects[index]; - } - - subpixelForGlyphAt(index: number, - pixelsPerUnit: number, - rotationAngle: number, - hint: Hint, - subpixelGranularity: number, - textFrameBounds: glmatrix.vec4): - number { - const textGlyphOrigin = this.calculatePixelOriginForGlyphAt(index, - pixelsPerUnit, - rotationAngle, - hint, - textFrameBounds)[0]; - return Math.abs(Math.round(textGlyphOrigin * subpixelGranularity) % subpixelGranularity); - } - - recalculatePixelRects(pixelsPerUnit: number, - rotationAngle: number, - hint: Hint, - emboldenAmount: glmatrix.vec2, - subpixelGranularity: number, - textFrameBounds: glmatrix.vec4): - void { - for (let index = 0; index < this.glyphIDs.length; index++) { - const metrics = unwrapNull(this.font.metricsForGlyph(this.glyphIDs[index])); - const unitMetrics = new UnitMetrics(metrics, rotationAngle, emboldenAmount); - const textGlyphOrigin = this.calculatePixelOriginForGlyphAt(index, - pixelsPerUnit, - rotationAngle, - hint, - textFrameBounds); - - textGlyphOrigin[0] *= subpixelGranularity; - glmatrix.vec2.round(textGlyphOrigin, textGlyphOrigin); - textGlyphOrigin[0] /= subpixelGranularity; - - const pixelRect = calculatePixelRectForGlyph(unitMetrics, - textGlyphOrigin, - pixelsPerUnit, - hint); - - this.pixelRects[index] = pixelRect; - } - } - - get measure(): number { - const lastGlyphID = _.last(this.glyphIDs), lastAdvance = _.last(this.advances); - if (lastGlyphID == null || lastAdvance == null) - return 0.0; - return lastAdvance + this.font.opentypeFont.glyphs.get(lastGlyphID).advanceWidth; - } -} - -export class TextFrame { - readonly runs: TextRun[]; - readonly origin: glmatrix.vec3; - - private readonly font: PathfinderFont; - - constructor(runs: TextRun[], font: PathfinderFont) { - this.runs = runs; - this.origin = glmatrix.vec3.create(); - this.font = font; - } - - expandMeshes(meshes: PathfinderMeshPack, glyphIDs: number[]): ExpandedMeshData { - const pathIDs = []; - for (const textRun of this.runs) { - for (const glyphID of textRun.glyphIDs) { - if (glyphID === 0) - continue; - const pathID = _.sortedIndexOf(glyphIDs, glyphID); - pathIDs.push(pathID + 1); - } - } - - return { - meshes: new PathfinderPackedMeshes(meshes, pathIDs), - }; - } - - get bounds(): glmatrix.vec4 { - if (this.runs.length === 0) - return glmatrix.vec4.create(); - - const upperLeft = glmatrix.vec2.clone(this.runs[0].origin); - const lowerRight = glmatrix.vec2.clone(_.last(this.runs)!.origin); - - const lowerLeft = glmatrix.vec2.clone([upperLeft[0], lowerRight[1]]); - const upperRight = glmatrix.vec2.clone([lowerRight[0], upperLeft[1]]); - - const lineHeight = this.font.opentypeFont.lineHeight(); - lowerLeft[1] -= lineHeight; - upperRight[1] += lineHeight * 2.0; - - upperRight[0] = _.defaultTo(_.max(this.runs.map(run => run.measure)), 0.0); - - return glmatrix.vec4.clone([lowerLeft[0], lowerLeft[1], upperRight[0], upperRight[1]]); - } - - get totalGlyphCount(): number { - return _.sumBy(this.runs, run => run.glyphIDs.length); - } - - get allGlyphIDs(): number[] { - const glyphIDs = []; - for (const run of this.runs) - glyphIDs.push(...run.glyphIDs); - return glyphIDs; - } -} - -/// Stores one copy of each glyph. -export class GlyphStore { - readonly font: PathfinderFont; - readonly glyphIDs: number[]; - - constructor(font: PathfinderFont, glyphIDs: number[]) { - this.font = font; - this.glyphIDs = glyphIDs; - } - - partition(): Promise { - // Build the partitioning request to the server. - let fontFace; - if (this.font.builtinFontName != null) - fontFace = { Builtin: this.font.builtinFontName }; - else - fontFace = { Custom: base64js.fromByteArray(new Uint8Array(this.font.data)) }; - - const request = { - face: fontFace, - fontIndex: 0, - glyphs: this.glyphIDs.map(id => ({ id: id, transform: [1, 0, 0, 1, 0, 0] })), - pointSize: this.font.opentypeFont.unitsPerEm, - }; - - // Make the request. - let time = 0; - return window.fetch(PARTITION_FONT_ENDPOINT_URI, { - body: JSON.stringify(request), - headers: {'Content-Type': 'application/json'} as any, - method: 'POST', - }).then(response => { - time = parseServerTiming(response.headers); - return response.arrayBuffer(); - }).then(buffer => { - return { - meshes: new PathfinderMeshPack(buffer), - time: time, - }; - }); - } - - indexOfGlyphWithID(glyphID: number): number | null { - const index = _.sortedIndexOf(this.glyphIDs, glyphID); - return index >= 0 ? index : null; - } -} - -export class SimpleTextLayout { - readonly textFrame: TextFrame; - - constructor(font: PathfinderFont, text: string) { - const lineHeight = font.opentypeFont.lineHeight(); - const textRuns: TextRun[] = text.split("\n").map((line, lineNumber) => { - return new TextRun(line, [0.0, -lineHeight * lineNumber], font); - }); - this.textFrame = new TextFrame(textRuns, font); - } - - layoutRuns() { - this.textFrame.runs.forEach(textRun => textRun.layout()); - } -} - -export class Hint { - readonly xHeight: number; - readonly hintedXHeight: number; - readonly stemHeight: number; - readonly hintedStemHeight: number; - - private useHinting: boolean; - - constructor(font: PathfinderFont, pixelsPerUnit: number, useHinting: boolean) { - useHinting = false; - this.useHinting = useHinting; - - const os2Table = font.opentypeFont.tables.os2; - this.xHeight = os2Table.sxHeight != null ? os2Table.sxHeight : 0; - this.stemHeight = os2Table.sCapHeight != null ? os2Table.sCapHeight : 0; - - if (!useHinting) { - this.hintedXHeight = this.xHeight; - this.hintedStemHeight = this.stemHeight; - } else { - this.hintedXHeight = Math.round(Math.round(this.xHeight * pixelsPerUnit) / - pixelsPerUnit); - this.hintedStemHeight = Math.round(Math.round(this.stemHeight * pixelsPerUnit) / - pixelsPerUnit); - } - } - - /// NB: This must match `hintPosition()` in `common.inc.glsl`. - hintPosition(position: glmatrix.vec2): glmatrix.vec2 { - if (!this.useHinting) - return position; - - if (position[1] >= this.stemHeight) { - const y = position[1] - this.stemHeight + this.hintedStemHeight; - return glmatrix.vec2.clone([position[0], y]); - } - - if (position[1] >= this.xHeight) { - const y = lerp(this.hintedXHeight, this.hintedStemHeight, - (position[1] - this.xHeight) / (this.stemHeight - this.xHeight)); - return glmatrix.vec2.clone([position[0], y]); - } - - if (position[1] >= 0.0) { - const y = lerp(0.0, this.hintedXHeight, position[1] / this.xHeight); - return glmatrix.vec2.clone([position[0], y]); - } - - return position; - } -} - -export class UnitMetrics { - left: number; - right: number; - ascent: number; - descent: number; - - constructor(metrics: Metrics, rotationAngle: number, emboldenAmount: glmatrix.vec2) { - const left = metrics.xMin; - const bottom = metrics.yMin; - const right = metrics.xMax + emboldenAmount[0] * 2; - const top = metrics.yMax + emboldenAmount[1] * 2; - - const transform = glmatrix.mat2.create(); - glmatrix.mat2.fromRotation(transform, -rotationAngle); - - const lowerLeft = glmatrix.vec2.clone([Infinity, Infinity]); - const upperRight = glmatrix.vec2.clone([-Infinity, -Infinity]); - const points = [[left, bottom], [left, top], [right, top], [right, bottom]]; - const transformedPoint = glmatrix.vec2.create(); - for (const point of points) { - glmatrix.vec2.transformMat2(transformedPoint, point, transform); - glmatrix.vec2.min(lowerLeft, lowerLeft, transformedPoint); - glmatrix.vec2.max(upperRight, upperRight, transformedPoint); - } - - this.left = lowerLeft[0]; - this.right = upperRight[0]; - this.ascent = upperRight[1]; - this.descent = lowerLeft[1]; - } -} - -export function calculatePixelXMin(metrics: UnitMetrics, pixelsPerUnit: number): number { - return Math.floor(metrics.left * pixelsPerUnit); -} - -export function calculatePixelDescent(metrics: UnitMetrics, pixelsPerUnit: number): number { - return Math.floor(metrics.descent * pixelsPerUnit); -} - -function calculateSubpixelMetricsForGlyph(metrics: UnitMetrics, pixelsPerUnit: number, hint: Hint): - PixelMetrics { - const ascent = hint.hintPosition(glmatrix.vec2.fromValues(0, metrics.ascent))[1]; - return { - ascent: ascent * pixelsPerUnit, - descent: metrics.descent * pixelsPerUnit, - left: metrics.left * pixelsPerUnit, - right: metrics.right * pixelsPerUnit, - }; -} - -export function calculatePixelRectForGlyph(metrics: UnitMetrics, - subpixelOrigin: glmatrix.vec2, - pixelsPerUnit: number, - hint: Hint): - glmatrix.vec4 { - const pixelMetrics = calculateSubpixelMetricsForGlyph(metrics, pixelsPerUnit, hint); - return glmatrix.vec4.clone([Math.floor(subpixelOrigin[0] + pixelMetrics.left), - Math.floor(subpixelOrigin[1] + pixelMetrics.descent), - Math.ceil(subpixelOrigin[0] + pixelMetrics.right), - Math.ceil(subpixelOrigin[1] + pixelMetrics.ascent)]); -} - -export function computeStemDarkeningAmount(pixelsPerEm: number, pixelsPerUnit: number): - glmatrix.vec2 { - const amount = glmatrix.vec2.create(); - if (pixelsPerEm > MAX_STEM_DARKENING_PIXELS_PER_EM) - return amount; - - glmatrix.vec2.scale(amount, STEM_DARKENING_FACTORS, pixelsPerEm); - glmatrix.vec2.min(amount, amount, MAX_STEM_DARKENING_AMOUNT); - glmatrix.vec2.scale(amount, amount, 1.0 / pixelsPerUnit); - return amount; -} diff --git a/demo/client/src/utils.ts b/demo/client/src/utils.ts deleted file mode 100644 index eda91b7b..00000000 --- a/demo/client/src/utils.ts +++ /dev/null @@ -1,85 +0,0 @@ -// pathfinder/client/src/utils.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; - -export const FLOAT32_SIZE: number = 4; -export const UINT16_SIZE: number = 2; -export const UINT8_SIZE: number = 1; - -export const UINT32_MAX: number = 0xffffffff; -export const UINT32_SIZE: number = 4; - -export const EPSILON: number = 0.001; - -export function panic(message: string): never { - throw new PathfinderError(message); -} - -export function assert(value: boolean, message: string) { - if (!value) - panic(message); -} - -export function expectNotNull(value: T | null, message: string): T { - if (value === null) - throw new PathfinderError(message); - return value; -} - -function expectNotUndef(value: T | undefined, message: string): T { - if (value === undefined) - throw new PathfinderError(message); - return value; -} - -export function unwrapNull(value: T | null): T { - return expectNotNull(value, "Unexpected null!"); -} - -export function unwrapUndef(value: T | undefined): T { - return expectNotUndef(value, "Unexpected `undefined`!"); -} - -export function scaleRect(rect: glmatrix.vec4, scale: number): glmatrix.vec4 { - const upperLeft = glmatrix.vec2.clone([rect[0], rect[1]]); - const lowerRight = glmatrix.vec2.clone([rect[2], rect[3]]); - glmatrix.vec2.scale(upperLeft, upperLeft, scale); - glmatrix.vec2.scale(lowerRight, lowerRight, scale); - return glmatrix.vec4.clone([upperLeft[0], upperLeft[1], lowerRight[0], lowerRight[1]]); -} - -export function lerp(a: number, b: number, t: number): number { - return a + (b - a) * t; -} - -export class PathfinderError extends Error { - constructor(message?: string | undefined) { - super(message); - } -} - -export class Range { - start: number; - end: number; - - get isEmpty(): boolean { - return this.start >= this.end; - } - - get length(): number { - return this.end - this.start; - } - - constructor(start: number, end: number) { - this.start = start; - this.end = end; - } -} diff --git a/demo/client/src/view.ts b/demo/client/src/view.ts deleted file mode 100644 index 60969ae8..00000000 --- a/demo/client/src/view.ts +++ /dev/null @@ -1,427 +0,0 @@ -// pathfinder/client/src/view.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; - -import {AntialiasingStrategy, AntialiasingStrategyName, NoAAStrategy} from "./aa-strategy"; -import {StemDarkeningMode, SubpixelAAType} from "./aa-strategy"; -import {AAOptions} from './app-controller'; -import PathfinderBufferTexture from './buffer-texture'; -import {Camera} from "./camera"; -import {EXTDisjointTimerQuery, QUAD_ELEMENTS, UniformMap} from './gl-utils'; -import {PathfinderPackedMeshBuffers, PathfinderPackedMeshes} 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 QUAD_POSITIONS: Float32Array = new Float32Array([ - 0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0, -]); - -const QUAD_TEX_COORDS: Float32Array = new Float32Array([ - 0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0, -]); - -export const TIMINGS: {[name: string]: string} = { - compositing: "Compositing", - rendering: "Rendering", -}; - -export interface Timings { - compositing: number; - rendering: number; -} - -export type ColorAlphaFormat = 'RGBA8' | 'RGB5_A1'; - -declare class WebGLQuery {} - -export abstract class PathfinderView { - canvas: HTMLCanvasElement; - - suppressAutomaticRedraw: boolean; - - vrDisplayWidth: number | null; - vrDisplayHeight: number | null; - - protected abstract get camera(): Camera; - - private dirty: boolean; - - private pulseHandle: number; - - constructor() { - this.dirty = false; - this.pulseHandle = 0; - this.suppressAutomaticRedraw = false; - this.canvas = unwrapNull(document.getElementById('pf-canvas')) as HTMLCanvasElement; - window.addEventListener('resize', () => this.resizeToFit(false), false); - - this.vrDisplayHeight = null; - this.vrDisplayWidth = null; - } - - setDirty(): void { - if (this.dirty || this.suppressAutomaticRedraw) - return; - this.dirty = true; - window.requestAnimationFrame(() => this.redraw()); - } - - zoomIn(): void { - this.camera.zoomIn(); - } - - zoomOut(): void { - this.camera.zoomOut(); - } - - zoomPulse(): void { - if (this.pulseHandle) { - window.cancelAnimationFrame(this.pulseHandle); - this.pulseHandle = 0; - return; - } - let c = 0; - let d = 0.005; - const self = this; - function tick() { - self.camera.zoom(1 + d); - if (c++ % 200 === 0) { - d *= -1; - } - self.pulseHandle = window.requestAnimationFrame(tick); - } - this.pulseHandle = window.requestAnimationFrame(tick); - } - - rotate(newAngle: number): void { - this.camera.rotate(newAngle); - } - - redraw(): void { - this.dirty = false; - } - - protected resized(): void { - this.setDirty(); - } - - protected inVR(): boolean { - return false; - } - - protected resizeToFit(initialSize: boolean): void { - if (!this.canvas.classList.contains('pf-no-autoresize')) { - - if (this.inVR()) { - const width = unwrapNull(this.vrDisplayWidth); - const height = unwrapNull(this.vrDisplayHeight); - // these are already in device pixel units, no need to multiply - this.canvas.style.width = width + 'px'; - this.canvas.style.height = height + 'px'; - this.canvas.width = width; - this.canvas.height = height; - } else { - const width = window.innerWidth; - const canvasTop = this.canvas.getBoundingClientRect().top; - const height = window.scrollY + window.innerHeight - canvasTop; - const devicePixelRatio = window.devicePixelRatio; - const canvasSize = new Float32Array([width, height]) as glmatrix.vec2; - glmatrix.vec2.scale(canvasSize, canvasSize, devicePixelRatio); - glmatrix.vec2.round(canvasSize, canvasSize); - - this.canvas.style.width = width + 'px'; - this.canvas.style.height = height + 'px'; - this.canvas.width = canvasSize[0]; - this.canvas.height = canvasSize[1]; - } - - } - this.resized(); - } -} - -export abstract class DemoView extends PathfinderView implements RenderContext { - readonly renderer!: Renderer; - - gl!: WebGLRenderingContext; - - shaderPrograms: ShaderMap; - areaLUT: HTMLImageElement; - gammaLUT: HTMLImageElement; - - instancedArraysExt!: ANGLE_instanced_arrays; - textureHalfFloatExt!: OESTextureHalfFloat; - timerQueryExt!: EXTDisjointTimerQuery | null; - vertexArrayObjectExt!: OESVertexArrayObject; - - quadPositionsBuffer!: WebGLBuffer; - quadTexCoordsBuffer!: WebGLBuffer; - quadElementsBuffer!: WebGLBuffer; - - atlasRenderingTimerQuery!: WebGLQuery | null; - compositingTimerQuery!: WebGLQuery | null; - - meshes: PathfinderPackedMeshBuffers[]; - meshData: PathfinderPackedMeshes[]; - - get colorAlphaFormat(): ColorAlphaFormat { - // On macOS, RGBA framebuffers seem to cause driver stalls when switching between rendering - // and texturing. Work around this by using RGB5A1 instead. - return navigator.platform === 'MacIntel' ? 'RGB5_A1' : 'RGBA8'; - } - - get renderContext(): RenderContext { - return this; - } - - protected colorBufferHalfFloatExt: any; - - private wantsScreenshot: boolean; - - /// NB: All subclasses are responsible for creating a renderer in their constructors. - constructor(areaLUT: HTMLImageElement, - gammaLUT: HTMLImageElement, - commonShaderSource: string, - shaderSources: ShaderMap) { - super(); - - this.meshes = []; - this.meshData = []; - - this.initContext(); - - const shaderSource = this.compileShaders(commonShaderSource, shaderSources); - this.shaderPrograms = this.linkShaders(shaderSource); - - this.areaLUT = areaLUT; - this.gammaLUT = gammaLUT; - - this.wantsScreenshot = false; - - } - - attachMeshes(meshes: PathfinderPackedMeshes[]): void { - this.renderer.attachMeshes(meshes); - this.setDirty(); - } - - initQuadVAO(attributes: any): void { - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadPositionsBuffer); - this.gl.vertexAttribPointer(attributes.aPosition, 2, this.gl.FLOAT, false, 0, 0); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadTexCoordsBuffer); - 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.quadElementsBuffer); - } - - queueScreenshot(): void { - this.wantsScreenshot = true; - this.setDirty(); - } - - setAntialiasingOptions(aaType: AntialiasingStrategyName, - aaLevel: number, - aaOptions: AAOptions): - void { - this.renderer.setAntialiasingOptions(aaType, aaLevel, aaOptions); - } - - snapshot(rect: glmatrix.vec4): Uint8Array { - const gl = this.renderContext.gl; - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - const canvasHeight = this.canvas.height; - const width = rect[2] - rect[0], height = rect[3] - rect[1]; - const originX = Math.max(rect[0], 0); - const originY = Math.max(canvasHeight - height, 0); - const flippedBuffer = new Uint8Array(width * height * 4); - gl.readPixels(originX, originY, width, height, gl.RGBA, gl.UNSIGNED_BYTE, flippedBuffer); - - const buffer = new Uint8Array(width * height * 4); - for (let y = 0; y < height; y++) { - const destRowStart = y * width * 4; - const srcRowStart = (height - y - 1) * width * 4; - buffer.set(flippedBuffer.slice(srcRowStart, srcRowStart + width * 4), - destRowStart); - } - - return buffer; - } - - enterVR(): void {} - redrawVR(): void { - this.renderer.redraw(); - } - - redraw(): void { - super.redraw(); - - if (!this.renderer.meshesAttached) - return; - - if (!this.renderer.inVR) { - this.renderer.redraw(); - } else { - this.redrawVR(); - } - - // Invoke the post-render hook. - this.renderingFinished(); - - // Take a screenshot if desired. - if (this.wantsScreenshot) { - this.wantsScreenshot = false; - this.takeScreenshot(); - } - } - - protected resized(): void { - super.resized(); - this.renderer.canvasResized(); - } - - protected inVR(): boolean { - return this.renderer.inVR; - } - - protected initContext(): void { - // Initialize the OpenGL context. - this.gl = expectNotNull(this.canvas.getContext('webgl', { antialias: false, depth: true }), - "Failed to initialize WebGL! Check that your browser supports it."); - this.colorBufferHalfFloatExt = this.gl.getExtension('EXT_color_buffer_half_float'); - this.instancedArraysExt = unwrapNull(this.gl.getExtension('ANGLE_instanced_arrays')); - this.textureHalfFloatExt = unwrapNull(this.gl.getExtension('OES_texture_half_float')); - this.timerQueryExt = this.gl.getExtension('EXT_disjoint_timer_query'); - this.vertexArrayObjectExt = unwrapNull(this.gl.getExtension('OES_vertex_array_object')); - this.gl.getExtension('EXT_frag_depth'); - this.gl.getExtension('OES_element_index_uint'); - this.gl.getExtension('OES_standard_derivatives'); - this.gl.getExtension('OES_texture_float'); - this.gl.getExtension('WEBGL_depth_texture'); - - // Upload quad buffers. - this.quadPositionsBuffer = unwrapNull(this.gl.createBuffer()); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadPositionsBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, QUAD_POSITIONS, this.gl.STATIC_DRAW); - this.quadTexCoordsBuffer = unwrapNull(this.gl.createBuffer()); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadTexCoordsBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, QUAD_TEX_COORDS, this.gl.STATIC_DRAW); - this.quadElementsBuffer = unwrapNull(this.gl.createBuffer()); - this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.quadElementsBuffer); - this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, QUAD_ELEMENTS, this.gl.STATIC_DRAW); - - // Set up our timer queries for profiling. - if (this.timerQueryExt != null) { - this.atlasRenderingTimerQuery = this.timerQueryExt.createQueryEXT(); - this.compositingTimerQuery = this.timerQueryExt.createQueryEXT(); - } else { - this.atlasRenderingTimerQuery = null; - this.compositingTimerQuery = null; - } - } - - protected renderingFinished(): void {} - - private compileShaders(commonSource: string, shaderSources: ShaderMap): - ShaderMap { - const shaders: Partial>> = {}; - - for (const shaderKey of SHADER_NAMES) { - for (const typeName of ['vertex', 'fragment'] as Array<'vertex' | 'fragment'>) { - const type = { - fragment: this.gl.FRAGMENT_SHADER, - vertex: this.gl.VERTEX_SHADER, - }[typeName]; - - const source = shaderSources[shaderKey][typeName]; - const shader = this.gl.createShader(type); - if (shader == null) - throw new PathfinderError("Failed to create shader!"); - - this.gl.shaderSource(shader, commonSource + "\n#line 1\n" + source); - this.gl.compileShader(shader); - if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { - const infoLog = this.gl.getShaderInfoLog(shader); - throw new PathfinderError(`Failed to compile ${typeName} shader ` + - `"${shaderKey}":\n${infoLog}`); - } - - if (shaders[shaderKey] == null) - shaders[shaderKey] = {}; - shaders[shaderKey]![typeName] = shader; - } - } - - return shaders as ShaderMap; - } - - private linkShaders(shaders: ShaderMap): - ShaderMap { - const shaderProgramMap: Partial> = {}; - for (const shaderName of Object.keys(shaders) as Array>) { - shaderProgramMap[shaderName] = new PathfinderShaderProgram(this.gl, - shaderName, - shaders[shaderName]); - } - return shaderProgramMap as ShaderMap; - } - - private takeScreenshot(): void { - const width = this.canvas.width, height = this.canvas.height; - const scratchCanvas = document.createElement('canvas'); - scratchCanvas.width = width; - scratchCanvas.height = height; - const scratch2DContext = unwrapNull(scratchCanvas.getContext('2d')); - scratch2DContext.drawImage(this.canvas, 0, 0, width, height); - - const scratchLink = document.createElement('a'); - scratchLink.download = 'pathfinder-screenshot.png'; - scratchLink.href = scratchCanvas.toDataURL(); - scratchLink.style.position = 'absolute'; - document.body.appendChild(scratchLink); - scratchLink.click(); - document.body.removeChild(scratchLink); - } -} - -export interface RenderContext { - /// The OpenGL context. - readonly gl: WebGLRenderingContext; - - readonly instancedArraysExt: ANGLEInstancedArrays; - readonly textureHalfFloatExt: OESTextureHalfFloat; - readonly timerQueryExt: EXTDisjointTimerQuery | null; - readonly vertexArrayObjectExt: OESVertexArrayObject; - - readonly colorAlphaFormat: ColorAlphaFormat; - - readonly shaderPrograms: ShaderMap; - readonly areaLUT: HTMLImageElement; - readonly gammaLUT: HTMLImageElement; - - readonly quadPositionsBuffer: WebGLBuffer; - readonly quadElementsBuffer: WebGLBuffer; - - readonly atlasRenderingTimerQuery: WebGLQuery | null; - readonly compositingTimerQuery: WebGLQuery | null; - - initQuadVAO(attributes: any): void; - setDirty(): void; -} diff --git a/demo/client/src/xcaa-strategy.ts b/demo/client/src/xcaa-strategy.ts deleted file mode 100644 index f1323b4e..00000000 --- a/demo/client/src/xcaa-strategy.ts +++ /dev/null @@ -1,886 +0,0 @@ -// pathfinder/demo/client/src/xcaa-strategy.ts -// -// Copyright © 2017 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. - -import * as glmatrix from 'gl-matrix'; -import * as _ from 'lodash'; - -import {AntialiasingStrategy, DirectRenderingMode, SubpixelAAType} from './aa-strategy'; -import PathfinderBufferTexture from './buffer-texture'; -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, ShaderMap} from './shader-loader'; -import {computeStemDarkeningAmount} from './text'; -import {assert, FLOAT32_SIZE, lerp, Range, UINT16_SIZE, UINT32_SIZE, unwrapNull} from './utils'; -import {unwrapUndef} from './utils'; -import {RenderContext} from './view'; - -interface FastEdgeVAOs { - upper: WebGLVertexArrayObject; - lower: WebGLVertexArrayObject; -} - -type Direction = 'upper' | 'lower'; - -const DIRECTIONS: Direction[] = ['upper', 'lower']; - -const PATCH_VERTICES: Float32Array = new Float32Array([ - 0.0, 0.0, - 0.5, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 0.5, 1.0, - 1.0, 1.0, -]); - -const MCAA_PATCH_INDICES: Uint8Array = new Uint8Array([0, 1, 2, 1, 3, 2]); - -export type TransformType = 'affine' | '3d'; - -export abstract class XCAAStrategy extends AntialiasingStrategy { - abstract readonly directRenderingMode: DirectRenderingMode; - - protected patchVertexBuffer: WebGLBuffer | null = null; - protected patchIndexBuffer: WebGLBuffer | null = null; - - get passCount(): number { - return 1; - } - - protected abstract get transformType(): TransformType; - - protected abstract get patchIndices(): Uint8Array; - - protected pathBoundsBufferTextures: PathfinderBufferTexture[]; - - protected supersampledFramebufferSize: glmatrix.vec2; - protected destFramebufferSize: glmatrix.vec2; - - protected resolveVAO: WebGLVertexArrayObject | null; - - protected aaAlphaTexture: WebGLTexture | null = null; - protected aaDepthTexture: WebGLTexture | null = null; - protected aaFramebuffer: WebGLFramebuffer | null = null; - - protected abstract get mightUseAAFramebuffer(): boolean; - - constructor(level: number, subpixelAA: SubpixelAAType) { - super(subpixelAA); - - this.supersampledFramebufferSize = glmatrix.vec2.create(); - this.destFramebufferSize = glmatrix.vec2.create(); - - this.pathBoundsBufferTextures = []; - } - - init(renderer: Renderer): void { - super.init(renderer); - } - - attachMeshes(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - this.createResolveVAO(renderer); - this.pathBoundsBufferTextures = []; - - this.patchVertexBuffer = unwrapNull(gl.createBuffer()); - gl.bindBuffer(gl.ARRAY_BUFFER, this.patchVertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, PATCH_VERTICES.buffer as ArrayBuffer, gl.STATIC_DRAW); - gl.bindBuffer(gl.ARRAY_BUFFER, null); - - this.patchIndexBuffer = unwrapNull(gl.createBuffer()); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.patchIndexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, - this.patchIndices.buffer as ArrayBuffer, - gl.STATIC_DRAW); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); - } - - setFramebufferSize(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - this.destFramebufferSize = glmatrix.vec2.clone(renderer.destAllocatedSize); - glmatrix.vec2.mul(this.supersampledFramebufferSize, - this.destFramebufferSize, - this.supersampleScale); - - this.initAAAlphaFramebuffer(renderer); - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - } - - prepareForRendering(renderer: Renderer): void {} - - prepareForDirectRendering(renderer: Renderer): void {} - - finishAntialiasingObject(renderer: Renderer, objectIndex: number): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - this.initResolveFramebufferForObject(renderer, objectIndex); - - if (!this.usesAAFramebuffer(renderer)) - return; - - const usedSize = this.supersampledUsedSize(renderer); - gl.scissor(0, 0, usedSize[0], usedSize[1]); - gl.enable(gl.SCISSOR_TEST); - - // Clear out the color and depth textures. - gl.clearColor(1.0, 1.0, 1.0, 1.0); - gl.clearDepth(0.0); - gl.depthMask(true); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - prepareToRenderObject(renderer: Renderer, objectIndex: number): void {} - - finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void { - // TODO(pcwalton) - } - - antialiasObject(renderer: Renderer, objectIndex: number): void { - // Perform early preparations. - this.createPathBoundsBufferTextureForObjectIfNecessary(renderer, objectIndex); - - // Set up antialiasing. - this.prepareAA(renderer); - - // Clear. - this.clearForAA(renderer); - } - - resolveAAForObject(renderer: Renderer, objectIndex: number): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - if (!this.usesAAFramebuffer(renderer)) - return; - - const resolveProgram = this.getResolveProgram(renderer); - if (resolveProgram == null) - return; - - // Set state for XCAA resolve. - const usedSize = renderer.destUsedSize; - gl.scissor(0, 0, usedSize[0], usedSize[1]); - gl.enable(gl.SCISSOR_TEST); - this.setDepthAndBlendModeForResolve(renderContext); - - // Clear out the resolve buffer, if necessary. - this.clearForResolve(renderer); - - // Resolve. - gl.useProgram(resolveProgram.program); - renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.resolveVAO); - gl.uniform2i(resolveProgram.uniforms.uFramebufferSize, - this.destFramebufferSize[0], - this.destFramebufferSize[1]); - gl.activeTexture(renderContext.gl.TEXTURE0); - gl.bindTexture(renderContext.gl.TEXTURE_2D, this.aaAlphaTexture); - gl.uniform1i(resolveProgram.uniforms.uAAAlpha, 0); - gl.uniform2i(resolveProgram.uniforms.uAAAlphaDimensions, - this.supersampledFramebufferSize[0], - this.supersampledFramebufferSize[1]); - if (renderer.bgColor != null) - gl.uniform4fv(resolveProgram.uniforms.uBGColor, renderer.bgColor); - if (renderer.fgColor != null) - gl.uniform4fv(resolveProgram.uniforms.uFGColor, renderer.fgColor); - renderer.setTransformSTAndTexScaleUniformsForDest(resolveProgram.uniforms); - this.setSubpixelAAKernelUniform(renderer, resolveProgram.uniforms); - this.setAdditionalStateForResolveIfNecessary(renderer, resolveProgram, 1); - gl.drawElements(renderContext.gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0); - renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); - } - - resolve(pass: number, renderer: Renderer): void {} - - get transform(): glmatrix.mat4 { - return glmatrix.mat4.create(); - } - - protected abstract usesAAFramebuffer(renderer: Renderer): boolean; - - protected supersampledUsedSize(renderer: Renderer): glmatrix.vec2 { - const usedSize = glmatrix.vec2.create(); - glmatrix.vec2.mul(usedSize, renderer.destUsedSize, this.supersampleScale); - return usedSize; - } - - protected prepareAA(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - // Set state for antialiasing. - const usedSize = this.supersampledUsedSize(renderer); - if (this.usesAAFramebuffer(renderer)) - gl.bindFramebuffer(gl.FRAMEBUFFER, this.aaFramebuffer); - gl.viewport(0, - 0, - this.supersampledFramebufferSize[0], - this.supersampledFramebufferSize[1]); - gl.scissor(0, 0, usedSize[0], usedSize[1]); - gl.enable(gl.SCISSOR_TEST); - } - - protected setAAState(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - const usedSize = this.supersampledUsedSize(renderer); - if (this.usesAAFramebuffer(renderer)) - gl.bindFramebuffer(gl.FRAMEBUFFER, this.aaFramebuffer); - gl.viewport(0, - 0, - this.supersampledFramebufferSize[0], - this.supersampledFramebufferSize[1]); - gl.scissor(0, 0, usedSize[0], usedSize[1]); - gl.enable(gl.SCISSOR_TEST); - - this.setAADepthState(renderer); - } - - protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - switch (this.transformType) { - case 'affine': - renderer.setTransformAffineUniforms(uniforms, 0); - break; - case '3d': - renderer.setTransformUniform(uniforms, 0, 0); - break; - } - - gl.uniform2i(uniforms.uFramebufferSize, - this.supersampledFramebufferSize[0], - this.supersampledFramebufferSize[1]); - renderer.pathTransformBufferTextures[0].ext.bind(gl, uniforms, 0); - renderer.pathTransformBufferTextures[0].st.bind(gl, uniforms, 1); - this.pathBoundsBufferTextures[objectIndex].bind(gl, uniforms, 2); - renderer.setHintsUniform(uniforms); - renderer.bindAreaLUT(4, uniforms); - } - - protected setDepthAndBlendModeForResolve(renderContext: RenderContext): void { - const gl = renderContext.gl; - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - } - - protected setAdditionalStateForResolveIfNecessary(renderer: Renderer, - resolveProgram: PathfinderShaderProgram, - firstFreeTextureUnit: number): - void {} - - protected abstract clearForAA(renderer: Renderer): void; - protected abstract getResolveProgram(renderer: Renderer): PathfinderShaderProgram | null; - protected abstract setAADepthState(renderer: Renderer): void; - protected abstract clearForResolve(renderer: Renderer): void; - - private initResolveFramebufferForObject(renderer: Renderer, objectIndex: number): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - gl.bindFramebuffer(gl.FRAMEBUFFER, renderer.destFramebuffer); - renderer.setDrawViewport(); - gl.disable(gl.SCISSOR_TEST); - } - - private initAAAlphaFramebuffer(renderer: Renderer): void { - if (!this.mightUseAAFramebuffer) { - this.aaAlphaTexture = null; - this.aaDepthTexture = null; - this.aaFramebuffer = null; - return; - } - - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - this.aaAlphaTexture = unwrapNull(gl.createTexture()); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, this.aaAlphaTexture); - gl.texImage2D(gl.TEXTURE_2D, - 0, - gl.RGB, - this.supersampledFramebufferSize[0], - this.supersampledFramebufferSize[1], - 0, - gl.RGB, - renderContext.textureHalfFloatExt.HALF_FLOAT_OES, - null); - setTextureParameters(gl, gl.NEAREST); - - this.aaDepthTexture = createFramebufferDepthTexture(gl, this.supersampledFramebufferSize); - this.aaFramebuffer = createFramebuffer(gl, this.aaAlphaTexture, this.aaDepthTexture); - } - - private createPathBoundsBufferTextureForObjectIfNecessary(renderer: Renderer, - objectIndex: number): - void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - const pathBounds = renderer.pathBoundingRects(objectIndex); - - if (this.pathBoundsBufferTextures[objectIndex] == null) { - this.pathBoundsBufferTextures[objectIndex] = - new PathfinderBufferTexture(gl, 'uPathBounds'); - } - - this.pathBoundsBufferTextures[objectIndex].upload(gl, pathBounds); - } - - private createResolveVAO(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - const resolveProgram = this.getResolveProgram(renderer); - if (resolveProgram == null) - return; - - this.resolveVAO = renderContext.vertexArrayObjectExt.createVertexArrayOES(); - renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.resolveVAO); - - gl.useProgram(resolveProgram.program); - renderContext.initQuadVAO(resolveProgram.attributes); - - renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); - } - - protected get directDepthTexture(): WebGLTexture | null { - return null; - } - - protected get supersampleScale(): glmatrix.vec2 { - return glmatrix.vec2.fromValues(this.subpixelAA !== 'none' ? 3.0 : 1.0, 1.0); - } -} - -export class MCAAStrategy extends XCAAStrategy { - protected vao: WebGLVertexArrayObject | null; - - protected get patchIndices(): Uint8Array { - return MCAA_PATCH_INDICES; - } - - protected get transformType(): TransformType { - return 'affine'; - } - - protected get mightUseAAFramebuffer(): boolean { - return true; - } - - attachMeshes(renderer: Renderer): void { - super.attachMeshes(renderer); - - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - this.vao = renderContext.vertexArrayObjectExt.createVertexArrayOES(); - } - - antialiasObject(renderer: Renderer, objectIndex: number): void { - super.antialiasObject(renderer, objectIndex); - - const shaderProgram = this.edgeProgram(renderer); - this.antialiasEdgesOfObjectWithProgram(renderer, objectIndex, shaderProgram); - } - - protected usesAAFramebuffer(renderer: Renderer): boolean { - return !renderer.isMulticolor; - } - - protected getResolveProgram(renderer: Renderer): PathfinderShaderProgram | null { - const renderContext = renderer.renderContext; - if (renderer.isMulticolor) - return null; - if (this.subpixelAA !== 'none' && renderer.allowSubpixelAA) - return renderContext.shaderPrograms.xcaaMonoSubpixelResolve; - return renderContext.shaderPrograms.xcaaMonoResolve; - } - - protected clearForAA(renderer: Renderer): void { - if (!this.usesAAFramebuffer(renderer)) - return; - - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - gl.clearColor(0.0, 0.0, 0.0, 0.0); - gl.clearDepth(0.0); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - protected setAADepthState(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - if (this.directRenderingMode !== 'conservative') { - gl.disable(gl.DEPTH_TEST); - return; - } - - gl.depthFunc(gl.GREATER); - gl.depthMask(false); - gl.enable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); - } - - protected clearForResolve(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - if (!renderer.isMulticolor) { - gl.clearColor(0.0, 0.0, 0.0, 1.0); - gl.clear(gl.COLOR_BUFFER_BIT); - } - } - - protected setBlendModeForAA(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - if (renderer.isMulticolor) - gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE); - else - gl.blendFunc(gl.ONE, gl.ONE); - - gl.blendEquation(gl.FUNC_ADD); - gl.enable(gl.BLEND); - } - - protected prepareAA(renderer: Renderer): void { - super.prepareAA(renderer); - - this.setBlendModeForAA(renderer); - } - - protected initVAOForObject(renderer: Renderer, objectIndex: number): void { - if (renderer.meshBuffers == null || renderer.meshes == null) - return; - - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - const pathRange = renderer.pathRangeForObject(objectIndex); - const meshIndex = renderer.meshIndexForObject(objectIndex); - - const shaderProgram = this.edgeProgram(renderer); - const attributes = shaderProgram.attributes; - - // FIXME(pcwalton): Refactor. - const vao = this.vao; - renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao); - - const bBoxRanges = renderer.meshes[meshIndex].bBoxPathRanges; - const offset = calculateStartFromIndexRanges(pathRange, bBoxRanges); - - gl.useProgram(shaderProgram.program); - gl.bindBuffer(gl.ARRAY_BUFFER, renderer.renderContext.quadPositionsBuffer); - gl.vertexAttribPointer(attributes.aTessCoord, 2, gl.FLOAT, false, FLOAT32_SIZE * 2, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, renderer.meshBuffers[meshIndex].bBoxes); - gl.vertexAttribPointer(attributes.aRect, - 4, - gl.FLOAT, - false, - FLOAT32_SIZE * 20, - FLOAT32_SIZE * 0 + offset * FLOAT32_SIZE * 20); - gl.vertexAttribPointer(attributes.aUV, - 4, - gl.FLOAT, - false, - FLOAT32_SIZE * 20, - FLOAT32_SIZE * 4 + offset * FLOAT32_SIZE * 20); - gl.vertexAttribPointer(attributes.aDUVDX, - 4, - gl.FLOAT, - false, - FLOAT32_SIZE * 20, - FLOAT32_SIZE * 8 + offset * FLOAT32_SIZE * 20); - gl.vertexAttribPointer(attributes.aDUVDY, - 4, - gl.FLOAT, - false, - FLOAT32_SIZE * 20, - FLOAT32_SIZE * 12 + offset * FLOAT32_SIZE * 20); - gl.vertexAttribPointer(attributes.aSignMode, - 4, - gl.FLOAT, - false, - FLOAT32_SIZE * 20, - FLOAT32_SIZE * 16 + offset * FLOAT32_SIZE * 20); - - gl.enableVertexAttribArray(attributes.aTessCoord); - gl.enableVertexAttribArray(attributes.aRect); - gl.enableVertexAttribArray(attributes.aUV); - gl.enableVertexAttribArray(attributes.aDUVDX); - gl.enableVertexAttribArray(attributes.aDUVDY); - gl.enableVertexAttribArray(attributes.aSignMode); - - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aRect, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aUV, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aDUVDX, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aDUVDY, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aSignMode, 1); - - gl.bindBuffer(gl.ARRAY_BUFFER, renderer.meshBuffers[meshIndex].bBoxPathIDs); - gl.vertexAttribPointer(attributes.aPathID, - 1, - gl.UNSIGNED_SHORT, - false, - UINT16_SIZE, - offset * UINT16_SIZE); - gl.enableVertexAttribArray(attributes.aPathID); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderer.renderContext.quadElementsBuffer); - - renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); - } - - protected edgeProgram(renderer: Renderer): PathfinderShaderProgram { - return renderer.renderContext.shaderPrograms.mcaa; - } - - protected antialiasEdgesOfObjectWithProgram(renderer: Renderer, - objectIndex: number, - shaderProgram: PathfinderShaderProgram): - void { - if (renderer.meshBuffers == null || renderer.meshes == null) - return; - - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - const pathRange = renderer.pathRangeForObject(objectIndex); - const meshIndex = renderer.meshIndexForObject(objectIndex); - - this.initVAOForObject(renderer, objectIndex); - - gl.useProgram(shaderProgram.program); - const uniforms = shaderProgram.uniforms; - this.setAAUniforms(renderer, uniforms, objectIndex); - - // FIXME(pcwalton): Refactor. - const vao = this.vao; - renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao); - - this.setBlendModeForAA(renderer); - this.setAADepthState(renderer); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer); - - const bBoxRanges = renderer.meshes[meshIndex].bBoxPathRanges; - const count = calculateCountFromIndexRanges(pathRange, bBoxRanges); - - renderContext.instancedArraysExt - .drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count); - - renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); - } - - get directRenderingMode(): DirectRenderingMode { - // FIXME(pcwalton): Only in multicolor mode? - return 'conservative'; - } - - protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): void { - super.setAAUniforms(renderer, uniforms, objectIndex); - - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - renderer.setPathColorsUniform(0, uniforms, 3); - - gl.uniform1i(uniforms.uMulticolor, renderer.isMulticolor ? 1 : 0); - } -} - -export class StencilAAAStrategy extends XCAAStrategy { - directRenderingMode: DirectRenderingMode = 'none'; - - protected transformType: TransformType = 'affine'; - protected patchIndices: Uint8Array = MCAA_PATCH_INDICES; - protected mightUseAAFramebuffer: boolean = true; - - private vao: WebGLVertexArrayObject; - - attachMeshes(renderer: Renderer): void { - super.attachMeshes(renderer); - this.createVAO(renderer); - } - - antialiasObject(renderer: Renderer, objectIndex: number): void { - super.antialiasObject(renderer, objectIndex); - - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - if (renderer.meshes == null) - return; - - // Antialias. - const shaderPrograms = renderer.renderContext.shaderPrograms; - this.setAAState(renderer); - this.setBlendModeForAA(renderer); - - const program = renderContext.shaderPrograms.stencilAAA; - gl.useProgram(program.program); - const uniforms = program.uniforms; - this.setAAUniforms(renderer, uniforms, objectIndex); - - renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.vao); - - // FIXME(pcwalton): Only render the appropriate instances. - const count = renderer.meshes[0].count('stencilSegments'); - for (let side = 0; side < 2; side++) { - gl.uniform1i(uniforms.uSide, side); - renderContext.instancedArraysExt - .drawElementsInstancedANGLE(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, count); - } - - renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); - } - - protected usesAAFramebuffer(renderer: Renderer): boolean { - return true; - } - - protected clearForAA(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - gl.clearColor(0.0, 0.0, 0.0, 0.0); - gl.clearDepth(0.0); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - } - - protected getResolveProgram(renderer: Renderer): PathfinderShaderProgram | null { - const renderContext = renderer.renderContext; - - if (this.subpixelAA !== 'none' && renderer.allowSubpixelAA) - return renderContext.shaderPrograms.xcaaMonoSubpixelResolve; - return renderContext.shaderPrograms.xcaaMonoResolve; - } - - protected setAADepthState(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); - } - - protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): - void { - super.setAAUniforms(renderer, uniforms, objectIndex); - renderer.setEmboldenAmountUniform(objectIndex, uniforms); - } - - protected clearForResolve(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - gl.clearColor(0.0, 0.0, 0.0, 0.0); - gl.clear(gl.COLOR_BUFFER_BIT); - } - - private createVAO(renderer: Renderer): void { - if (renderer.meshBuffers == null || renderer.meshes == null) - return; - - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - const program = renderContext.shaderPrograms.stencilAAA; - const attributes = program.attributes; - - this.vao = renderContext.vertexArrayObjectExt.createVertexArrayOES(); - renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.vao); - - const vertexPositionsBuffer = renderer.meshBuffers[0].stencilSegments; - const vertexNormalsBuffer = renderer.meshBuffers[0].stencilNormals; - const pathIDsBuffer = renderer.meshBuffers[0].stencilSegmentPathIDs; - - gl.useProgram(program.program); - gl.bindBuffer(gl.ARRAY_BUFFER, renderContext.quadPositionsBuffer); - gl.vertexAttribPointer(attributes.aTessCoord, 2, gl.FLOAT, false, 0, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionsBuffer); - gl.vertexAttribPointer(attributes.aFromPosition, 2, gl.FLOAT, false, FLOAT32_SIZE * 6, 0); - gl.vertexAttribPointer(attributes.aCtrlPosition, - 2, - gl.FLOAT, - false, - FLOAT32_SIZE * 6, - FLOAT32_SIZE * 2); - gl.vertexAttribPointer(attributes.aToPosition, - 2, - gl.FLOAT, - false, - FLOAT32_SIZE * 6, - FLOAT32_SIZE * 4); - gl.bindBuffer(gl.ARRAY_BUFFER, vertexNormalsBuffer); - gl.vertexAttribPointer(attributes.aFromNormal, 2, gl.FLOAT, false, FLOAT32_SIZE * 6, 0); - gl.vertexAttribPointer(attributes.aCtrlNormal, - 2, - gl.FLOAT, - false, - FLOAT32_SIZE * 6, - FLOAT32_SIZE * 2); - gl.vertexAttribPointer(attributes.aToNormal, - 2, - gl.FLOAT, - false, - FLOAT32_SIZE * 6, - FLOAT32_SIZE * 4); - gl.bindBuffer(gl.ARRAY_BUFFER, pathIDsBuffer); - gl.vertexAttribPointer(attributes.aPathID, 1, gl.UNSIGNED_SHORT, false, 0, 0); - - gl.enableVertexAttribArray(attributes.aTessCoord); - gl.enableVertexAttribArray(attributes.aFromPosition); - gl.enableVertexAttribArray(attributes.aCtrlPosition); - gl.enableVertexAttribArray(attributes.aToPosition); - gl.enableVertexAttribArray(attributes.aFromNormal); - gl.enableVertexAttribArray(attributes.aCtrlNormal); - gl.enableVertexAttribArray(attributes.aToNormal); - gl.enableVertexAttribArray(attributes.aPathID); - - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aFromPosition, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aCtrlPosition, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aToPosition, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aFromNormal, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aCtrlNormal, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aToNormal, 1); - renderContext.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aPathID, 1); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, renderContext.quadElementsBuffer); - - renderContext.vertexArrayObjectExt.bindVertexArrayOES(null); - } - - private setBlendModeForAA(renderer: Renderer): void { - const renderContext = renderer.renderContext; - const gl = renderContext.gl; - - gl.blendEquation(gl.FUNC_ADD); - gl.blendFunc(gl.ONE, gl.ONE); - gl.enable(gl.BLEND); - } -} - -/// Switches between mesh-based and stencil-based analytic antialiasing depending on whether stem -/// darkening is enabled. -/// -/// FIXME(pcwalton): Share textures and FBOs between the two strategies. -export class AdaptiveStencilMeshAAAStrategy extends AntialiasingStrategy { - private meshStrategy: MCAAStrategy; - private stencilStrategy: StencilAAAStrategy; - - get directRenderingMode(): DirectRenderingMode { - return 'none'; - } - - get passCount(): number { - return 1; - } - - constructor(level: number, subpixelAA: SubpixelAAType) { - super(subpixelAA); - this.meshStrategy = new MCAAStrategy(level, subpixelAA); - this.stencilStrategy = new StencilAAAStrategy(level, subpixelAA); - } - - init(renderer: Renderer): void { - this.meshStrategy.init(renderer); - this.stencilStrategy.init(renderer); - } - - attachMeshes(renderer: Renderer): void { - this.meshStrategy.attachMeshes(renderer); - this.stencilStrategy.attachMeshes(renderer); - } - - setFramebufferSize(renderer: Renderer): void { - this.meshStrategy.setFramebufferSize(renderer); - this.stencilStrategy.setFramebufferSize(renderer); - } - - get transform(): glmatrix.mat4 { - return this.meshStrategy.transform; - } - - prepareForRendering(renderer: Renderer): void { - this.getAppropriateStrategy(renderer).prepareForRendering(renderer); - } - - prepareForDirectRendering(renderer: Renderer): void { - this.getAppropriateStrategy(renderer).prepareForDirectRendering(renderer); - } - - finishAntialiasingObject(renderer: Renderer, objectIndex: number): void { - this.getAppropriateStrategy(renderer).finishAntialiasingObject(renderer, objectIndex); - } - - prepareToRenderObject(renderer: Renderer, objectIndex: number): void { - this.getAppropriateStrategy(renderer).prepareToRenderObject(renderer, objectIndex); - } - - finishDirectlyRenderingObject(renderer: Renderer, objectIndex: number): void { - this.getAppropriateStrategy(renderer).finishDirectlyRenderingObject(renderer, objectIndex); - } - - antialiasObject(renderer: Renderer, objectIndex: number): void { - this.getAppropriateStrategy(renderer).antialiasObject(renderer, objectIndex); - } - - resolveAAForObject(renderer: Renderer, objectIndex: number): void { - this.getAppropriateStrategy(renderer).resolveAAForObject(renderer, objectIndex); - } - - resolve(pass: number, renderer: Renderer): void { - this.getAppropriateStrategy(renderer).resolve(pass, renderer); - } - - worldTransformForPass(renderer: Renderer, pass: number): glmatrix.mat4 { - return glmatrix.mat4.create(); - } - - private getAppropriateStrategy(renderer: Renderer): AntialiasingStrategy { - return renderer.needsStencil ? this.stencilStrategy : this.meshStrategy; - } -} - -function calculateStartFromIndexRanges(pathRange: Range, indexRanges: Range[]): number { - return indexRanges.length === 0 ? 0 : indexRanges[pathRange.start - 1].start; -} - -function calculateCountFromIndexRanges(pathRange: Range, indexRanges: Range[]): number { - if (indexRanges.length === 0) - return 0; - - let lastIndex; - if (pathRange.end - 1 >= indexRanges.length) - lastIndex = unwrapUndef(_.last(indexRanges)).end; - else - lastIndex = indexRanges[pathRange.end - 1].start; - - const firstIndex = indexRanges[pathRange.start - 1].start; - - return lastIndex - firstIndex; -} diff --git a/demo/client/tsconfig.json b/demo/client/tsconfig.json deleted file mode 100644 index 09871b14..00000000 --- a/demo/client/tsconfig.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2017", - "module": "commonjs", - "types": [ - "base64-js", - "gl-matrix", - "lodash", - "node", - "opentype.js", - "webgl-ext" - ], - "typeRoots": [ - "node_modules/@types" - ], - "sourceMap": true, - "strict": true, - "allowUnreachableCode": true - }, - "include": [ - "src" - ], - "exclude": [ - "node_modules" - ] -} \ No newline at end of file diff --git a/demo/client/tslint.json b/demo/client/tslint.json deleted file mode 100644 index 552e7c09..00000000 --- a/demo/client/tslint.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "defaultSeverity": "error", - "extends": [ - "tslint:recommended" - ], - "jsRules": { - "quotemark": false - }, - "rules": { - "quotemark": false, - "interface-name": false, - "curly": false, - "member-access": false, - "max-classes-per-file": false, - "arrow-parens": false, - "one-variable-per-declaration": false, - "object-literal-shorthand": false, - "no-empty": false, - "variable-name": false, - "no-bitwise": false, - "new-parens": false, - "no-conditional-assignment": false, - "no-shadowed-variable": false, - "no-var-requires": false, - "max-line-length": [true, 100] - }, - "rulesDirectory": [] -} \ No newline at end of file diff --git a/demo/client/webpack.config.js b/demo/client/webpack.config.js deleted file mode 100644 index 5642207a..00000000 --- a/demo/client/webpack.config.js +++ /dev/null @@ -1,64 +0,0 @@ -const Handlebars = require('handlebars'); -const HandlebarsPlugin = require('handlebars-webpack-plugin'); -const RustdocPlugin = require('rustdoc-webpack-plugin'); -const fs = require('fs'); -const path = require('path'); - -const cwd = fs.realpathSync("."); - -module.exports = { - devtool: 'inline-source-map', - entry: { - '3d-demo': "./src/3d-demo.ts", - 'svg-demo': "./src/svg-demo.ts", - 'text-demo': "./src/text-demo.ts", - 'reference-test': "./src/reference-test.ts", - 'benchmark': "./src/benchmark.ts", - 'mesh-debugger': "./src/mesh-debugger.ts", - }, - module: { - rules: [ - { - test: /src(\/|\\)[a-zA-Z0-9_-]+\.tsx?$/, - enforce: 'pre', - loader: 'tslint-loader', - exclude: /node_modules/, - options: { - configFile: "tslint.json", - }, - }, - { - test: /src(\/|\\)[a-zA-Z0-9_-]+\.tsx?$/, - use: 'ts-loader', - exclude: /node_modules/, - }, - ] - }, - resolve: { - extensions: [".tsx", ".ts", ".html", ".js"], - }, - output: { - filename: "[name].js", - path: __dirname, - }, - plugins: [ - new HandlebarsPlugin({ - entry: "html/*.hbs", - output: "./[name]", - partials: ["html/partials/*.hbs"], - helpers: { - octicon: function(iconName) { - const svg = fs.readFileSync(`node_modules/octicons/build/svg/${iconName}.svg`); - return new Handlebars.SafeString(svg); - } - }, - }), - new RustdocPlugin({ - directories: [fs.realpathSync("../..")], - flags: { - 'html-in-header': path.join(cwd, "doc-header.html"), - 'html-before-content': path.join(cwd, "doc-before-content.html"), - }, - }), - ] -}