Get basic polygons rendering in the demo

This commit is contained in:
Patrick Walton 2017-08-14 16:08:45 -07:00
parent 5f89f7ba50
commit f8bd405759
4 changed files with 260 additions and 74 deletions

View File

@ -3,14 +3,30 @@
const base64js = require('base64-js'); const base64js = require('base64-js');
const opentype = require('opentype.js'); const opentype = require('opentype.js');
const TEXT: string = "A"; const TEXT: string = "G";
const FONT_SIZE: number = 16.0; const FONT_SIZE: number = 16.0;
const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font"; const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font";
const COMMON_SHADER_URL: string = '/glsl/gles2/common.inc.glsl'; 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<ShaderProgramURLs> = {
directCurve: { directCurve: {
vertex: "/glsl/gles2/direct-curve.vs.glsl", vertex: "/glsl/gles2/direct-curve.vs.glsl",
fragment: "/glsl/gles2/direct-curve.fs.glsl", fragment: "/glsl/gles2/direct-curve.fs.glsl",
@ -21,16 +37,34 @@ const SHADER_URLS: ShaderURLMap = {
}, },
}; };
interface ShaderURLMap { interface UnlinkedShaderProgram {
[shaderName: string]: { 'vertex': string, 'fragment': string }; vertex: WebGLShader;
fragment: WebGLShader;
} }
interface ShaderMap { type Matrix4D = number[];
[shaderName: string]: { [shaderType: number]: WebGLShader };
interface ShaderProgramSource {
vertex: string;
fragment: string;
} }
interface ShaderProgramMap { interface ShaderProgramURLs {
[shaderProgramName: string]: PathfinderShaderProgram; vertex: string;
fragment: string;
}
interface ShaderMap<T> {
directCurve: T;
directInterior: T;
}
interface UniformMap {
[uniformName: string]: WebGLUniformLocation;
}
interface AttributeMap {
[attributeName: string]: number;
} }
type ShaderType = number; type ShaderType = number;
@ -43,26 +77,91 @@ function expect<T>(value: T | null, message: string): T {
return value; return value;
} }
function unwrap<T>(value: T | null): T {
return expect(value, "Unexpected null!");
}
class PathfinderError extends Error { class PathfinderError extends Error {
constructor(message?: string | undefined) { constructor(message?: string | undefined) {
super(message); super(message);
} }
} }
class PathfinderMeshes { interface Meshes<T> {
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<BufferType> = {
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<ArrayBuffer> {
constructor(encodedResponse: string) { constructor(encodedResponse: string) {
const response = JSON.parse(encodedResponse); const response = JSON.parse(encodedResponse);
if (!('Ok' in response)) if (!('Ok' in response))
throw new PathfinderError("Failed to partition the font!"); throw new PathfinderError("Failed to partition the font!");
const meshes = response.Ok; const meshes = response.Ok;
this.bQuadPositions = base64js.toByteArray(meshes.bQuadPositions); for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof PathfinderMeshData>)
this.bQuadInfo = base64js.toByteArray(meshes.bQuadInfo); this[bufferName] = base64js.toByteArray(meshes[bufferName]).buffer;
this.bVertices = base64js.toByteArray(meshes.bVertices);
} }
bQuadPositions: ArrayBuffer; readonly bQuads: ArrayBuffer;
bQuadInfo: ArrayBuffer; readonly bVertexPositions: ArrayBuffer;
bVertices: 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<WebGLBuffer> {
constructor(gl: WebGLRenderingContext, meshData: PathfinderMeshData) {
for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof PathfinderMeshBuffers>) {
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 { class AppController {
@ -105,21 +204,21 @@ class AppController {
body: JSON.stringify(request), body: JSON.stringify(request),
}).then((response) => { }).then((response) => {
response.text().then((encodedMeshes) => { response.text().then((encodedMeshes) => {
this.meshes = new PathfinderMeshes(encodedMeshes); this.meshes = new PathfinderMeshData(encodedMeshes);
this.meshesReceived(); this.meshesReceived();
}); });
}); });
} }
meshesReceived() { meshesReceived() {
// TODO(pcwalton) this.view.attachMeshes(this.meshes);
} }
view: PathfinderView; view: PathfinderView;
loadFontButton: HTMLInputElement; loadFontButton: HTMLInputElement;
fontData: ArrayBuffer; fontData: ArrayBuffer;
font: any; font: any;
meshes: PathfinderMeshes; meshes: PathfinderMeshData;
} }
class PathfinderView { class PathfinderView {
@ -137,18 +236,15 @@ class PathfinderView {
initContext() { initContext() {
this.gl = expect(this.canvas.getContext('webgl', { antialias: false, depth: true }), this.gl = expect(this.canvas.getContext('webgl', { antialias: false, depth: true }),
"Failed to initialize WebGL! Check that your browser supports it."); "Failed to initialize WebGL! Check that your browser supports it.");
this.gl.getExtension('OES_element_index_uint');
this.gl.clearColor(0.0, 0.0, 1.0, 1.0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
this.gl.flush();
} }
loadShaders(): Promise<ShaderMap> { loadShaders(): Promise<ShaderMap<UnlinkedShaderProgram>> {
let shaders: ShaderMap = {}; let shaders: Partial<ShaderMap<Partial<UnlinkedShaderProgram>>> = {};
return window.fetch(COMMON_SHADER_URL) return window.fetch(COMMON_SHADER_URL)
.then((response) => response.text()) .then((response) => response.text())
.then((commonSource) => { .then((commonSource) => {
const shaderKeys = Object.keys(SHADER_URLS); const shaderKeys = Object.keys(SHADER_URLS) as Array<keyof ShaderMap<string>>;
let promises = []; let promises = [];
for (const shaderKey of shaderKeys) { for (const shaderKey of shaderKeys) {
@ -173,53 +269,150 @@ class PathfinderView {
`"${shaderKey}":\n${infoLog}`); `"${shaderKey}":\n${infoLog}`);
} }
if (!(shaderKey in shaders)) if (shaders[shaderKey] == null)
shaders[shaderKey] = {}; shaders[shaderKey] = {};
shaders[shaderKey][type] = shader; shaders[shaderKey]![typeName] = shader;
})); }));
} }
} }
return Promise.all(promises); return Promise.all(promises);
}).then(() => shaders); }).then(() => shaders as ShaderMap<UnlinkedShaderProgram>);
} }
linkShaders(shaders: ShaderMap): Promise<ShaderProgramMap> { linkShaders(shaders: ShaderMap<UnlinkedShaderProgram>):
Promise<ShaderMap<PathfinderShaderProgram>> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let shaderProgramMap: ShaderProgramMap = {}; let shaderProgramMap: Partial<ShaderMap<PathfinderShaderProgram>> = {};
for (const shaderKey of Object.keys(shaders)) { for (const shaderName of Object.keys(shaders) as
const program = expect(this.gl.createProgram(), "Failed to create shader program!"); Array<keyof ShaderMap<UnlinkedShaderProgram>>) {
const compiledShaders = shaders[shaderKey]; shaderProgramMap[shaderName] = new PathfinderShaderProgram(this.gl,
for (const compiledShader of Object.values(compiledShaders)) shaderName,
this.gl.attachShader(program, compiledShader); shaders[shaderName]);
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;
} }
resolve(shaderProgramMap); resolve(shaderProgramMap as ShaderMap<PathfinderShaderProgram>);
}); });
} }
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() { resizeToFit() {
this.canvas.width = window.innerWidth; const width = window.innerWidth, height = window.innerHeight - this.canvas.scrollTop;
this.canvas.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<PathfinderShaderProgram>) => {
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; canvas: HTMLCanvasElement;
gl: WebGLRenderingContext; gl: WebGLRenderingContext;
shaderProgramsPromise: Promise<ShaderProgramMap>; shaderProgramsPromise: Promise<ShaderMap<PathfinderShaderProgram>>;
meshes: PathfinderMeshBuffers;
dirty: boolean;
} }
class PathfinderShaderProgram { class PathfinderShaderProgram {
constructor(vertexShader: WebGLShader, fragmentShader: WebGLShader) { constructor(gl: WebGLRenderingContext,
// TODO(pcwalton) 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() { function main() {

View File

@ -9,8 +9,7 @@
"node_modules/@types" "node_modules/@types"
], ],
"sourceMap": true, "sourceMap": true,
"strictNullChecks": true, "strict": true
"noImplicitAny": true
}, },
"include": [ "include": [
"src" "src"

View File

@ -57,21 +57,13 @@ impl IndexRange {
} }
} }
fn from_vector_append_operation<T>(dest: &mut Vec<T>, 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<T>(dest: &mut Vec<u8>, src: &[T]) fn from_vector_append_and_serialization<T>(dest: &mut Vec<u8>, src: &[T])
-> Result<IndexRange, ()> -> Result<IndexRange, ()>
where T: Serialize { where T: Serialize {
let byte_len_before = dest.len(); 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(); let byte_len_after = dest.len();
Ok(IndexRange { Ok(IndexRange {
start: byte_len_before / mem::size_of::<T>(), start: byte_len_before / mem::size_of::<T>(),
@ -129,8 +121,10 @@ struct PartitionFontResponse {
bVertexPositions: String, bVertexPositions: String,
// Base64-encoded `bincode`-encoded `BVertexInfo`s. // Base64-encoded `bincode`-encoded `BVertexInfo`s.
bVertexInfo: String, bVertexInfo: String,
coverInteriorIndices: Vec<u32>, // Base64-encoded `u32`s.
coverCurveIndices: Vec<u32>, coverInteriorIndices: String,
// Base64-encoded `u32`s.
coverCurveIndices: String,
// Base64-encoded `bincode`-encoded `LineIndices` instances. // Base64-encoded `bincode`-encoded `LineIndices` instances.
edgeUpperLineIndices: String, edgeUpperLineIndices: String,
// Base64-encoded `bincode`-encoded `CurveIndices` instances. // Base64-encoded `bincode`-encoded `CurveIndices` instances.
@ -251,12 +245,12 @@ fn partition_font(request: Json<PartitionFontRequest>)
bVertexIndices: bVertexIndices:
IndexRange::from_vector_append_and_serialization(&mut b_vertex_info, IndexRange::from_vector_append_and_serialization(&mut b_vertex_info,
path_b_vertex_info).unwrap(), path_b_vertex_info).unwrap(),
coverInteriorIndices: coverInteriorIndices: IndexRange::from_vector_append_and_serialization(
IndexRange::from_vector_append_operation(&mut cover_interior_indices, &mut cover_interior_indices,
cover_indices.interior_indices), cover_indices.interior_indices).unwrap(),
coverCurveIndices: coverCurveIndices: IndexRange::from_vector_append_and_serialization(
IndexRange::from_vector_append_operation(&mut cover_curve_indices, &mut cover_curve_indices,
cover_indices.curve_indices), cover_indices.curve_indices).unwrap(),
edgeUpperLineIndices: IndexRange::from_vector_append_and_serialization( edgeUpperLineIndices: IndexRange::from_vector_append_and_serialization(
&mut edge_upper_line_indices, &mut edge_upper_line_indices,
edge_indices.upper_line_indices).unwrap(), edge_indices.upper_line_indices).unwrap(),
@ -278,8 +272,8 @@ fn partition_font(request: Json<PartitionFontRequest>)
bQuads: base64::encode(&b_quads), bQuads: base64::encode(&b_quads),
bVertexPositions: base64::encode(&b_vertex_positions), bVertexPositions: base64::encode(&b_vertex_positions),
bVertexInfo: base64::encode(&b_vertex_info), bVertexInfo: base64::encode(&b_vertex_info),
coverInteriorIndices: cover_interior_indices, coverInteriorIndices: base64::encode(&cover_interior_indices),
coverCurveIndices: cover_curve_indices, coverCurveIndices: base64::encode(&cover_curve_indices),
edgeUpperLineIndices: base64::encode(&edge_upper_line_indices), edgeUpperLineIndices: base64::encode(&edge_upper_line_indices),
edgeUpperCurveIndices: base64::encode(&edge_upper_curve_indices), edgeUpperCurveIndices: base64::encode(&edge_upper_curve_indices),
edgeLowerLineIndices: base64::encode(&edge_lower_line_indices), edgeLowerLineIndices: base64::encode(&edge_lower_line_indices),

View File

@ -6,7 +6,6 @@ precision highp float;
uniform mat4 uTransform; uniform mat4 uTransform;
uniform ivec2 uFramebufferSize; uniform ivec2 uFramebufferSize;
uniform int uMaxTextureSize;
uniform ivec2 uPathColorsDimensions; uniform ivec2 uPathColorsDimensions;
uniform sampler2D uPathColors; uniform sampler2D uPathColors;
@ -20,5 +19,6 @@ void main() {
position = convertScreenToClipSpace(position, uFramebufferSize); position = convertScreenToClipSpace(position, uFramebufferSize);
gl_Position = vec4(position, aPathDepth, 1.0); 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);
} }