From f8bd4057591eec0c77a148d9de52066bd308af0b Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 14 Aug 2017 16:08:45 -0700 Subject: [PATCH] Get basic polygons rendering in the demo --- demo/client/src/index.ts | 291 +++++++++++++++++++++----- demo/client/tsconfig.json | 3 +- demo/server/src/main.rs | 36 ++-- shaders/gles2/direct-interior.vs.glsl | 4 +- 4 files changed, 260 insertions(+), 74 deletions(-) diff --git a/demo/client/src/index.ts b/demo/client/src/index.ts index 2c5f8f3f..f6b58a7d 100644 --- a/demo/client/src/index.ts +++ b/demo/client/src/index.ts @@ -3,14 +3,30 @@ const base64js = require('base64-js'); const opentype = require('opentype.js'); -const TEXT: string = "A"; +const TEXT: string = "G"; const FONT_SIZE: number = 16.0; const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font"; const COMMON_SHADER_URL: string = '/glsl/gles2/common.inc.glsl'; -const SHADER_URLS: ShaderURLMap = { +const UINT32_SIZE: number = 4; + +const B_POSITION_SIZE: number = 8; + +const B_VERTEX_QUAD_SIZE: number = 8; +const B_VERTEX_QUAD_PATH_ID_OFFSET: number = 0; +const B_VERTEX_QUAD_TEX_COORD_OFFSET: number = 4; +const B_VERTEX_QUAD_SIGN_OFFSET: number = 6; + +const IDENTITY: Matrix4D = [ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0, +]; + +const SHADER_URLS: ShaderMap = { directCurve: { vertex: "/glsl/gles2/direct-curve.vs.glsl", fragment: "/glsl/gles2/direct-curve.fs.glsl", @@ -21,16 +37,34 @@ const SHADER_URLS: ShaderURLMap = { }, }; -interface ShaderURLMap { - [shaderName: string]: { 'vertex': string, 'fragment': string }; +interface UnlinkedShaderProgram { + vertex: WebGLShader; + fragment: WebGLShader; } -interface ShaderMap { - [shaderName: string]: { [shaderType: number]: WebGLShader }; +type Matrix4D = number[]; + +interface ShaderProgramSource { + vertex: string; + fragment: string; } -interface ShaderProgramMap { - [shaderProgramName: string]: PathfinderShaderProgram; +interface ShaderProgramURLs { + vertex: string; + fragment: string; +} + +interface ShaderMap { + directCurve: T; + directInterior: T; +} + +interface UniformMap { + [uniformName: string]: WebGLUniformLocation; +} + +interface AttributeMap { + [attributeName: string]: number; } type ShaderType = number; @@ -43,26 +77,91 @@ function expect(value: T | null, message: string): T { return value; } +function unwrap(value: T | null): T { + return expect(value, "Unexpected null!"); +} + class PathfinderError extends Error { constructor(message?: string | undefined) { super(message); } } -class PathfinderMeshes { +interface Meshes { + readonly bQuads: T; + readonly bVertexPositions: T; + readonly bVertexInfo: T; + readonly coverInteriorIndices: T; + readonly coverCurveIndices: T; + readonly edgeUpperLineIndices: T; + readonly edgeUpperCurveIndices: T; + readonly edgeLowerLineIndices: T; + readonly edgeLowerCurveIndices: T; +} + +type BufferType = number; + +const BUFFER_TYPES: Meshes = { + bQuads: WebGLRenderingContext.ARRAY_BUFFER, + bVertexPositions: WebGLRenderingContext.ARRAY_BUFFER, + bVertexInfo: WebGLRenderingContext.ARRAY_BUFFER, + coverInteriorIndices: WebGLRenderingContext.ELEMENT_ARRAY_BUFFER, + coverCurveIndices: WebGLRenderingContext.ELEMENT_ARRAY_BUFFER, + edgeUpperLineIndices: WebGLRenderingContext.ELEMENT_ARRAY_BUFFER, + edgeUpperCurveIndices: WebGLRenderingContext.ELEMENT_ARRAY_BUFFER, + edgeLowerLineIndices: WebGLRenderingContext.ELEMENT_ARRAY_BUFFER, + edgeLowerCurveIndices: WebGLRenderingContext.ELEMENT_ARRAY_BUFFER, +}; + +class PathfinderMeshData implements Meshes { constructor(encodedResponse: string) { const response = JSON.parse(encodedResponse); if (!('Ok' in response)) throw new PathfinderError("Failed to partition the font!"); const meshes = response.Ok; - this.bQuadPositions = base64js.toByteArray(meshes.bQuadPositions); - this.bQuadInfo = base64js.toByteArray(meshes.bQuadInfo); - this.bVertices = base64js.toByteArray(meshes.bVertices); + for (const bufferName of Object.keys(BUFFER_TYPES) as Array) + this[bufferName] = base64js.toByteArray(meshes[bufferName]).buffer; } - bQuadPositions: ArrayBuffer; - bQuadInfo: ArrayBuffer; - bVertices: ArrayBuffer; + readonly bQuads: ArrayBuffer; + readonly bVertexPositions: ArrayBuffer; + readonly bVertexInfo: ArrayBuffer; + readonly coverInteriorIndices: ArrayBuffer; + readonly coverCurveIndices: ArrayBuffer; + readonly edgeUpperLineIndices: ArrayBuffer; + readonly edgeUpperCurveIndices: ArrayBuffer; + readonly edgeLowerLineIndices: ArrayBuffer; + readonly edgeLowerCurveIndices: ArrayBuffer; +} + +class PathfinderMeshBuffers implements Meshes { + constructor(gl: WebGLRenderingContext, meshData: PathfinderMeshData) { + for (const bufferName of Object.keys(BUFFER_TYPES) as Array) { + const bufferType = BUFFER_TYPES[bufferName]; + const buffer = expect(gl.createBuffer(), "Failed to create buffer!"); + gl.bindBuffer(bufferType, buffer); + gl.bufferData(bufferType, meshData[bufferName], gl.STATIC_DRAW); + console.log(`${bufferName} has size ${meshData[bufferName].byteLength}`); + if (bufferName == 'coverInteriorIndices') { + const typedArray = new Uint32Array(meshData[bufferName]); + let array = []; + for (let i = 0; i < typedArray.length; i++) + array[i] = typedArray[i]; + console.log(array.toString()); + } + this[bufferName] = buffer; + } + } + + readonly bQuads: WebGLBuffer; + readonly bVertexPositions: WebGLBuffer; + readonly bVertexInfo: WebGLBuffer; + readonly coverInteriorIndices: WebGLBuffer; + readonly coverCurveIndices: WebGLBuffer; + readonly edgeUpperLineIndices: WebGLBuffer; + readonly edgeUpperCurveIndices: WebGLBuffer; + readonly edgeLowerLineIndices: WebGLBuffer; + readonly edgeLowerCurveIndices: WebGLBuffer; } class AppController { @@ -105,21 +204,21 @@ class AppController { body: JSON.stringify(request), }).then((response) => { response.text().then((encodedMeshes) => { - this.meshes = new PathfinderMeshes(encodedMeshes); + this.meshes = new PathfinderMeshData(encodedMeshes); this.meshesReceived(); }); }); } meshesReceived() { - // TODO(pcwalton) + this.view.attachMeshes(this.meshes); } view: PathfinderView; loadFontButton: HTMLInputElement; fontData: ArrayBuffer; font: any; - meshes: PathfinderMeshes; + meshes: PathfinderMeshData; } class PathfinderView { @@ -137,18 +236,15 @@ class PathfinderView { 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(); + this.gl.getExtension('OES_element_index_uint'); } - loadShaders(): Promise { - let shaders: ShaderMap = {}; + loadShaders(): Promise> { + let shaders: Partial>> = {}; return window.fetch(COMMON_SHADER_URL) .then((response) => response.text()) .then((commonSource) => { - const shaderKeys = Object.keys(SHADER_URLS); + const shaderKeys = Object.keys(SHADER_URLS) as Array>; let promises = []; for (const shaderKey of shaderKeys) { @@ -173,53 +269,150 @@ class PathfinderView { `"${shaderKey}":\n${infoLog}`); } - if (!(shaderKey in shaders)) + if (shaders[shaderKey] == null) shaders[shaderKey] = {}; - shaders[shaderKey][type] = shader; + shaders[shaderKey]![typeName] = shader; })); } } return Promise.all(promises); - }).then(() => shaders); + }).then(() => shaders as ShaderMap); } - linkShaders(shaders: ShaderMap): Promise { + linkShaders(shaders: ShaderMap): + Promise> { return new Promise((resolve, reject) => { - let shaderProgramMap: ShaderProgramMap = {}; - for (const shaderKey of Object.keys(shaders)) { - const program = expect(this.gl.createProgram(), "Failed to create shader program!"); - const compiledShaders = shaders[shaderKey]; - for (const compiledShader of Object.values(compiledShaders)) - this.gl.attachShader(program, compiledShader); - this.gl.linkProgram(program); - - if (this.gl.getProgramParameter(program, this.gl.LINK_STATUS) == 0) { - const infoLog = this.gl.getProgramInfoLog(program); - throw new PathfinderError(`Failed to link program "${program}":\n${infoLog}`); - } - - shaderProgramMap[shaderKey] = program; + let shaderProgramMap: Partial> = {}; + for (const shaderName of Object.keys(shaders) as + Array>) { + shaderProgramMap[shaderName] = new PathfinderShaderProgram(this.gl, + shaderName, + shaders[shaderName]); } - resolve(shaderProgramMap); + resolve(shaderProgramMap as ShaderMap); }); } + attachMeshes(meshes: PathfinderMeshData) { + this.meshes = new PathfinderMeshBuffers(this.gl, meshes); + this.setDirty(); + } + + setDirty() { + if (this.dirty) + return; + this.dirty = true; + window.requestAnimationFrame(() => this.redraw()); + } + resizeToFit() { - this.canvas.width = window.innerWidth; - this.canvas.height = window.innerHeight - this.canvas.scrollTop; + const width = window.innerWidth, height = window.innerHeight - this.canvas.scrollTop; + const devicePixelRatio = window.devicePixelRatio; + this.canvas.style.width = width + 'px'; + this.canvas.style.height = height + 'px'; + this.canvas.width = width * devicePixelRatio; + this.canvas.height = height * devicePixelRatio; + this.setDirty(); + } + + redraw() { + this.shaderProgramsPromise.then((shaderPrograms: ShaderMap) => { + if (this.meshes == null) { + this.dirty = false; + return; + } + + // Clear. + this.gl.clearColor(1.0, 1.0, 1.0, 1.0); + this.gl.clearDepth(0.0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); + + // Set up the depth buffer. + this.gl.depthFunc(this.gl.GREATER); + this.gl.enable(this.gl.DEPTH_TEST); + + // Set up the implicit cover interior VAO. + const directInteriorProgram = shaderPrograms.directInterior; + this.gl.useProgram(directInteriorProgram.program); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.meshes.bVertexPositions); + this.gl.vertexAttribPointer(directInteriorProgram.attributes.aPosition, + 2, + this.gl.FLOAT, + false, + 0, + 0); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.meshes.bVertexInfo); + this.gl.vertexAttribPointer(directInteriorProgram.attributes.aPathDepth, + 1, + this.gl.UNSIGNED_SHORT, // FIXME(pcwalton) + true, + B_VERTEX_QUAD_SIZE, + B_VERTEX_QUAD_PATH_ID_OFFSET); + this.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPosition); + this.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPathDepth); + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.meshes.coverInteriorIndices); + + // Draw direct interior parts. + this.gl.uniformMatrix4fv(directInteriorProgram.uniforms.uTransform, false, IDENTITY); + this.gl.uniform2i(directInteriorProgram.uniforms.uFramebufferSize, + this.canvas.width, + this.canvas.height); + const triangleCount = + this.gl.getBufferParameter(this.gl.ELEMENT_ARRAY_BUFFER, this.gl.BUFFER_SIZE) / + UINT32_SIZE; + this.gl.drawElements(this.gl.TRIANGLES, triangleCount, this.gl.UNSIGNED_INT, 0); + + // Clear dirty bit and finish. + this.dirty = false; + }); } canvas: HTMLCanvasElement; gl: WebGLRenderingContext; - shaderProgramsPromise: Promise; + shaderProgramsPromise: Promise>; + meshes: PathfinderMeshBuffers; + dirty: boolean; } class PathfinderShaderProgram { - constructor(vertexShader: WebGLShader, fragmentShader: WebGLShader) { - // TODO(pcwalton) + constructor(gl: WebGLRenderingContext, + programName: string, + unlinkedShaderProgram: UnlinkedShaderProgram) { + this.program = expect(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); + + let uniforms: UniformMap = {}; + let attributes: AttributeMap = {}; + + for (let uniformIndex = 0; uniformIndex < uniformCount; uniformIndex++) { + const uniformName = unwrap(gl.getActiveUniform(this.program, uniformIndex)).name; + uniforms[uniformName] = expect(gl.getUniformLocation(this.program, uniformName), + `Didn't find uniform "${uniformName}"!`); + } + for (let attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) { + const attributeName = unwrap(gl.getActiveAttrib(this.program, attributeIndex)).name; + attributes[attributeName] = attributeIndex; + } + + this.uniforms = uniforms; + this.attributes = attributes; } + + readonly uniforms: UniformMap; + readonly attributes: AttributeMap; + readonly program: WebGLProgram; } function main() { diff --git a/demo/client/tsconfig.json b/demo/client/tsconfig.json index ed668865..f60a7b3f 100644 --- a/demo/client/tsconfig.json +++ b/demo/client/tsconfig.json @@ -9,8 +9,7 @@ "node_modules/@types" ], "sourceMap": true, - "strictNullChecks": true, - "noImplicitAny": true + "strict": true }, "include": [ "src" diff --git a/demo/server/src/main.rs b/demo/server/src/main.rs index 2692b1d4..b5483c89 100644 --- a/demo/server/src/main.rs +++ b/demo/server/src/main.rs @@ -57,21 +57,13 @@ impl IndexRange { } } - fn from_vector_append_operation(dest: &mut Vec, src: &[T]) -> IndexRange where T: Clone { - let start = dest.len(); - dest.extend(src.iter().cloned()); - let end = dest.len(); - IndexRange { - start: start, - end: end, - } - } - fn from_vector_append_and_serialization(dest: &mut Vec, src: &[T]) -> Result where T: Serialize { let byte_len_before = dest.len(); - try!(bincode::serialize_into(dest, src, Infinite).map_err(drop)); + for src_value in src { + try!(bincode::serialize_into(dest, src_value, Infinite).map_err(drop)) + } let byte_len_after = dest.len(); Ok(IndexRange { start: byte_len_before / mem::size_of::(), @@ -129,8 +121,10 @@ struct PartitionFontResponse { bVertexPositions: String, // Base64-encoded `bincode`-encoded `BVertexInfo`s. bVertexInfo: String, - coverInteriorIndices: Vec, - coverCurveIndices: Vec, + // Base64-encoded `u32`s. + coverInteriorIndices: String, + // Base64-encoded `u32`s. + coverCurveIndices: String, // Base64-encoded `bincode`-encoded `LineIndices` instances. edgeUpperLineIndices: String, // Base64-encoded `bincode`-encoded `CurveIndices` instances. @@ -251,12 +245,12 @@ fn partition_font(request: Json) bVertexIndices: IndexRange::from_vector_append_and_serialization(&mut b_vertex_info, path_b_vertex_info).unwrap(), - coverInteriorIndices: - IndexRange::from_vector_append_operation(&mut cover_interior_indices, - cover_indices.interior_indices), - coverCurveIndices: - IndexRange::from_vector_append_operation(&mut cover_curve_indices, - cover_indices.curve_indices), + coverInteriorIndices: IndexRange::from_vector_append_and_serialization( + &mut cover_interior_indices, + cover_indices.interior_indices).unwrap(), + coverCurveIndices: IndexRange::from_vector_append_and_serialization( + &mut cover_curve_indices, + cover_indices.curve_indices).unwrap(), edgeUpperLineIndices: IndexRange::from_vector_append_and_serialization( &mut edge_upper_line_indices, edge_indices.upper_line_indices).unwrap(), @@ -278,8 +272,8 @@ fn partition_font(request: Json) bQuads: base64::encode(&b_quads), bVertexPositions: base64::encode(&b_vertex_positions), bVertexInfo: base64::encode(&b_vertex_info), - coverInteriorIndices: cover_interior_indices, - coverCurveIndices: cover_curve_indices, + coverInteriorIndices: base64::encode(&cover_interior_indices), + coverCurveIndices: base64::encode(&cover_curve_indices), edgeUpperLineIndices: base64::encode(&edge_upper_line_indices), edgeUpperCurveIndices: base64::encode(&edge_upper_curve_indices), edgeLowerLineIndices: base64::encode(&edge_lower_line_indices), diff --git a/shaders/gles2/direct-interior.vs.glsl b/shaders/gles2/direct-interior.vs.glsl index 7417af9f..6cdd6698 100644 --- a/shaders/gles2/direct-interior.vs.glsl +++ b/shaders/gles2/direct-interior.vs.glsl @@ -6,7 +6,6 @@ precision highp float; uniform mat4 uTransform; uniform ivec2 uFramebufferSize; -uniform int uMaxTextureSize; uniform ivec2 uPathColorsDimensions; uniform sampler2D uPathColors; @@ -20,5 +19,6 @@ void main() { position = convertScreenToClipSpace(position, uFramebufferSize); gl_Position = vec4(position, aPathDepth, 1.0); - vColor = fetchFloat4NormIndexedData(uPathColors, aPathDepth, uPathColorsDimensions); + //vColor = fetchFloat4NormIndexedData(uPathColors, aPathDepth, uPathColorsDimensions); + vColor = vec4(0.0, 0.0, 0.0, 1.0); }