From ee9c1a7998ad0f30a7b4935ba309dc1bc2e3a292 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 11 Aug 2017 21:26:25 -0700 Subject: [PATCH] Download shaders via XHR --- demo/client/src/index.ts | 119 +++++++++++++++++++++++++++++++++++--- demo/client/tsconfig.json | 4 +- demo/server/src/main.rs | 29 +++++++++- 3 files changed, 143 insertions(+), 9 deletions(-) diff --git a/demo/client/src/index.ts b/demo/client/src/index.ts index 65fb4833..98ed8b52 100644 --- a/demo/client/src/index.ts +++ b/demo/client/src/index.ts @@ -8,6 +8,39 @@ const FONT_SIZE: number = 16.0; const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font"; +const SHADER_URLS: ShaderURLMap = { + directCurve: { + vertex: "/glsl/gles2/direct-curve.vs.glsl", + fragment: "/glsl/gles2/direct-curve.fs.glsl", + }, + directInterior: { + vertex: "/glsl/gles2/direct-interior.vs.glsl", + fragment: "/glsl/gles2/direct-interior.fs.glsl", + }, +}; + +interface ShaderURLMap { + [shaderName: string]: { 'vertex': string, 'fragment': string } +} + +interface ShaderMap { + [shaderName: string]: { [shaderType: number]: WebGLShader }; +} + +interface ShaderProgramMap { + [shaderProgramName: string]: PathfinderShaderProgram; +} + +type ShaderType = number; + +type ShaderTypeName = 'vertex' | 'fragment'; + +function expect(value: T | null, message: string): T { + if (value == null) + throw new Error(message); + return value; +} + class PathfinderMeshes { constructor(encodedResponse: string) { const response = JSON.parse(encodedResponse); @@ -35,7 +68,7 @@ class AppController { } loadFont() { - const file = this.loadFontButton.files[0]; + const file = expect(this.loadFontButton.files, "No file selected!")[0]; const reader = new FileReader; reader.addEventListener('loadend', () => { this.fontData = reader.result; @@ -46,12 +79,10 @@ class AppController { fontLoaded() { this.font = opentype.parse(this.fontData); - if (!this.font.supported) { - window.alert("The font type is unsupported."); - return; - } + if (!this.font.supported) + throw new Error("The font type is unsupported."); - const glyphIDs = this.font.stringToGlyphs(TEXT).map(glyph => glyph.index); + const glyphIDs = this.font.stringToGlyphs(TEXT).map((glyph: any) => glyph.index); const request = { otf: base64js.fromByteArray(new Uint8Array(this.fontData)), @@ -60,7 +91,7 @@ class AppController { pointSize: FONT_SIZE, }; - const xhr = new XMLHttpRequest(); + const xhr = new XMLHttpRequest; xhr.addEventListener('load', () => { this.meshes = new PathfinderMeshes(xhr.responseText); this.meshesReceived(); @@ -84,9 +115,83 @@ class AppController { class PathfinderView { constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; + + this.initContext(); + + this.loadShaders().then(shaders => this.shaderProgramsPromise = this.linkShaders(shaders)); + + window.addEventListener('resize', () => this.resizeToFit(), false); + this.resizeToFit(); + } + + initContext() { + this.gl = expect(this.canvas.getContext('webgl', { antialias: false, depth: true }), + "Failed to initialize WebGL! Check that your browser supports it."); + + this.gl.clearColor(0.0, 0.0, 1.0, 1.0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + this.gl.flush(); + } + + loadShaders(): Promise { + return new Promise((resolve, reject) => { + let shaders: ShaderMap = {}; + const shaderKeys = Object.keys(SHADER_URLS); + let shaderKeysLeft = shaderKeys.length; + + let loaded = (type: ShaderType, shaderKey: string, source: string) => { + const shader = this.gl.createShader(type); + if (shader == null) + throw new Error("Failed to create shader!"); + this.gl.shaderSource(shader, source); + this.gl.compileShader(shader); + if (!(shaderKey in shaders)) + shaders[shaderKey] = {}; + shaders[shaderKey][type] = shader; + + shaderKeysLeft--; + if (shaderKeysLeft == 0) + resolve(shaders); + }; + + for (const shaderKey of shaderKeys) { + for (const typeName of ['vertex', 'fragment'] as Array) { + const type = { + vertex: this.gl.VERTEX_SHADER, + fragment: this.gl.FRAGMENT_SHADER, + }[typeName]; + + const xhr = new XMLHttpRequest; + xhr.addEventListener('load', + () => loaded(type, shaderKey, xhr.responseText), + false); + xhr.addEventListener('error', () => reject(), false); + xhr.open('GET', SHADER_URLS[shaderKey][typeName], true); + xhr.send(); + } + } + }); + } + + linkShaders(shaders: ShaderMap): Promise { + // TODO(pcwalton) + throw new Error("TODO"); + } + + resizeToFit() { + this.canvas.width = window.innerWidth; + this.canvas.height = window.innerHeight - this.canvas.scrollTop; } canvas: HTMLCanvasElement; + gl: WebGLRenderingContext; + shaderProgramsPromise: Promise; +} + +class PathfinderShaderProgram { + constructor(vertexShaderSource: string, fragmentShaderSource: string) { + // TODO(pcwalton) + } } function main() { diff --git a/demo/client/tsconfig.json b/demo/client/tsconfig.json index a92f59c9..1417c77c 100644 --- a/demo/client/tsconfig.json +++ b/demo/client/tsconfig.json @@ -8,7 +8,9 @@ "typeRoots": [ "node_modules/@types" ], - "sourceMap": true + "sourceMap": true, + "strictNullChecks": true, + "noImplicitAny": true }, "include": [ "src" diff --git a/demo/server/src/main.rs b/demo/server/src/main.rs index 53de7ffe..2692b1d4 100644 --- a/demo/server/src/main.rs +++ b/demo/server/src/main.rs @@ -26,9 +26,12 @@ use euclid::{Point2D, Size2D}; use pathfinder_font_renderer::{FontContext, FontInstanceKey, FontKey}; use pathfinder_font_renderer::{GlyphKey, GlyphOutlineBuffer}; use pathfinder_partitioner::partitioner::Partitioner; -use rocket::response::NamedFile; +use rocket::http::{ContentType, Status}; +use rocket::request::Request; +use rocket::response::{NamedFile, Responder, Response}; use rocket_contrib::json::Json; use serde::Serialize; +use std::fs::File; use std::io; use std::mem; use std::path::{Path, PathBuf}; @@ -38,6 +41,7 @@ static STATIC_CSS_BOOTSTRAP_PATH: &'static str = "../client/node_modules/bootstr static STATIC_JS_BOOTSTRAP_PATH: &'static str = "../client/node_modules/bootstrap/dist/js"; static STATIC_JS_JQUERY_PATH: &'static str = "../client/node_modules/jquery/dist"; static STATIC_JS_PATHFINDER_JS_PATH: &'static str = "../client/pathfinder.js"; +static STATIC_GLSL_PATH: &'static str = "../../shaders"; #[derive(Clone, Copy, Serialize, Deserialize)] struct IndexRange { @@ -304,6 +308,28 @@ fn static_js_bootstrap(file: PathBuf) -> Option { fn static_js_jquery(file: PathBuf) -> Option { NamedFile::open(Path::new(STATIC_JS_JQUERY_PATH).join(file)).ok() } +#[get("/glsl/")] +fn static_glsl(file: PathBuf) -> Option { + Shader::open(Path::new(STATIC_GLSL_PATH).join(file)).ok() +} + +struct Shader { + file: File, +} + +impl Shader { + fn open(path: PathBuf) -> io::Result { + File::open(path).map(|file| Shader { + file: file, + }) + } +} + +impl<'a> Responder<'a> for Shader { + fn respond_to(self, _: &Request) -> Result, Status> { + Response::build().header(ContentType::Plain).streamed_body(self.file).ok() + } +} fn main() { rocket::ignite().mount("/", routes![ @@ -313,5 +339,6 @@ fn main() { static_css_bootstrap, static_js_bootstrap, static_js_jquery, + static_glsl, ]).launch(); }