2017-08-08 14:23:30 -04:00
|
|
|
// pathfinder/demo/src/index.ts
|
|
|
|
|
2017-08-10 21:38:54 -04:00
|
|
|
const base64js = require('base64-js');
|
2017-08-08 14:23:30 -04:00
|
|
|
const opentype = require('opentype.js');
|
|
|
|
|
2017-08-18 18:17:23 -04:00
|
|
|
const TEXT: string = "X";
|
2017-08-10 21:38:54 -04:00
|
|
|
const FONT_SIZE: number = 16.0;
|
|
|
|
|
|
|
|
const PARTITION_FONT_ENDPOINT_URL: string = "/partition-font";
|
|
|
|
|
2017-08-13 16:39:51 -04:00
|
|
|
const COMMON_SHADER_URL: string = '/glsl/gles2/common.inc.glsl';
|
|
|
|
|
2017-08-14 19:08:45 -04:00
|
|
|
const UINT32_SIZE: number = 4;
|
|
|
|
|
|
|
|
const B_POSITION_SIZE: number = 8;
|
|
|
|
|
2017-08-17 15:47:50 -04:00
|
|
|
const B_PATH_INDEX_SIZE: number = 2;
|
|
|
|
|
|
|
|
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;
|
2017-08-14 19:08:45 -04:00
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
const B_QUAD_SIZE: number = 4 * 8;
|
|
|
|
const B_QUAD_UPPER_INDICES_OFFSET: number = 0;
|
|
|
|
const B_QUAD_LOWER_INDICES_OFFSET: number = 4 * 4;
|
|
|
|
|
2017-08-14 19:08:45 -04:00
|
|
|
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> = {
|
2017-08-15 18:57:52 -04:00
|
|
|
blit: {
|
|
|
|
vertex: "/glsl/gles2/blit.vs.glsl",
|
|
|
|
fragment: "/glsl/gles2/blit.fs.glsl",
|
|
|
|
},
|
2017-08-12 00:26:25 -04:00
|
|
|
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",
|
|
|
|
},
|
2017-08-15 20:28:07 -04:00
|
|
|
ecaaEdgeDetect: {
|
|
|
|
vertex: "/glsl/gles2/ecaa-edge-detect.vs.glsl",
|
|
|
|
fragment: "/glsl/gles2/ecaa-edge-detect.fs.glsl",
|
|
|
|
},
|
2017-08-16 22:51:13 -04:00
|
|
|
ecaaCover: {
|
|
|
|
vertex: "/glsl/gles2/ecaa-cover.vs.glsl",
|
|
|
|
fragment: "/glsl/gles2/ecaa-cover.fs.glsl",
|
|
|
|
},
|
2017-08-18 14:44:17 -04:00
|
|
|
ecaaLine: {
|
|
|
|
vertex: "/glsl/gles2/ecaa-line.vs.glsl",
|
|
|
|
fragment: "/glsl/gles2/ecaa-line.fs.glsl",
|
|
|
|
},
|
|
|
|
ecaaResolve: {
|
|
|
|
vertex: "/glsl/gles2/ecaa-resolve.vs.glsl",
|
|
|
|
fragment: "/glsl/gles2/ecaa-resolve.fs.glsl",
|
|
|
|
},
|
2017-08-12 00:26:25 -04:00
|
|
|
};
|
|
|
|
|
2017-08-14 19:08:45 -04:00
|
|
|
interface UnlinkedShaderProgram {
|
|
|
|
vertex: WebGLShader;
|
|
|
|
fragment: WebGLShader;
|
|
|
|
}
|
|
|
|
|
|
|
|
type Matrix4D = number[];
|
|
|
|
|
2017-08-15 00:24:58 -04:00
|
|
|
interface Size2D {
|
|
|
|
width: number;
|
|
|
|
height: number;
|
|
|
|
}
|
|
|
|
|
2017-08-14 19:08:45 -04:00
|
|
|
interface ShaderProgramSource {
|
|
|
|
vertex: string;
|
|
|
|
fragment: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ShaderProgramURLs {
|
|
|
|
vertex: string;
|
|
|
|
fragment: string;
|
2017-08-12 00:26:25 -04:00
|
|
|
}
|
|
|
|
|
2017-08-14 19:08:45 -04:00
|
|
|
interface ShaderMap<T> {
|
2017-08-15 18:57:52 -04:00
|
|
|
blit: T;
|
2017-08-14 19:08:45 -04:00
|
|
|
directCurve: T;
|
|
|
|
directInterior: T;
|
2017-08-15 20:28:07 -04:00
|
|
|
ecaaEdgeDetect: T;
|
2017-08-16 22:51:13 -04:00
|
|
|
ecaaCover: T;
|
2017-08-18 14:44:17 -04:00
|
|
|
ecaaLine: T;
|
|
|
|
ecaaResolve: T;
|
2017-08-12 00:26:25 -04:00
|
|
|
}
|
|
|
|
|
2017-08-14 19:08:45 -04:00
|
|
|
interface UniformMap {
|
|
|
|
[uniformName: string]: WebGLUniformLocation;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface AttributeMap {
|
|
|
|
[attributeName: string]: number;
|
2017-08-12 00:26:25 -04:00
|
|
|
}
|
|
|
|
|
2017-08-18 18:17:23 -04:00
|
|
|
interface UpperAndLower<T> {
|
|
|
|
upper: T;
|
|
|
|
lower: T;
|
|
|
|
}
|
|
|
|
|
2017-08-15 16:38:54 -04:00
|
|
|
interface AntialiasingStrategy {
|
|
|
|
// Prepares any OpenGL data. This is only called on startup and canvas resize.
|
2017-08-16 01:09:09 -04:00
|
|
|
init(view: PathfinderView, framebufferSize: Size2D): void;
|
2017-08-15 16:38:54 -04:00
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
// Uploads any mesh data. This is called whenever a new set of meshes is supplied.
|
|
|
|
attachMeshes(view: PathfinderView): void;
|
|
|
|
|
2017-08-15 16:38:54 -04:00
|
|
|
// Called before direct rendering.
|
|
|
|
//
|
|
|
|
// Typically, this redirects direct rendering to a framebuffer of some sort.
|
2017-08-15 18:57:52 -04:00
|
|
|
prepare(view: PathfinderView): void;
|
2017-08-15 16:38:54 -04:00
|
|
|
|
|
|
|
// Called after direct rendering.
|
|
|
|
//
|
|
|
|
// This usually performs the actual antialiasing and blits to the real framebuffer.
|
2017-08-17 23:09:18 -04:00
|
|
|
resolve(view: PathfinderView): void;
|
2017-08-15 16:38:54 -04:00
|
|
|
}
|
|
|
|
|
2017-08-12 00:26:25 -04:00
|
|
|
type ShaderType = number;
|
|
|
|
|
|
|
|
type ShaderTypeName = 'vertex' | 'fragment';
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
type WebGLVertexArrayObject = any;
|
|
|
|
|
2017-08-15 18:57:52 -04:00
|
|
|
const QUAD_POSITIONS: Float32Array = new Float32Array([
|
|
|
|
-1.0, 1.0,
|
|
|
|
1.0, 1.0,
|
|
|
|
-1.0, -1.0,
|
|
|
|
1.0, -1.0,
|
|
|
|
]);
|
|
|
|
|
|
|
|
const QUAD_TEX_COORDS: Float32Array = new Float32Array([
|
|
|
|
0.0, 1.0,
|
|
|
|
1.0, 1.0,
|
|
|
|
0.0, 0.0,
|
|
|
|
1.0, 0.0,
|
|
|
|
]);
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
const QUAD_ELEMENTS: Uint8Array = new Uint8Array([2, 0, 1, 1, 3, 2]);
|
|
|
|
|
2017-08-15 18:57:52 -04:00
|
|
|
// Various utility functions
|
|
|
|
|
|
|
|
function assert(value: boolean, message: string) {
|
|
|
|
if (!value)
|
|
|
|
throw new PathfinderError(message);
|
|
|
|
}
|
|
|
|
|
2017-08-15 16:38:54 -04:00
|
|
|
function expectNotNull<T>(value: T | null, message: string): T {
|
|
|
|
if (value === null)
|
2017-08-13 16:39:51 -04:00
|
|
|
throw new PathfinderError(message);
|
2017-08-12 00:26:25 -04:00
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2017-08-15 16:38:54 -04:00
|
|
|
function expectNotUndef<T>(value: T | undefined, message: string): T {
|
|
|
|
if (value === undefined)
|
|
|
|
throw new PathfinderError(message);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
function unwrapNull<T>(value: T | null): T {
|
|
|
|
return expectNotNull(value, "Unexpected null!");
|
|
|
|
}
|
|
|
|
|
|
|
|
function unwrapUndef<T>(value: T | undefined): T {
|
|
|
|
return expectNotUndef(value, "Unexpected `undefined`!");
|
2017-08-14 19:08:45 -04:00
|
|
|
}
|
|
|
|
|
2017-08-13 16:39:51 -04:00
|
|
|
class PathfinderError extends Error {
|
|
|
|
constructor(message?: string | undefined) {
|
|
|
|
super(message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-15 19:51:26 -04:00
|
|
|
// GL utilities
|
|
|
|
|
2017-08-16 19:37:39 -04:00
|
|
|
function createFramebufferColorTexture(gl: WebGLRenderingContext, size: Size2D): 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.
|
|
|
|
|
|
|
|
const texture = unwrapNull(gl.createTexture());
|
|
|
|
gl.activeTexture(gl.TEXTURE0);
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
|
|
gl.texImage2D(gl.TEXTURE_2D,
|
|
|
|
0,
|
|
|
|
gl.RGBA,
|
|
|
|
size.width,
|
|
|
|
size.height,
|
|
|
|
0,
|
|
|
|
gl.RGBA,
|
|
|
|
gl.UNSIGNED_BYTE,
|
|
|
|
new Uint8Array(size.width * size.height * 4));
|
|
|
|
setTextureParameters(gl, gl.NEAREST);
|
|
|
|
return texture;
|
|
|
|
}
|
|
|
|
|
|
|
|
function createFramebufferDepthTexture(gl: WebGLRenderingContext, size: Size2D): 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.width,
|
|
|
|
size.height,
|
|
|
|
0,
|
|
|
|
gl.DEPTH_COMPONENT,
|
|
|
|
gl.UNSIGNED_INT,
|
|
|
|
null);
|
|
|
|
setTextureParameters(gl, gl.NEAREST);
|
|
|
|
return texture;
|
|
|
|
}
|
|
|
|
|
2017-08-15 19:51:26 -04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-08-16 19:37:39 -04:00
|
|
|
function createFramebuffer(gl: WebGLRenderingContext,
|
|
|
|
drawBuffersExt: any,
|
|
|
|
colorAttachments: WebGLTexture[],
|
|
|
|
depthAttachment: WebGLTexture | null):
|
|
|
|
WebGLFramebuffer {
|
|
|
|
const framebuffer = unwrapNull(gl.createFramebuffer());
|
|
|
|
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
|
|
|
|
|
|
const colorAttachmentCount = colorAttachments.length;
|
|
|
|
for (let colorAttachmentIndex = 0;
|
|
|
|
colorAttachmentIndex < colorAttachmentCount;
|
|
|
|
colorAttachmentIndex++) {
|
|
|
|
gl.framebufferTexture2D(gl.FRAMEBUFFER,
|
|
|
|
drawBuffersExt[`COLOR_ATTACHMENT${colorAttachmentIndex}_WEBGL`],
|
|
|
|
gl.TEXTURE_2D,
|
|
|
|
colorAttachments[colorAttachmentIndex],
|
|
|
|
0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (depthAttachment != null) {
|
|
|
|
gl.framebufferTexture2D(gl.FRAMEBUFFER,
|
|
|
|
gl.DEPTH_ATTACHMENT,
|
|
|
|
gl.TEXTURE_2D,
|
|
|
|
depthAttachment,
|
|
|
|
0);
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE,
|
|
|
|
"Framebuffer was incomplete!");
|
|
|
|
return framebuffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
function initQuadVAO(view: PathfinderView, attributes: any) {
|
|
|
|
view.gl.bindBuffer(view.gl.ARRAY_BUFFER, view.quadPositionsBuffer);
|
|
|
|
view.gl.vertexAttribPointer(attributes.aPosition, 2, view.gl.FLOAT, false, 0, 0);
|
|
|
|
view.gl.bindBuffer(view.gl.ARRAY_BUFFER, view.quadTexCoordsBuffer);
|
|
|
|
view.gl.vertexAttribPointer(attributes.aTexCoord, 2, view.gl.FLOAT, false, 0, 0);
|
|
|
|
view.gl.enableVertexAttribArray(attributes.aPosition);
|
|
|
|
view.gl.enableVertexAttribArray(attributes.aTexCoord);
|
2017-08-17 23:09:18 -04:00
|
|
|
view.gl.bindBuffer(view.gl.ELEMENT_ARRAY_BUFFER, view.quadElementsBuffer);
|
2017-08-16 19:37:39 -04:00
|
|
|
}
|
|
|
|
|
2017-08-14 19:08:45 -04:00
|
|
|
interface Meshes<T> {
|
|
|
|
readonly bQuads: T;
|
|
|
|
readonly bVertexPositions: T;
|
2017-08-17 15:47:50 -04:00
|
|
|
readonly bVertexPathIDs: T;
|
|
|
|
readonly bVertexLoopBlinnData: T;
|
2017-08-14 19:08:45 -04:00
|
|
|
readonly coverInteriorIndices: T;
|
|
|
|
readonly coverCurveIndices: T;
|
|
|
|
readonly edgeUpperLineIndices: T;
|
|
|
|
readonly edgeLowerLineIndices: T;
|
2017-08-18 18:17:23 -04:00
|
|
|
readonly edgeUpperCurveIndices: T;
|
2017-08-14 19:08:45 -04:00
|
|
|
readonly edgeLowerCurveIndices: T;
|
|
|
|
}
|
|
|
|
|
2017-08-17 15:47:50 -04:00
|
|
|
type BufferType = 'ARRAY_BUFFER' | 'ELEMENT_ARRAY_BUFFER';
|
2017-08-14 19:08:45 -04:00
|
|
|
|
|
|
|
const BUFFER_TYPES: Meshes<BufferType> = {
|
2017-08-17 15:47:50 -04:00
|
|
|
bQuads: 'ARRAY_BUFFER',
|
|
|
|
bVertexPositions: 'ARRAY_BUFFER',
|
|
|
|
bVertexPathIDs: 'ARRAY_BUFFER',
|
|
|
|
bVertexLoopBlinnData: 'ARRAY_BUFFER',
|
|
|
|
coverInteriorIndices: 'ELEMENT_ARRAY_BUFFER',
|
|
|
|
coverCurveIndices: 'ELEMENT_ARRAY_BUFFER',
|
2017-08-18 18:17:23 -04:00
|
|
|
edgeUpperLineIndices: 'ARRAY_BUFFER',
|
|
|
|
edgeLowerLineIndices: 'ARRAY_BUFFER',
|
|
|
|
edgeUpperCurveIndices: 'ARRAY_BUFFER',
|
|
|
|
edgeLowerCurveIndices: 'ARRAY_BUFFER',
|
2017-08-14 19:08:45 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
class PathfinderMeshData implements Meshes<ArrayBuffer> {
|
2017-08-10 21:38:54 -04:00
|
|
|
constructor(encodedResponse: string) {
|
|
|
|
const response = JSON.parse(encodedResponse);
|
|
|
|
if (!('Ok' in response))
|
2017-08-13 16:39:51 -04:00
|
|
|
throw new PathfinderError("Failed to partition the font!");
|
2017-08-10 21:38:54 -04:00
|
|
|
const meshes = response.Ok;
|
2017-08-18 18:17:23 -04:00
|
|
|
for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof Meshes<void>>)
|
2017-08-14 19:08:45 -04:00
|
|
|
this[bufferName] = base64js.toByteArray(meshes[bufferName]).buffer;
|
2017-08-17 23:09:18 -04:00
|
|
|
|
|
|
|
this.bQuadCount = this.bQuads.byteLength / B_QUAD_SIZE;
|
2017-08-18 18:17:23 -04:00
|
|
|
this.edgeUpperLineIndexCount = this.edgeUpperLineIndices.byteLength / 8;
|
|
|
|
this.edgeLowerLineIndexCount = this.edgeLowerLineIndices.byteLength / 8;
|
|
|
|
this.edgeUpperCurveIndexCount = this.edgeUpperCurveIndices.byteLength / 16;
|
|
|
|
this.edgeLowerCurveIndexCount = this.edgeLowerCurveIndices.byteLength / 16;
|
2017-08-08 14:23:30 -04:00
|
|
|
}
|
|
|
|
|
2017-08-14 19:08:45 -04:00
|
|
|
readonly bQuads: ArrayBuffer;
|
|
|
|
readonly bVertexPositions: ArrayBuffer;
|
2017-08-17 15:47:50 -04:00
|
|
|
readonly bVertexPathIDs: ArrayBuffer;
|
|
|
|
readonly bVertexLoopBlinnData: ArrayBuffer;
|
2017-08-14 19:08:45 -04:00
|
|
|
readonly coverInteriorIndices: ArrayBuffer;
|
|
|
|
readonly coverCurveIndices: ArrayBuffer;
|
|
|
|
readonly edgeUpperLineIndices: ArrayBuffer;
|
|
|
|
readonly edgeLowerLineIndices: ArrayBuffer;
|
2017-08-18 18:17:23 -04:00
|
|
|
readonly edgeUpperCurveIndices: ArrayBuffer;
|
2017-08-14 19:08:45 -04:00
|
|
|
readonly edgeLowerCurveIndices: ArrayBuffer;
|
2017-08-17 23:09:18 -04:00
|
|
|
|
|
|
|
readonly bQuadCount: number;
|
2017-08-18 18:17:23 -04:00
|
|
|
readonly edgeUpperLineIndexCount: number;
|
|
|
|
readonly edgeLowerLineIndexCount: number;
|
|
|
|
readonly edgeUpperCurveIndexCount: number;
|
|
|
|
readonly edgeLowerCurveIndexCount: number;
|
2017-08-14 19:08:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
class PathfinderMeshBuffers implements Meshes<WebGLBuffer> {
|
|
|
|
constructor(gl: WebGLRenderingContext, meshData: PathfinderMeshData) {
|
|
|
|
for (const bufferName of Object.keys(BUFFER_TYPES) as Array<keyof PathfinderMeshBuffers>) {
|
2017-08-17 15:47:50 -04:00
|
|
|
const bufferType = gl[BUFFER_TYPES[bufferName]];
|
2017-08-15 16:38:54 -04:00
|
|
|
const buffer = expectNotNull(gl.createBuffer(), "Failed to create buffer!");
|
2017-08-14 19:08:45 -04:00
|
|
|
gl.bindBuffer(bufferType, buffer);
|
|
|
|
gl.bufferData(bufferType, meshData[bufferName], gl.STATIC_DRAW);
|
|
|
|
this[bufferName] = buffer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
readonly bQuads: WebGLBuffer;
|
|
|
|
readonly bVertexPositions: WebGLBuffer;
|
2017-08-17 15:47:50 -04:00
|
|
|
readonly bVertexPathIDs: WebGLBuffer;
|
|
|
|
readonly bVertexLoopBlinnData: WebGLBuffer;
|
2017-08-14 19:08:45 -04:00
|
|
|
readonly coverInteriorIndices: WebGLBuffer;
|
|
|
|
readonly coverCurveIndices: WebGLBuffer;
|
|
|
|
readonly edgeUpperLineIndices: WebGLBuffer;
|
|
|
|
readonly edgeUpperCurveIndices: WebGLBuffer;
|
|
|
|
readonly edgeLowerLineIndices: WebGLBuffer;
|
|
|
|
readonly edgeLowerCurveIndices: WebGLBuffer;
|
2017-08-10 21:38:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
class AppController {
|
|
|
|
constructor() {}
|
|
|
|
|
2017-08-08 14:23:30 -04:00
|
|
|
start() {
|
2017-08-17 23:09:18 -04:00
|
|
|
const canvas = document.getElementById('pf-canvas') as HTMLCanvasElement;
|
|
|
|
const shaderLoader = new PathfinderShaderLoader;
|
|
|
|
shaderLoader.load();
|
|
|
|
this.view = Promise.all([shaderLoader.common, shaderLoader.shaders]).then(allShaders => {
|
|
|
|
return new PathfinderView(canvas, allShaders[0], allShaders[1]);
|
|
|
|
});
|
2017-08-08 14:23:30 -04:00
|
|
|
|
|
|
|
this.loadFontButton = document.getElementById('pf-load-font-button') as HTMLInputElement;
|
|
|
|
this.loadFontButton.addEventListener('change', () => this.loadFont(), false);
|
2017-08-15 16:38:54 -04:00
|
|
|
|
|
|
|
this.aaLevelSelect = document.getElementById('pf-aa-level-select') as HTMLSelectElement;
|
|
|
|
this.aaLevelSelect.addEventListener('change', () => this.updateAALevel(), false);
|
|
|
|
this.updateAALevel();
|
2017-08-08 14:23:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
loadFont() {
|
2017-08-15 16:38:54 -04:00
|
|
|
const file = expectNotNull(this.loadFontButton.files, "No file selected!")[0];
|
2017-08-10 21:38:54 -04:00
|
|
|
const reader = new FileReader;
|
|
|
|
reader.addEventListener('loadend', () => {
|
|
|
|
this.fontData = reader.result;
|
|
|
|
this.fontLoaded();
|
|
|
|
}, false);
|
|
|
|
reader.readAsArrayBuffer(file);
|
|
|
|
}
|
|
|
|
|
2017-08-15 16:38:54 -04:00
|
|
|
updateAALevel() {
|
|
|
|
const selectedOption = this.aaLevelSelect.selectedOptions[0];
|
2017-08-15 18:57:52 -04:00
|
|
|
const aaType = unwrapUndef(selectedOption.dataset.pfType) as
|
|
|
|
keyof AntialiasingStrategyTable;
|
2017-08-15 16:38:54 -04:00
|
|
|
const aaLevel = parseInt(unwrapUndef(selectedOption.dataset.pfLevel));
|
2017-08-17 23:09:18 -04:00
|
|
|
this.view.then(view => view.setAntialiasingOptions(aaType, aaLevel));
|
2017-08-15 16:38:54 -04:00
|
|
|
}
|
|
|
|
|
2017-08-10 21:38:54 -04:00
|
|
|
fontLoaded() {
|
|
|
|
this.font = opentype.parse(this.fontData);
|
2017-08-12 00:26:25 -04:00
|
|
|
if (!this.font.supported)
|
2017-08-13 16:39:51 -04:00
|
|
|
throw new PathfinderError("The font type is unsupported.");
|
2017-08-10 21:38:54 -04:00
|
|
|
|
2017-08-12 00:26:25 -04:00
|
|
|
const glyphIDs = this.font.stringToGlyphs(TEXT).map((glyph: any) => glyph.index);
|
2017-08-10 21:38:54 -04:00
|
|
|
|
|
|
|
const request = {
|
|
|
|
otf: base64js.fromByteArray(new Uint8Array(this.fontData)),
|
|
|
|
fontIndex: 0,
|
|
|
|
glyphIDs: glyphIDs,
|
|
|
|
pointSize: FONT_SIZE,
|
|
|
|
};
|
|
|
|
|
2017-08-12 13:08:35 -04:00
|
|
|
window.fetch(PARTITION_FONT_ENDPOINT_URL, {
|
|
|
|
method: 'POST',
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
body: JSON.stringify(request),
|
2017-08-15 00:24:58 -04:00
|
|
|
}).then(response => response.text()).then(encodedMeshes => {
|
|
|
|
this.meshes = new PathfinderMeshData(encodedMeshes);
|
|
|
|
this.meshesReceived();
|
2017-08-12 13:08:35 -04:00
|
|
|
});
|
2017-08-08 14:23:30 -04:00
|
|
|
}
|
|
|
|
|
2017-08-10 21:38:54 -04:00
|
|
|
meshesReceived() {
|
2017-08-17 23:09:18 -04:00
|
|
|
this.view.then(view => {
|
|
|
|
view.uploadPathData(TEXT.length);
|
|
|
|
view.attachMeshes(this.meshes);
|
|
|
|
})
|
2017-08-08 14:23:30 -04:00
|
|
|
}
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
view: Promise<PathfinderView>;
|
2017-08-08 14:23:30 -04:00
|
|
|
loadFontButton: HTMLInputElement;
|
2017-08-15 16:38:54 -04:00
|
|
|
aaLevelSelect: HTMLSelectElement;
|
2017-08-10 21:38:54 -04:00
|
|
|
fontData: ArrayBuffer;
|
|
|
|
font: any;
|
2017-08-14 19:08:45 -04:00
|
|
|
meshes: PathfinderMeshData;
|
2017-08-08 14:23:30 -04:00
|
|
|
}
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
class PathfinderShaderLoader {
|
|
|
|
load() {
|
|
|
|
this.common = window.fetch(COMMON_SHADER_URL).then(response => response.text());
|
|
|
|
|
|
|
|
const shaderKeys = Object.keys(SHADER_URLS) as Array<keyof ShaderMap<string>>;
|
|
|
|
let 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 => { return { vertex: results[0], fragment: results[1] } }));
|
|
|
|
}
|
|
|
|
|
|
|
|
this.shaders = Promise.all(promises).then(promises => {
|
|
|
|
let shaderMap: Partial<ShaderMap<ShaderProgramSource>> = {};
|
|
|
|
for (let keyIndex = 0; keyIndex < shaderKeys.length; keyIndex++)
|
|
|
|
shaderMap[shaderKeys[keyIndex]] = promises[keyIndex];
|
|
|
|
return shaderMap as ShaderMap<ShaderProgramSource>;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
common: Promise<string>;
|
|
|
|
shaders: Promise<ShaderMap<ShaderProgramSource>>;
|
|
|
|
}
|
|
|
|
|
2017-08-08 14:23:30 -04:00
|
|
|
class PathfinderView {
|
2017-08-17 23:09:18 -04:00
|
|
|
constructor(canvas: HTMLCanvasElement,
|
|
|
|
commonShaderSource: string,
|
|
|
|
shaderSources: ShaderMap<ShaderProgramSource>) {
|
2017-08-08 14:23:30 -04:00
|
|
|
this.canvas = canvas;
|
2017-08-12 00:26:25 -04:00
|
|
|
|
|
|
|
this.initContext();
|
|
|
|
|
2017-08-15 16:38:54 -04:00
|
|
|
this.antialiasingStrategy = new NoAAStrategy(0);
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
const shaderSource = this.compileShaders(commonShaderSource, shaderSources);
|
|
|
|
this.shaderPrograms = this.linkShaders(shaderSource);
|
2017-08-12 00:26:25 -04:00
|
|
|
|
|
|
|
window.addEventListener('resize', () => this.resizeToFit(), false);
|
|
|
|
this.resizeToFit();
|
|
|
|
}
|
|
|
|
|
2017-08-15 18:57:52 -04:00
|
|
|
setAntialiasingOptions(aaType: keyof AntialiasingStrategyTable, aaLevel: number) {
|
2017-08-15 16:38:54 -04:00
|
|
|
this.antialiasingStrategy = new (ANTIALIASING_STRATEGIES[aaType])(aaLevel);
|
|
|
|
|
|
|
|
let canvas = this.canvas;
|
2017-08-16 01:09:09 -04:00
|
|
|
this.antialiasingStrategy.init(this, { width: canvas.width, height: canvas.height });
|
2017-08-17 23:09:18 -04:00
|
|
|
if (this.meshData != null)
|
|
|
|
this.antialiasingStrategy.attachMeshes(this);
|
2017-08-15 18:57:52 -04:00
|
|
|
|
|
|
|
this.setDirty();
|
2017-08-15 16:38:54 -04:00
|
|
|
}
|
|
|
|
|
2017-08-12 00:26:25 -04:00
|
|
|
initContext() {
|
2017-08-15 18:57:52 -04:00
|
|
|
// Initialize the OpenGL context.
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl = expectNotNull(this.canvas.getContext('webgl', { antialias: false, depth: true }),
|
|
|
|
"Failed to initialize WebGL! Check that your browser supports it.");
|
2017-08-16 01:09:09 -04:00
|
|
|
this.drawBuffersExt = this.gl.getExtension('WEBGL_draw_buffers');
|
2017-08-18 01:47:28 -04:00
|
|
|
this.colorBufferHalfFloatExt = this.gl.getExtension('EXT_color_buffer_half_float');
|
2017-08-17 23:09:18 -04:00
|
|
|
this.instancedArraysExt = this.gl.getExtension('ANGLE_instanced_arrays');
|
2017-08-18 01:47:28 -04:00
|
|
|
this.textureHalfFloatExt = this.gl.getExtension('OES_texture_half_float');
|
2017-08-17 23:09:18 -04:00
|
|
|
this.vertexArrayObjectExt = this.gl.getExtension('OES_vertex_array_object');
|
2017-08-15 20:28:07 -04:00
|
|
|
this.gl.getExtension('EXT_frag_depth');
|
2017-08-14 19:08:45 -04:00
|
|
|
this.gl.getExtension('OES_element_index_uint');
|
2017-08-17 23:09:18 -04:00
|
|
|
this.gl.getExtension('OES_texture_float');
|
2017-08-15 19:51:26 -04:00
|
|
|
this.gl.getExtension('WEBGL_depth_texture');
|
2017-08-15 18:57:52 -04:00
|
|
|
|
|
|
|
// 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);
|
2017-08-17 23:09:18 -04:00
|
|
|
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);
|
2017-08-12 00:26:25 -04:00
|
|
|
}
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
compileShaders(commonSource: string, shaderSources: ShaderMap<ShaderProgramSource>):
|
|
|
|
ShaderMap<UnlinkedShaderProgram> {
|
2017-08-14 19:08:45 -04:00
|
|
|
let shaders: Partial<ShaderMap<Partial<UnlinkedShaderProgram>>> = {};
|
2017-08-17 23:09:18 -04:00
|
|
|
const shaderKeys = Object.keys(SHADER_URLS) as Array<keyof ShaderMap<string>>;
|
|
|
|
|
|
|
|
for (const shaderKey of shaderKeys) {
|
|
|
|
for (const typeName of ['vertex', 'fragment'] as Array<ShaderTypeName>) {
|
|
|
|
const type = {
|
|
|
|
vertex: this.gl.VERTEX_SHADER,
|
|
|
|
fragment: this.gl.FRAGMENT_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) == 0) {
|
|
|
|
const infoLog = this.gl.getShaderInfoLog(shader);
|
|
|
|
throw new PathfinderError(`Failed to compile ${typeName} shader ` +
|
|
|
|
`"${shaderKey}":\n${infoLog}`);
|
2017-08-13 16:39:51 -04:00
|
|
|
}
|
2017-08-17 23:09:18 -04:00
|
|
|
|
|
|
|
if (shaders[shaderKey] == null)
|
|
|
|
shaders[shaderKey] = {};
|
|
|
|
shaders[shaderKey]![typeName] = shader;
|
2017-08-12 00:26:25 -04:00
|
|
|
}
|
2017-08-17 23:09:18 -04:00
|
|
|
}
|
2017-08-12 13:08:35 -04:00
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
return shaders as ShaderMap<UnlinkedShaderProgram>;
|
2017-08-12 00:26:25 -04:00
|
|
|
}
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
linkShaders(shaders: ShaderMap<UnlinkedShaderProgram>): ShaderMap<PathfinderShaderProgram> {
|
|
|
|
let shaderProgramMap: Partial<ShaderMap<PathfinderShaderProgram>> = {};
|
|
|
|
for (const shaderName of Object.keys(shaders) as Array<keyof ShaderMap<string>>) {
|
|
|
|
shaderProgramMap[shaderName] = new PathfinderShaderProgram(this.gl,
|
|
|
|
shaderName,
|
|
|
|
shaders[shaderName]);
|
|
|
|
}
|
|
|
|
return shaderProgramMap as ShaderMap<PathfinderShaderProgram>;
|
2017-08-12 00:26:25 -04:00
|
|
|
}
|
|
|
|
|
2017-08-15 00:24:58 -04:00
|
|
|
uploadPathData(pathCount: number) {
|
|
|
|
const pathColors = new Uint8Array(4 * pathCount);
|
|
|
|
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
|
|
|
|
for (let channel = 0; channel < 3; channel++)
|
|
|
|
pathColors[pathIndex * 4 + channel] = 0x00; // RGB
|
|
|
|
pathColors[pathIndex * 4 + 3] = 0xff; // alpha
|
|
|
|
}
|
|
|
|
|
|
|
|
this.pathColorsBufferTexture = new PathfinderBufferTexture(this.gl, pathColors);
|
|
|
|
}
|
|
|
|
|
2017-08-14 19:08:45 -04:00
|
|
|
attachMeshes(meshes: PathfinderMeshData) {
|
2017-08-17 23:09:18 -04:00
|
|
|
this.meshData = meshes;
|
2017-08-14 19:08:45 -04:00
|
|
|
this.meshes = new PathfinderMeshBuffers(this.gl, meshes);
|
2017-08-17 23:09:18 -04:00
|
|
|
this.antialiasingStrategy.attachMeshes(this);
|
2017-08-14 19:08:45 -04:00
|
|
|
this.setDirty();
|
|
|
|
}
|
|
|
|
|
|
|
|
setDirty() {
|
|
|
|
if (this.dirty)
|
|
|
|
return;
|
|
|
|
this.dirty = true;
|
|
|
|
window.requestAnimationFrame(() => this.redraw());
|
|
|
|
}
|
|
|
|
|
2017-08-12 00:26:25 -04:00
|
|
|
resizeToFit() {
|
2017-08-14 22:08:18 -04:00
|
|
|
const width = window.innerWidth;
|
|
|
|
const height = window.scrollY + window.innerHeight -
|
|
|
|
this.canvas.getBoundingClientRect().top;
|
2017-08-14 19:08:45 -04:00
|
|
|
const devicePixelRatio = window.devicePixelRatio;
|
2017-08-15 16:38:54 -04:00
|
|
|
|
|
|
|
const framebufferSize = {
|
|
|
|
width: width * devicePixelRatio,
|
|
|
|
height: height * devicePixelRatio,
|
|
|
|
};
|
|
|
|
|
2017-08-14 19:08:45 -04:00
|
|
|
this.canvas.style.width = width + 'px';
|
|
|
|
this.canvas.style.height = height + 'px';
|
2017-08-15 16:38:54 -04:00
|
|
|
this.canvas.width = framebufferSize.width;
|
|
|
|
this.canvas.height = framebufferSize.height;
|
|
|
|
|
2017-08-16 01:09:09 -04:00
|
|
|
this.antialiasingStrategy.init(this, framebufferSize);
|
2017-08-15 16:38:54 -04:00
|
|
|
|
2017-08-14 19:08:45 -04:00
|
|
|
this.setDirty();
|
|
|
|
}
|
|
|
|
|
|
|
|
redraw() {
|
2017-08-17 23:09:18 -04:00
|
|
|
if (this.meshes == null) {
|
|
|
|
this.dirty = false;
|
|
|
|
return;
|
|
|
|
}
|
2017-08-14 19:08:45 -04:00
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
// Prepare for direct rendering.
|
|
|
|
this.antialiasingStrategy.prepare(this);
|
2017-08-15 18:57:52 -04:00
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
// Perform direct rendering (Loop-Blinn).
|
|
|
|
this.renderDirect();
|
2017-08-15 16:38:54 -04:00
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
// Antialias.
|
|
|
|
this.antialiasingStrategy.resolve(this);
|
2017-08-14 19:08:45 -04:00
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
// Clear dirty bit and finish.
|
|
|
|
this.dirty = false;
|
2017-08-08 14:23:30 -04:00
|
|
|
}
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
renderDirect() {
|
2017-08-16 19:37:39 -04:00
|
|
|
// Set up implicit cover state.
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl.depthFunc(this.gl.GREATER);
|
|
|
|
this.gl.depthMask(true);
|
|
|
|
this.gl.enable(this.gl.DEPTH_TEST);
|
2017-08-16 19:37:39 -04:00
|
|
|
this.gl.disable(this.gl.BLEND);
|
2017-08-15 16:38:54 -04:00
|
|
|
|
|
|
|
// Set up the implicit cover interior VAO.
|
2017-08-17 23:09:18 -04:00
|
|
|
const directInteriorProgram = this.shaderPrograms.directInterior;
|
2017-08-15 16:38:54 -04:00
|
|
|
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);
|
2017-08-17 15:47:50 -04:00
|
|
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.meshes.bVertexPathIDs);
|
2017-08-16 01:09:09 -04:00
|
|
|
this.gl.vertexAttribPointer(directInteriorProgram.attributes.aPathID,
|
2017-08-15 16:38:54 -04:00
|
|
|
1,
|
2017-08-17 15:47:50 -04:00
|
|
|
this.gl.UNSIGNED_SHORT,
|
2017-08-16 01:09:09 -04:00
|
|
|
false,
|
2017-08-17 15:47:50 -04:00
|
|
|
0,
|
|
|
|
0);
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPosition);
|
2017-08-16 19:37:39 -04:00
|
|
|
this.gl.enableVertexAttribArray(directInteriorProgram.attributes.aPathID);
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.meshes.coverInteriorIndices);
|
|
|
|
|
|
|
|
// Draw direct interior parts.
|
|
|
|
this.gl.activeTexture(this.gl.TEXTURE0);
|
|
|
|
this.gl.bindTexture(this.gl.TEXTURE_2D, this.pathColorsBufferTexture.texture);
|
|
|
|
this.gl.uniformMatrix4fv(directInteriorProgram.uniforms.uTransform, false, IDENTITY);
|
|
|
|
this.gl.uniform2i(directInteriorProgram.uniforms.uFramebufferSize,
|
2017-08-15 18:57:52 -04:00
|
|
|
this.canvas.width,
|
|
|
|
this.canvas.height);
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl.uniform2i(directInteriorProgram.uniforms.uPathColorsDimensions,
|
2017-08-15 18:57:52 -04:00
|
|
|
this.pathColorsBufferTexture.size.width,
|
|
|
|
this.pathColorsBufferTexture.size.height);
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl.uniform1i(directInteriorProgram.uniforms.uPathColors, 0);
|
|
|
|
let indexCount = this.gl.getBufferParameter(this.gl.ELEMENT_ARRAY_BUFFER,
|
|
|
|
this.gl.BUFFER_SIZE) / UINT32_SIZE;
|
|
|
|
this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0);
|
|
|
|
|
2017-08-16 19:37:39 -04:00
|
|
|
// Set up direct curve state.
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl.depthMask(false);
|
2017-08-16 19:37:39 -04:00
|
|
|
this.gl.enable(this.gl.BLEND);
|
|
|
|
this.gl.blendEquation(this.gl.FUNC_ADD);
|
|
|
|
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
|
2017-08-15 16:38:54 -04:00
|
|
|
|
2017-08-16 19:37:39 -04:00
|
|
|
// Set up the direct curve VAO.
|
2017-08-17 23:09:18 -04:00
|
|
|
const directCurveProgram = this.shaderPrograms.directCurve;
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl.useProgram(directCurveProgram.program);
|
|
|
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.meshes.bVertexPositions);
|
|
|
|
this.gl.vertexAttribPointer(directCurveProgram.attributes.aPosition,
|
|
|
|
2,
|
|
|
|
this.gl.FLOAT,
|
|
|
|
false,
|
|
|
|
0,
|
|
|
|
0);
|
2017-08-17 15:47:50 -04:00
|
|
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.meshes.bVertexPathIDs);
|
|
|
|
this.gl.vertexAttribPointer(directCurveProgram.attributes.aPathID,
|
|
|
|
1,
|
|
|
|
this.gl.UNSIGNED_SHORT,
|
|
|
|
false,
|
|
|
|
0,
|
|
|
|
0);
|
|
|
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.meshes.bVertexLoopBlinnData);
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl.vertexAttribPointer(directCurveProgram.attributes.aTexCoord,
|
|
|
|
2,
|
|
|
|
this.gl.UNSIGNED_BYTE,
|
|
|
|
false,
|
2017-08-17 15:47:50 -04:00
|
|
|
B_LOOP_BLINN_DATA_SIZE,
|
|
|
|
B_LOOP_BLINN_DATA_TEX_COORD_OFFSET);
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl.vertexAttribPointer(directCurveProgram.attributes.aSign,
|
|
|
|
1,
|
|
|
|
this.gl.BYTE,
|
|
|
|
false,
|
2017-08-17 15:47:50 -04:00
|
|
|
B_LOOP_BLINN_DATA_SIZE,
|
|
|
|
B_LOOP_BLINN_DATA_SIGN_OFFSET);
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl.enableVertexAttribArray(directCurveProgram.attributes.aPosition);
|
|
|
|
this.gl.enableVertexAttribArray(directCurveProgram.attributes.aTexCoord);
|
2017-08-16 19:37:39 -04:00
|
|
|
this.gl.enableVertexAttribArray(directCurveProgram.attributes.aPathID);
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl.enableVertexAttribArray(directCurveProgram.attributes.aSign);
|
|
|
|
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.meshes.coverCurveIndices);
|
|
|
|
|
|
|
|
// Draw direct curve parts.
|
|
|
|
this.gl.activeTexture(this.gl.TEXTURE0);
|
|
|
|
this.gl.bindTexture(this.gl.TEXTURE_2D, this.pathColorsBufferTexture.texture);
|
|
|
|
this.gl.uniformMatrix4fv(directCurveProgram.uniforms.uTransform, false, IDENTITY);
|
|
|
|
this.gl.uniform2i(directCurveProgram.uniforms.uFramebufferSize,
|
2017-08-15 18:57:52 -04:00
|
|
|
this.canvas.width,
|
|
|
|
this.canvas.height);
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl.uniform2i(directCurveProgram.uniforms.uPathColorsDimensions,
|
2017-08-15 18:57:52 -04:00
|
|
|
this.pathColorsBufferTexture.size.width,
|
|
|
|
this.pathColorsBufferTexture.size.height);
|
2017-08-15 16:38:54 -04:00
|
|
|
this.gl.uniform1i(directCurveProgram.uniforms.uPathColors, 0);
|
|
|
|
indexCount = this.gl.getBufferParameter(this.gl.ELEMENT_ARRAY_BUFFER,
|
|
|
|
this.gl.BUFFER_SIZE) / UINT32_SIZE;
|
|
|
|
this.gl.drawElements(this.gl.TRIANGLES, indexCount, this.gl.UNSIGNED_INT, 0);
|
|
|
|
}
|
|
|
|
|
2017-08-08 14:23:30 -04:00
|
|
|
canvas: HTMLCanvasElement;
|
2017-08-12 00:26:25 -04:00
|
|
|
gl: WebGLRenderingContext;
|
2017-08-18 01:47:28 -04:00
|
|
|
colorBufferHalfFloatExt: any;
|
2017-08-16 01:09:09 -04:00
|
|
|
drawBuffersExt: any;
|
2017-08-17 23:09:18 -04:00
|
|
|
instancedArraysExt: any;
|
2017-08-18 01:47:28 -04:00
|
|
|
textureHalfFloatExt: any;
|
2017-08-17 23:09:18 -04:00
|
|
|
vertexArrayObjectExt: any;
|
2017-08-15 18:57:52 -04:00
|
|
|
antialiasingStrategy: AntialiasingStrategy;
|
2017-08-17 23:09:18 -04:00
|
|
|
shaderPrograms: ShaderMap<PathfinderShaderProgram>;
|
2017-08-14 19:08:45 -04:00
|
|
|
meshes: PathfinderMeshBuffers;
|
2017-08-17 23:09:18 -04:00
|
|
|
meshData: PathfinderMeshData;
|
2017-08-15 00:24:58 -04:00
|
|
|
pathColorsBufferTexture: PathfinderBufferTexture;
|
2017-08-15 18:57:52 -04:00
|
|
|
quadPositionsBuffer: WebGLBuffer;
|
|
|
|
quadTexCoordsBuffer: WebGLBuffer;
|
2017-08-17 23:09:18 -04:00
|
|
|
quadElementsBuffer: WebGLBuffer;
|
2017-08-14 19:08:45 -04:00
|
|
|
dirty: boolean;
|
2017-08-12 00:26:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
class PathfinderShaderProgram {
|
2017-08-14 19:08:45 -04:00
|
|
|
constructor(gl: WebGLRenderingContext,
|
|
|
|
programName: string,
|
|
|
|
unlinkedShaderProgram: UnlinkedShaderProgram) {
|
2017-08-15 16:38:54 -04:00
|
|
|
this.program = expectNotNull(gl.createProgram(), "Failed to create shader program!");
|
2017-08-14 19:08:45 -04:00
|
|
|
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++) {
|
2017-08-15 16:38:54 -04:00
|
|
|
const uniformName = unwrapNull(gl.getActiveUniform(this.program, uniformIndex)).name;
|
|
|
|
uniforms[uniformName] = expectNotNull(gl.getUniformLocation(this.program, uniformName),
|
|
|
|
`Didn't find uniform "${uniformName}"!`);
|
2017-08-14 19:08:45 -04:00
|
|
|
}
|
|
|
|
for (let attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) {
|
2017-08-15 16:38:54 -04:00
|
|
|
const attributeName = unwrapNull(gl.getActiveAttrib(this.program, attributeIndex)).name;
|
2017-08-14 19:08:45 -04:00
|
|
|
attributes[attributeName] = attributeIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.uniforms = uniforms;
|
|
|
|
this.attributes = attributes;
|
2017-08-12 00:26:25 -04:00
|
|
|
}
|
2017-08-14 19:08:45 -04:00
|
|
|
|
|
|
|
readonly uniforms: UniformMap;
|
|
|
|
readonly attributes: AttributeMap;
|
|
|
|
readonly program: WebGLProgram;
|
2017-08-08 14:23:30 -04:00
|
|
|
}
|
|
|
|
|
2017-08-15 00:24:58 -04:00
|
|
|
class PathfinderBufferTexture {
|
2017-08-17 23:09:18 -04:00
|
|
|
constructor(gl: WebGLRenderingContext, data: Float32Array | Uint8Array) {
|
2017-08-15 00:24:58 -04:00
|
|
|
const pixelCount = Math.ceil(data.length / 4);
|
|
|
|
const width = Math.ceil(Math.sqrt(pixelCount));
|
|
|
|
const height = Math.ceil(pixelCount / width);
|
|
|
|
this.size = { width: width, height: height };
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
// Pad out with zeroes as necessary.
|
|
|
|
//
|
|
|
|
// FIXME(pcwalton): Do this earlier to save a copy here.
|
|
|
|
const elementCount = width * height * 4;
|
|
|
|
if (data.length != elementCount) {
|
|
|
|
const newData = new Float32Array(elementCount);
|
|
|
|
newData.set(data);
|
|
|
|
data = newData;
|
|
|
|
}
|
|
|
|
|
2017-08-15 16:38:54 -04:00
|
|
|
this.texture = expectNotNull(gl.createTexture(), "Failed to create texture!");
|
2017-08-15 00:24:58 -04:00
|
|
|
gl.activeTexture(gl.TEXTURE0);
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
2017-08-17 23:09:18 -04:00
|
|
|
const glType = data instanceof Float32Array ? gl.FLOAT : gl.UNSIGNED_BYTE;
|
|
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, glType, data);
|
|
|
|
|
2017-08-15 19:51:26 -04:00
|
|
|
setTextureParameters(gl, gl.NEAREST);
|
2017-08-15 00:24:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
readonly texture: WebGLTexture;
|
|
|
|
readonly size: Size2D;
|
|
|
|
}
|
|
|
|
|
2017-08-15 16:38:54 -04:00
|
|
|
class NoAAStrategy implements AntialiasingStrategy {
|
|
|
|
constructor(level: number) {
|
|
|
|
this.framebufferSize = { width: 0, height: 0 };
|
|
|
|
}
|
|
|
|
|
2017-08-16 01:09:09 -04:00
|
|
|
init(view: PathfinderView, framebufferSize: Size2D) {
|
2017-08-15 16:38:54 -04:00
|
|
|
this.framebufferSize = framebufferSize;
|
|
|
|
}
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
attachMeshes(view: PathfinderView) {}
|
|
|
|
|
2017-08-15 18:57:52 -04:00
|
|
|
prepare(view: PathfinderView) {
|
|
|
|
view.gl.viewport(0, 0, this.framebufferSize.width, this.framebufferSize.height);
|
2017-08-16 19:37:39 -04:00
|
|
|
|
|
|
|
// Clear.
|
|
|
|
view.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
|
|
|
view.gl.clearDepth(0.0);
|
|
|
|
view.gl.depthMask(true);
|
|
|
|
view.gl.clear(view.gl.COLOR_BUFFER_BIT | view.gl.DEPTH_BUFFER_BIT);
|
2017-08-15 18:57:52 -04:00
|
|
|
}
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
resolve(view: PathfinderView) {}
|
2017-08-15 16:38:54 -04:00
|
|
|
|
|
|
|
framebufferSize: Size2D;
|
|
|
|
}
|
|
|
|
|
|
|
|
class SSAAStrategy implements AntialiasingStrategy {
|
|
|
|
constructor(level: number) {
|
2017-08-15 18:57:52 -04:00
|
|
|
this.level = level;
|
|
|
|
this.canvasFramebufferSize = { width: 0, height: 0 };
|
|
|
|
this.supersampledFramebufferSize = { width: 0, height: 0 };
|
2017-08-15 16:38:54 -04:00
|
|
|
}
|
|
|
|
|
2017-08-16 01:09:09 -04:00
|
|
|
init(view: PathfinderView, framebufferSize: Size2D) {
|
2017-08-15 18:57:52 -04:00
|
|
|
this.canvasFramebufferSize = framebufferSize;
|
|
|
|
this.supersampledFramebufferSize = {
|
|
|
|
width: framebufferSize.width * 2,
|
|
|
|
height: framebufferSize.height * (this.level == 2 ? 1 : 2),
|
|
|
|
};
|
|
|
|
|
2017-08-16 01:09:09 -04:00
|
|
|
this.supersampledColorTexture = unwrapNull(view.gl.createTexture());
|
2017-08-16 19:37:39 -04:00
|
|
|
view.gl.activeTexture(view.gl.TEXTURE0);
|
2017-08-16 01:09:09 -04:00
|
|
|
view.gl.bindTexture(view.gl.TEXTURE_2D, this.supersampledColorTexture);
|
|
|
|
view.gl.texImage2D(view.gl.TEXTURE_2D,
|
|
|
|
0,
|
|
|
|
view.gl.RGBA,
|
|
|
|
this.supersampledFramebufferSize.width,
|
|
|
|
this.supersampledFramebufferSize.height,
|
|
|
|
0,
|
|
|
|
view.gl.RGBA,
|
|
|
|
view.gl.UNSIGNED_BYTE,
|
|
|
|
null);
|
|
|
|
setTextureParameters(view.gl, view.gl.LINEAR);
|
|
|
|
|
2017-08-16 19:37:39 -04:00
|
|
|
this.supersampledDepthTexture =
|
|
|
|
createFramebufferDepthTexture(view.gl, this.supersampledFramebufferSize);
|
2017-08-16 01:09:09 -04:00
|
|
|
|
2017-08-16 19:37:39 -04:00
|
|
|
this.supersampledFramebuffer = createFramebuffer(view.gl,
|
|
|
|
view.drawBuffersExt,
|
|
|
|
[this.supersampledColorTexture],
|
|
|
|
this.supersampledDepthTexture);
|
2017-08-15 18:57:52 -04:00
|
|
|
|
2017-08-16 01:09:09 -04:00
|
|
|
view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, null);
|
2017-08-15 16:38:54 -04:00
|
|
|
}
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
attachMeshes(view: PathfinderView) {}
|
|
|
|
|
2017-08-15 18:57:52 -04:00
|
|
|
prepare(view: PathfinderView) {
|
|
|
|
const size = this.supersampledFramebufferSize;
|
|
|
|
view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, this.supersampledFramebuffer);
|
|
|
|
view.gl.viewport(0, 0, size.width, size.height);
|
2017-08-16 19:37:39 -04:00
|
|
|
|
|
|
|
// Clear.
|
|
|
|
view.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
|
|
|
view.gl.clearDepth(0.0);
|
|
|
|
view.gl.depthMask(true);
|
|
|
|
view.gl.clear(view.gl.COLOR_BUFFER_BIT | view.gl.DEPTH_BUFFER_BIT);
|
2017-08-15 18:57:52 -04:00
|
|
|
}
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
resolve(view: PathfinderView) {
|
2017-08-15 18:57:52 -04:00
|
|
|
view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, null);
|
|
|
|
view.gl.viewport(0, 0, view.canvas.width, view.canvas.height);
|
|
|
|
view.gl.disable(view.gl.DEPTH_TEST);
|
|
|
|
|
|
|
|
// Set up the blit program VAO.
|
2017-08-17 23:09:18 -04:00
|
|
|
const blitProgram = view.shaderPrograms.blit;
|
2017-08-15 18:57:52 -04:00
|
|
|
view.gl.useProgram(blitProgram.program);
|
2017-08-16 19:37:39 -04:00
|
|
|
initQuadVAO(view, blitProgram.attributes);
|
2017-08-15 18:57:52 -04:00
|
|
|
|
|
|
|
// Resolve framebuffer.
|
|
|
|
view.gl.activeTexture(view.gl.TEXTURE0);
|
|
|
|
view.gl.bindTexture(view.gl.TEXTURE_2D, this.supersampledColorTexture);
|
|
|
|
view.gl.uniform1i(blitProgram.uniforms.uSource, 0);
|
2017-08-17 23:09:18 -04:00
|
|
|
view.gl.bindBuffer(view.gl.ELEMENT_ARRAY_BUFFER, view.quadElementsBuffer);
|
|
|
|
view.gl.drawElements(view.gl.TRIANGLES, 6, view.gl.UNSIGNED_BYTE, 0);
|
2017-08-15 18:57:52 -04:00
|
|
|
}
|
2017-08-15 16:38:54 -04:00
|
|
|
|
2017-08-15 18:57:52 -04:00
|
|
|
level: number;
|
|
|
|
canvasFramebufferSize: Readonly<Size2D>;
|
|
|
|
supersampledFramebufferSize: Readonly<Size2D>;
|
|
|
|
supersampledColorTexture: WebGLTexture;
|
2017-08-15 19:51:26 -04:00
|
|
|
supersampledDepthTexture: WebGLTexture;
|
2017-08-15 18:57:52 -04:00
|
|
|
supersampledFramebuffer: WebGLFramebuffer;
|
2017-08-15 16:38:54 -04:00
|
|
|
}
|
|
|
|
|
2017-08-16 01:09:09 -04:00
|
|
|
class ECAAStrategy implements AntialiasingStrategy {
|
|
|
|
constructor(level: number) {
|
|
|
|
this.framebufferSize = { width: 0, height: 0 };
|
|
|
|
}
|
|
|
|
|
|
|
|
init(view: PathfinderView, framebufferSize: Size2D) {
|
|
|
|
this.framebufferSize = framebufferSize;
|
2017-08-17 23:09:18 -04:00
|
|
|
|
2017-08-16 19:37:39 -04:00
|
|
|
this.initDirectFramebuffer(view);
|
|
|
|
this.initEdgeDetectFramebuffer(view);
|
2017-08-17 23:09:18 -04:00
|
|
|
this.initAAAlphaFramebuffer(view);
|
2017-08-16 19:37:39 -04:00
|
|
|
view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, null);
|
2017-08-17 23:09:18 -04:00
|
|
|
|
|
|
|
this.createEdgeDetectVAO(view);
|
|
|
|
}
|
|
|
|
|
|
|
|
attachMeshes(view: PathfinderView) {
|
|
|
|
const bVertexPositions = new Float32Array(view.meshData.bVertexPositions);
|
|
|
|
const bVertexPathIDs = new Uint8Array(view.meshData.bVertexPathIDs);
|
|
|
|
this.bVertexPositionBufferTexture = new PathfinderBufferTexture(view.gl, bVertexPositions);
|
|
|
|
this.bVertexPathIDBufferTexture = new PathfinderBufferTexture(view.gl, bVertexPathIDs);
|
|
|
|
|
|
|
|
this.createEdgeDetectVAO(view);
|
|
|
|
this.createCoverVAO(view);
|
2017-08-18 18:17:23 -04:00
|
|
|
this.createLineVAO(view);
|
2017-08-17 23:09:18 -04:00
|
|
|
this.createResolveVAO(view);
|
2017-08-16 19:37:39 -04:00
|
|
|
}
|
2017-08-16 01:09:09 -04:00
|
|
|
|
2017-08-16 19:37:39 -04:00
|
|
|
initDirectFramebuffer(view: PathfinderView) {
|
|
|
|
this.directColorTexture = createFramebufferColorTexture(view.gl, this.framebufferSize);
|
|
|
|
this.directPathIDTexture = createFramebufferColorTexture(view.gl, this.framebufferSize);
|
|
|
|
this.directDepthTexture = createFramebufferDepthTexture(view.gl, this.framebufferSize);
|
|
|
|
this.directFramebuffer =
|
|
|
|
createFramebuffer(view.gl,
|
|
|
|
view.drawBuffersExt,
|
|
|
|
[this.directColorTexture, this.directPathIDTexture],
|
|
|
|
this.directDepthTexture);
|
|
|
|
}
|
2017-08-16 01:09:09 -04:00
|
|
|
|
2017-08-16 19:37:39 -04:00
|
|
|
initEdgeDetectFramebuffer(view: PathfinderView) {
|
|
|
|
this.bgColorTexture = createFramebufferColorTexture(view.gl, this.framebufferSize);
|
|
|
|
this.fgColorTexture = createFramebufferColorTexture(view.gl, this.framebufferSize);
|
|
|
|
this.aaDepthTexture = createFramebufferDepthTexture(view.gl, this.framebufferSize);
|
|
|
|
this.edgeDetectFramebuffer = createFramebuffer(view.gl,
|
|
|
|
view.drawBuffersExt,
|
|
|
|
[this.bgColorTexture, this.fgColorTexture],
|
|
|
|
this.aaDepthTexture);
|
2017-08-16 01:09:09 -04:00
|
|
|
}
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
initAAAlphaFramebuffer(view: PathfinderView) {
|
2017-08-18 01:47:28 -04:00
|
|
|
this.aaAlphaTexture = unwrapNull(view.gl.createTexture());
|
2017-08-17 23:09:18 -04:00
|
|
|
view.gl.activeTexture(view.gl.TEXTURE0);
|
2017-08-18 01:47:28 -04:00
|
|
|
view.gl.bindTexture(view.gl.TEXTURE_2D, this.aaAlphaTexture);
|
2017-08-17 23:09:18 -04:00
|
|
|
view.gl.texImage2D(view.gl.TEXTURE_2D,
|
|
|
|
0,
|
2017-08-18 01:47:28 -04:00
|
|
|
view.gl.RGB,
|
2017-08-17 23:09:18 -04:00
|
|
|
this.framebufferSize.width,
|
|
|
|
this.framebufferSize.height,
|
|
|
|
0,
|
2017-08-18 01:47:28 -04:00
|
|
|
view.gl.RGB,
|
|
|
|
view.textureHalfFloatExt.HALF_FLOAT_OES,
|
2017-08-17 23:09:18 -04:00
|
|
|
null);
|
2017-08-18 01:47:28 -04:00
|
|
|
setTextureParameters(view.gl, view.gl.NEAREST);
|
2017-08-17 23:09:18 -04:00
|
|
|
|
|
|
|
this.aaFramebuffer = createFramebuffer(view.gl,
|
|
|
|
view.drawBuffersExt,
|
|
|
|
[this.aaAlphaTexture],
|
|
|
|
this.aaDepthTexture);
|
|
|
|
}
|
|
|
|
|
|
|
|
createEdgeDetectVAO(view: PathfinderView) {
|
|
|
|
this.edgeDetectVAO = view.vertexArrayObjectExt.createVertexArrayOES();
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(this.edgeDetectVAO);
|
|
|
|
|
|
|
|
const edgeDetectProgram = view.shaderPrograms.ecaaEdgeDetect;
|
|
|
|
view.gl.useProgram(edgeDetectProgram.program);
|
|
|
|
initQuadVAO(view, edgeDetectProgram.attributes);
|
|
|
|
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
createCoverVAO(view: PathfinderView) {
|
|
|
|
this.coverVAO = view.vertexArrayObjectExt.createVertexArrayOES();
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(this.coverVAO);
|
|
|
|
|
|
|
|
const coverProgram = view.shaderPrograms.ecaaCover;
|
|
|
|
const attributes = coverProgram.attributes;
|
|
|
|
view.gl.useProgram(coverProgram.program);
|
|
|
|
view.gl.bindBuffer(view.gl.ARRAY_BUFFER, view.quadPositionsBuffer);
|
|
|
|
view.gl.vertexAttribPointer(attributes.aQuadPosition, 2, view.gl.FLOAT, false, 0, 0);
|
|
|
|
view.gl.bindBuffer(view.gl.ARRAY_BUFFER, view.meshes.bQuads);
|
|
|
|
view.gl.vertexAttribPointer(attributes.aUpperPointIndices,
|
|
|
|
4,
|
|
|
|
view.gl.UNSIGNED_SHORT,
|
|
|
|
false,
|
|
|
|
B_QUAD_SIZE,
|
|
|
|
B_QUAD_UPPER_INDICES_OFFSET);
|
|
|
|
view.gl.vertexAttribPointer(attributes.aLowerPointIndices,
|
|
|
|
4,
|
|
|
|
view.gl.UNSIGNED_SHORT,
|
|
|
|
false,
|
|
|
|
B_QUAD_SIZE,
|
|
|
|
B_QUAD_LOWER_INDICES_OFFSET);
|
|
|
|
view.gl.enableVertexAttribArray(attributes.aQuadPosition);
|
|
|
|
view.gl.enableVertexAttribArray(attributes.aUpperPointIndices);
|
|
|
|
view.gl.enableVertexAttribArray(attributes.aLowerPointIndices);
|
|
|
|
view.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aUpperPointIndices, 1);
|
|
|
|
view.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aLowerPointIndices, 1);
|
2017-08-18 01:47:28 -04:00
|
|
|
view.gl.bindBuffer(view.gl.ELEMENT_ARRAY_BUFFER, view.quadElementsBuffer);
|
2017-08-17 23:09:18 -04:00
|
|
|
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(null);
|
|
|
|
}
|
|
|
|
|
2017-08-18 18:17:23 -04:00
|
|
|
createLineVAO(view: PathfinderView) {
|
|
|
|
const lineProgram = view.shaderPrograms.ecaaLine;
|
|
|
|
const attributes = lineProgram.attributes;
|
|
|
|
|
|
|
|
const vaos: Partial<UpperAndLower<WebGLVertexArrayObject>> = {};
|
|
|
|
for (const direction of ['upper', 'lower'] as Array<'upper' | 'lower'>) {
|
|
|
|
vaos[direction] = view.vertexArrayObjectExt.createVertexArrayOES();
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(vaos[direction]);
|
|
|
|
|
|
|
|
const lineIndexBuffer = {
|
|
|
|
upper: view.meshes.edgeUpperLineIndices,
|
|
|
|
lower: view.meshes.edgeLowerLineIndices,
|
|
|
|
}[direction];
|
|
|
|
|
|
|
|
view.gl.useProgram(lineProgram.program);
|
|
|
|
view.gl.bindBuffer(view.gl.ARRAY_BUFFER, view.quadPositionsBuffer);
|
|
|
|
view.gl.vertexAttribPointer(attributes.aQuadPosition, 2, view.gl.FLOAT, false, 0, 0);
|
|
|
|
view.gl.bindBuffer(view.gl.ARRAY_BUFFER, lineIndexBuffer);
|
|
|
|
view.gl.vertexAttribPointer(attributes.aLineIndices,
|
|
|
|
4,
|
|
|
|
view.gl.UNSIGNED_SHORT,
|
|
|
|
false,
|
|
|
|
0,
|
|
|
|
0);
|
|
|
|
view.gl.enableVertexAttribArray(attributes.aQuadPosition);
|
|
|
|
view.gl.enableVertexAttribArray(attributes.aLineIndices);
|
|
|
|
view.instancedArraysExt.vertexAttribDivisorANGLE(attributes.aLineIndices, 1);
|
|
|
|
view.gl.bindBuffer(view.gl.ELEMENT_ARRAY_BUFFER, view.quadElementsBuffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(null);
|
|
|
|
|
|
|
|
this.edgeVAOs = vaos as UpperAndLower<WebGLVertexArrayObject>;
|
|
|
|
}
|
|
|
|
|
|
|
|
createResolveVAO(view: PathfinderView) {
|
|
|
|
this.resolveVAO = view.vertexArrayObjectExt.createVertexArrayOES();
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(this.resolveVAO);
|
|
|
|
|
|
|
|
const resolveProgram = view.shaderPrograms.ecaaResolve;
|
|
|
|
view.gl.useProgram(resolveProgram.program);
|
|
|
|
initQuadVAO(view, resolveProgram.attributes);
|
|
|
|
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(null);
|
|
|
|
}
|
|
|
|
|
2017-08-16 01:09:09 -04:00
|
|
|
prepare(view: PathfinderView) {
|
|
|
|
view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, this.directFramebuffer);
|
|
|
|
view.gl.viewport(0, 0, this.framebufferSize.width, this.framebufferSize.height);
|
2017-08-16 19:37:39 -04:00
|
|
|
|
|
|
|
// Clear out the color and depth textures.
|
|
|
|
view.drawBuffersExt.drawBuffersWEBGL([
|
|
|
|
view.drawBuffersExt.COLOR_ATTACHMENT0_WEBGL,
|
|
|
|
view.drawBuffersExt.NONE,
|
|
|
|
]);
|
|
|
|
view.gl.clearColor(1.0, 1.0, 1.0, 1.0);
|
|
|
|
view.gl.clearDepth(0.0);
|
|
|
|
view.gl.depthMask(true);
|
|
|
|
view.gl.clear(view.gl.COLOR_BUFFER_BIT | view.gl.DEPTH_BUFFER_BIT);
|
|
|
|
|
|
|
|
// Clear out the path ID texture.
|
|
|
|
view.drawBuffersExt.drawBuffersWEBGL([
|
|
|
|
view.drawBuffersExt.NONE,
|
|
|
|
view.drawBuffersExt.COLOR_ATTACHMENT1_WEBGL,
|
|
|
|
]);
|
|
|
|
view.gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
|
|
|
view.gl.clear(view.gl.COLOR_BUFFER_BIT);
|
|
|
|
|
|
|
|
// Render to both textures.
|
|
|
|
view.drawBuffersExt.drawBuffersWEBGL([
|
|
|
|
view.drawBuffersExt.COLOR_ATTACHMENT0_WEBGL,
|
|
|
|
view.drawBuffersExt.COLOR_ATTACHMENT1_WEBGL,
|
|
|
|
]);
|
2017-08-16 01:09:09 -04:00
|
|
|
}
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
resolve(view: PathfinderView) {
|
|
|
|
// Detect edges.
|
|
|
|
this.detectEdges(view);
|
|
|
|
|
|
|
|
// Conservatively cover.
|
|
|
|
this.cover(view);
|
|
|
|
|
2017-08-18 18:17:23 -04:00
|
|
|
// Antialias.
|
|
|
|
this.antialiasLines(view);
|
|
|
|
|
2017-08-18 01:47:28 -04:00
|
|
|
// Resolve the antialiasing.
|
|
|
|
this.resolveAA(view);
|
2017-08-17 23:09:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
detectEdges(view: PathfinderView) {
|
2017-08-16 19:37:39 -04:00
|
|
|
// Set state for edge detection.
|
2017-08-17 23:09:18 -04:00
|
|
|
const edgeDetectProgram = view.shaderPrograms.ecaaEdgeDetect;
|
2017-08-16 19:37:39 -04:00
|
|
|
view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, this.edgeDetectFramebuffer);
|
|
|
|
view.gl.viewport(0, 0, this.framebufferSize.width, this.framebufferSize.height);
|
|
|
|
|
|
|
|
view.drawBuffersExt.drawBuffersWEBGL([
|
|
|
|
view.drawBuffersExt.COLOR_ATTACHMENT0_WEBGL,
|
|
|
|
view.drawBuffersExt.COLOR_ATTACHMENT1_WEBGL,
|
|
|
|
]);
|
|
|
|
|
|
|
|
view.gl.depthMask(true);
|
|
|
|
view.gl.depthFunc(view.gl.ALWAYS);
|
|
|
|
view.gl.enable(view.gl.DEPTH_TEST);
|
|
|
|
view.gl.disable(view.gl.BLEND);
|
|
|
|
|
|
|
|
view.gl.clearDepth(0.0);
|
|
|
|
view.gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
|
|
|
view.gl.clear(view.gl.COLOR_BUFFER_BIT | view.gl.DEPTH_BUFFER_BIT);
|
|
|
|
|
|
|
|
// Perform edge detection.
|
2017-08-17 23:09:18 -04:00
|
|
|
view.gl.useProgram(edgeDetectProgram.program);
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(this.edgeDetectVAO);
|
2017-08-16 19:37:39 -04:00
|
|
|
view.gl.uniform2i(edgeDetectProgram.uniforms.uFramebufferSize,
|
|
|
|
this.framebufferSize.width,
|
|
|
|
this.framebufferSize.height);
|
|
|
|
view.gl.activeTexture(view.gl.TEXTURE0);
|
|
|
|
view.gl.bindTexture(view.gl.TEXTURE_2D, this.directColorTexture);
|
|
|
|
view.gl.uniform1i(edgeDetectProgram.uniforms.uColor, 0);
|
|
|
|
view.gl.activeTexture(view.gl.TEXTURE1);
|
|
|
|
view.gl.bindTexture(view.gl.TEXTURE_2D, this.directPathIDTexture);
|
|
|
|
view.gl.uniform1i(edgeDetectProgram.uniforms.uPathID, 1);
|
2017-08-17 23:09:18 -04:00
|
|
|
view.gl.bindBuffer(view.gl.ELEMENT_ARRAY_BUFFER, view.quadElementsBuffer);
|
|
|
|
view.gl.drawElements(view.gl.TRIANGLES, 6, view.gl.UNSIGNED_BYTE, 0);
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(null);
|
|
|
|
}
|
2017-08-16 19:37:39 -04:00
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
cover(view: PathfinderView) {
|
|
|
|
// Set state for conservative coverage.
|
|
|
|
const coverProgram = view.shaderPrograms.ecaaCover;
|
|
|
|
view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, this.aaFramebuffer);
|
2017-08-16 19:37:39 -04:00
|
|
|
view.gl.viewport(0, 0, this.framebufferSize.width, this.framebufferSize.height);
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
view.gl.depthMask(false);
|
|
|
|
//view.gl.depthFunc(view.gl.EQUAL);
|
|
|
|
view.gl.depthFunc(view.gl.ALWAYS);
|
|
|
|
view.gl.enable(view.gl.DEPTH_TEST);
|
|
|
|
view.gl.blendEquation(view.gl.FUNC_ADD);
|
|
|
|
view.gl.blendFunc(view.gl.ONE, view.gl.ONE);
|
|
|
|
view.gl.enable(view.gl.BLEND);
|
2017-08-16 19:37:39 -04:00
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
view.gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
|
|
|
view.gl.clear(view.gl.COLOR_BUFFER_BIT);
|
|
|
|
|
|
|
|
// Conservatively cover.
|
|
|
|
view.gl.useProgram(coverProgram.program);
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(this.coverVAO);
|
|
|
|
const uniforms = coverProgram.uniforms;
|
|
|
|
view.gl.uniformMatrix4fv(uniforms.uTransform, false, IDENTITY);
|
|
|
|
view.gl.uniform2i(uniforms.uFramebufferSize,
|
2017-08-16 19:37:39 -04:00
|
|
|
this.framebufferSize.width,
|
|
|
|
this.framebufferSize.height);
|
2017-08-17 23:09:18 -04:00
|
|
|
view.gl.uniform2i(uniforms.uBVertexPositionDimensions,
|
|
|
|
this.bVertexPositionBufferTexture.size.width,
|
|
|
|
this.bVertexPositionBufferTexture.size.height);
|
|
|
|
view.gl.uniform2i(uniforms.uBVertexPathIDDimensions,
|
|
|
|
this.bVertexPathIDBufferTexture.size.width,
|
|
|
|
this.bVertexPathIDBufferTexture.size.height);
|
2017-08-16 19:37:39 -04:00
|
|
|
view.gl.activeTexture(view.gl.TEXTURE0);
|
2017-08-17 23:09:18 -04:00
|
|
|
view.gl.bindTexture(view.gl.TEXTURE_2D, this.bVertexPositionBufferTexture.texture);
|
|
|
|
view.gl.uniform1i(uniforms.uBVertexPosition, 0);
|
2017-08-16 19:37:39 -04:00
|
|
|
view.gl.activeTexture(view.gl.TEXTURE1);
|
2017-08-17 23:09:18 -04:00
|
|
|
view.gl.bindTexture(view.gl.TEXTURE_2D, this.bVertexPathIDBufferTexture.texture);
|
|
|
|
view.gl.uniform1i(uniforms.uBVertexPathID, 1);
|
|
|
|
view.instancedArraysExt.drawElementsInstancedANGLE(view.gl.TRIANGLES,
|
|
|
|
6,
|
|
|
|
view.gl.UNSIGNED_BYTE,
|
|
|
|
0,
|
|
|
|
view.meshData.bQuadCount);
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(null);
|
2017-08-16 01:09:09 -04:00
|
|
|
}
|
|
|
|
|
2017-08-18 18:17:23 -04:00
|
|
|
antialiasLines(view: PathfinderView) {
|
|
|
|
// Set state for line antialiasing.
|
|
|
|
const lineProgram = view.shaderPrograms.ecaaLine;
|
|
|
|
view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, this.aaFramebuffer);
|
|
|
|
view.gl.viewport(0, 0, this.framebufferSize.width, this.framebufferSize.height);
|
|
|
|
|
|
|
|
view.gl.depthMask(false);
|
|
|
|
//view.gl.depthFunc(view.gl.EQUAL);
|
|
|
|
view.gl.depthFunc(view.gl.ALWAYS);
|
|
|
|
view.gl.enable(view.gl.DEPTH_TEST);
|
|
|
|
view.gl.blendEquation(view.gl.FUNC_REVERSE_SUBTRACT);
|
|
|
|
view.gl.blendFunc(view.gl.ONE, view.gl.ONE);
|
|
|
|
view.gl.enable(view.gl.BLEND);
|
|
|
|
|
|
|
|
// Antialias lines.
|
|
|
|
view.gl.useProgram(lineProgram.program);
|
|
|
|
const uniforms = lineProgram.uniforms;
|
|
|
|
view.gl.uniformMatrix4fv(uniforms.uTransform, false, IDENTITY);
|
|
|
|
view.gl.uniform2i(uniforms.uFramebufferSize,
|
|
|
|
this.framebufferSize.width,
|
|
|
|
this.framebufferSize.height);
|
|
|
|
view.gl.uniform2i(uniforms.uBVertexPositionDimensions,
|
|
|
|
this.bVertexPositionBufferTexture.size.width,
|
|
|
|
this.bVertexPositionBufferTexture.size.height);
|
|
|
|
view.gl.uniform2i(uniforms.uBVertexPathIDDimensions,
|
|
|
|
this.bVertexPathIDBufferTexture.size.width,
|
|
|
|
this.bVertexPathIDBufferTexture.size.height);
|
|
|
|
view.gl.activeTexture(view.gl.TEXTURE0);
|
|
|
|
view.gl.bindTexture(view.gl.TEXTURE_2D, this.bVertexPositionBufferTexture.texture);
|
|
|
|
view.gl.uniform1i(uniforms.uBVertexPosition, 0);
|
|
|
|
view.gl.activeTexture(view.gl.TEXTURE1);
|
|
|
|
view.gl.bindTexture(view.gl.TEXTURE_2D, this.bVertexPathIDBufferTexture.texture);
|
|
|
|
view.gl.uniform1i(uniforms.uBVertexPathID, 1);
|
|
|
|
|
|
|
|
for (const direction of ['upper', 'lower'] as Array<keyof UpperAndLower<void>>) {
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(this.edgeVAOs[direction]);
|
|
|
|
view.gl.uniform1i(uniforms.uLowerPart, direction === 'lower' ? 1 : 0);
|
|
|
|
const count = {
|
|
|
|
upper: view.meshData.edgeUpperLineIndexCount,
|
|
|
|
lower: view.meshData.edgeLowerLineIndexCount,
|
|
|
|
}[direction];
|
|
|
|
view.instancedArraysExt.drawElementsInstancedANGLE(view.gl.TRIANGLES,
|
|
|
|
6,
|
|
|
|
view.gl.UNSIGNED_BYTE,
|
|
|
|
0,
|
|
|
|
count);
|
|
|
|
}
|
|
|
|
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(null);
|
|
|
|
}
|
|
|
|
|
2017-08-18 01:47:28 -04:00
|
|
|
resolveAA(view: PathfinderView) {
|
|
|
|
// Set state for ECAA resolve.
|
|
|
|
view.gl.bindFramebuffer(view.gl.FRAMEBUFFER, null);
|
|
|
|
view.gl.viewport(0, 0, this.framebufferSize.width, this.framebufferSize.height);
|
|
|
|
view.gl.disable(view.gl.DEPTH_TEST);
|
|
|
|
view.gl.disable(view.gl.BLEND);
|
|
|
|
view.drawBuffersExt.drawBuffersWEBGL([view.gl.BACK]);
|
|
|
|
|
|
|
|
// Resolve.
|
|
|
|
const resolveProgram = view.shaderPrograms.ecaaResolve;
|
|
|
|
view.gl.useProgram(resolveProgram.program);
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(this.resolveVAO);
|
|
|
|
view.gl.uniform2i(resolveProgram.uniforms.uFramebufferSize,
|
|
|
|
this.framebufferSize.width,
|
|
|
|
this.framebufferSize.height);
|
|
|
|
view.gl.activeTexture(view.gl.TEXTURE0);
|
|
|
|
view.gl.bindTexture(view.gl.TEXTURE_2D, this.bgColorTexture);
|
|
|
|
view.gl.uniform1i(resolveProgram.uniforms.uBGColor, 0);
|
|
|
|
view.gl.activeTexture(view.gl.TEXTURE1);
|
|
|
|
view.gl.bindTexture(view.gl.TEXTURE_2D, this.fgColorTexture);
|
|
|
|
view.gl.uniform1i(resolveProgram.uniforms.uFGColor, 1);
|
|
|
|
view.gl.activeTexture(view.gl.TEXTURE2);
|
|
|
|
view.gl.bindTexture(view.gl.TEXTURE_2D, this.aaAlphaTexture);
|
|
|
|
view.gl.uniform1i(resolveProgram.uniforms.uAAAlpha, 2);
|
|
|
|
view.gl.drawElements(view.gl.TRIANGLES, 6, view.gl.UNSIGNED_BYTE, 0);
|
|
|
|
view.vertexArrayObjectExt.bindVertexArrayOES(null);
|
|
|
|
}
|
|
|
|
|
2017-08-17 23:09:18 -04:00
|
|
|
bVertexPositionBufferTexture: PathfinderBufferTexture;
|
|
|
|
bVertexPathIDBufferTexture: PathfinderBufferTexture;
|
2017-08-16 01:09:09 -04:00
|
|
|
directColorTexture: WebGLTexture;
|
|
|
|
directPathIDTexture: WebGLTexture;
|
|
|
|
directDepthTexture: WebGLTexture;
|
|
|
|
directFramebuffer: WebGLFramebuffer;
|
2017-08-16 19:37:39 -04:00
|
|
|
bgColorTexture: WebGLTexture;
|
|
|
|
fgColorTexture: WebGLTexture;
|
|
|
|
aaDepthTexture: WebGLTexture;
|
2017-08-17 23:09:18 -04:00
|
|
|
aaAlphaTexture: WebGLTexture;
|
2017-08-16 19:37:39 -04:00
|
|
|
edgeDetectFramebuffer: WebGLFramebuffer;
|
2017-08-17 23:09:18 -04:00
|
|
|
aaFramebuffer: WebGLFramebuffer;
|
|
|
|
edgeDetectVAO: WebGLVertexArrayObject;
|
|
|
|
coverVAO: WebGLVertexArrayObject;
|
2017-08-18 18:17:23 -04:00
|
|
|
edgeVAOs: UpperAndLower<WebGLVertexArrayObject>;
|
2017-08-17 23:09:18 -04:00
|
|
|
resolveVAO: WebGLVertexArrayObject;
|
2017-08-16 01:09:09 -04:00
|
|
|
framebufferSize: Size2D;
|
|
|
|
}
|
|
|
|
|
2017-08-15 16:38:54 -04:00
|
|
|
interface AntialiasingStrategyTable {
|
2017-08-15 18:57:52 -04:00
|
|
|
none: typeof NoAAStrategy;
|
|
|
|
ssaa: typeof SSAAStrategy;
|
2017-08-16 01:09:09 -04:00
|
|
|
ecaa: typeof ECAAStrategy;
|
2017-08-15 16:38:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
const ANTIALIASING_STRATEGIES: AntialiasingStrategyTable = {
|
|
|
|
none: NoAAStrategy,
|
|
|
|
ssaa: SSAAStrategy,
|
2017-08-16 01:09:09 -04:00
|
|
|
ecaa: ECAAStrategy,
|
2017-08-15 16:38:54 -04:00
|
|
|
};
|
|
|
|
|
2017-08-08 14:23:30 -04:00
|
|
|
function main() {
|
|
|
|
const controller = new AppController;
|
|
|
|
window.addEventListener('load', () => controller.start(), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
main();
|