Merge identical shaders into one

This commit is contained in:
Patrick Walton 2019-01-03 20:03:25 -08:00
parent eb7d3b46ef
commit 2109049269
4 changed files with 542 additions and 549 deletions

11
Cargo.lock generated
View File

@ -608,6 +608,15 @@ name = "half"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "hashbrown"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "httparse"
version = "1.3.2"
@ -1517,6 +1526,7 @@ dependencies = [
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"hashbrown 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"jemallocator 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"lyon_geom 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lyon_path 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1787,6 +1797,7 @@ source = "git+https://github.com/SergioBenitez/ring?branch=v0.12#9ccfa153a27aecc
"checksum glib-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9693049613ff52b93013cc3d2590366d8e530366d288438724b73f6c7dc4be8"
"checksum gobject-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60d507c87a71b1143c66ed21a969be9b99a76df234b342d733e787e6c9c7d7c2"
"checksum half 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee6c0438de3ca4d8cac2eec62b228e2f8865cfe9ebefea720406774223fa2d2e"
"checksum hashbrown 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "64b7d419d0622ae02fe5da6b9a5e1964b610a65bb37923b976aeebb6dbb8f86e"
"checksum httparse 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7b6288d7db100340ca12873fd4d08ad1b8f206a9457798dfb17c018a33fee540"
"checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e"
"checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2"

View File

@ -22,7 +22,7 @@ import {staticCast, unwrapNull} from "./util";
const SVGPath: (path: string) => SVGPath = require('svgpath');
const STENCIL_FRAMEBUFFER_SIZE: Size2D = {
width: TILE_SIZE.width * 128,
width: TILE_SIZE.width * 256,
height: TILE_SIZE.height * 256,
};
@ -46,6 +46,23 @@ interface Color {
type Edge = 'left' | 'top' | 'right' | 'bottom';
type FillProgram =
Program<'FramebufferSize' | 'TileSize' | 'AreaLUT',
'TessCoord' | 'FromPx' | 'ToPx' | 'FromSubpx' | 'ToSubpx' | 'TileIndex'>;
type SolidTileProgram =
Program<'FramebufferSize' |
'TileSize' |
'FillColorsTexture' | 'FillColorsTextureSize' |
'ViewBoxOrigin',
'TessCoord' | 'TileOrigin' | 'Object'>;
type MaskTileProgram =
Program<'FramebufferSize' |
'TileSize' |
'StencilTexture' | 'StencilTextureSize' |
'FillColorsTexture' | 'FillColorsTextureSize' |
'ViewBoxOrigin',
'TessCoord' | 'TileOrigin' | 'Backdrop' | 'Object'>;
class App {
private canvas: HTMLCanvasElement;
private openButton: HTMLInputElement;
@ -57,36 +74,17 @@ class App {
private fillColorsTexture: WebGLTexture;
private stencilTexture: WebGLTexture;
private stencilFramebuffer: WebGLFramebuffer;
private fillProgram: Program<'FramebufferSize' | 'TileSize' | 'AreaLUT',
'TessCoord' |
'FromPx' | 'ToPx' |
'FromSubpx' | 'ToSubpx' |
'TileIndex'>;
private solidTileProgram: Program<'FramebufferSize' |
'TileSize' |
'FillColorsTexture' | 'FillColorsTextureSize' |
'ViewBoxOrigin',
'TessCoord' | 'TileOrigin' | 'Object'>;
private maskTileProgram:
Program<'FramebufferSize' |
'TileSize' |
'StencilTexture' | 'StencilTextureSize' |
'FillColorsTexture' | 'FillColorsTextureSize' |
'ViewBoxOrigin',
'TessCoord' | 'TileOrigin' | 'Backdrop' | 'Object'>;
private fillProgram: FillProgram;
private solidTileProgram: SolidTileProgram;
private maskTileProgram: MaskTileProgram;
private quadVertexBuffer: WebGLBuffer;
private fillVertexBuffer: WebGLBuffer;
private fillVertexArray: WebGLVertexArrayObject;
private solidTileVertexBuffer: WebGLBuffer;
private solidVertexArray: WebGLVertexArrayObject;
private maskTileVertexBuffer: WebGLBuffer;
private maskVertexArray: WebGLVertexArrayObject;
private batchBuffers: BatchBuffers[];
private viewBox: Rect;
private fillPrimitiveCount: number;
private solidTileCount: number;
private maskTileCount: number;
private objectCount: number;
constructor(areaLUT: HTMLImageElement) {
@ -209,14 +207,307 @@ class App {
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTEX_POSITIONS, gl.STATIC_DRAW);
// Initialize tile VBOs and IBOs.
this.solidTileVertexBuffer = unwrapNull(gl.createBuffer());
// Initialize solid tile VAO.
this.solidVertexArray = unwrapNull(gl.createVertexArray());
gl.bindVertexArray(this.solidVertexArray);
gl.useProgram(this.solidTileProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
gl.vertexAttribPointer(solidTileProgram.attributes.TessCoord,
2,
gl.UNSIGNED_BYTE,
false,
0,
0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.solidTileVertexBuffer);
gl.vertexAttribPointer(solidTileProgram.attributes.TileOrigin,
2,
gl.SHORT,
false,
SOLID_TILE_INSTANCE_SIZE,
0);
gl.vertexAttribDivisor(solidTileProgram.attributes.TileOrigin, 1);
gl.vertexAttribIPointer(solidTileProgram.attributes.Object,
1,
gl.UNSIGNED_SHORT,
SOLID_TILE_INSTANCE_SIZE,
4);
gl.vertexAttribDivisor(solidTileProgram.attributes.Object, 1);
gl.enableVertexAttribArray(solidTileProgram.attributes.TessCoord);
gl.enableVertexAttribArray(solidTileProgram.attributes.TileOrigin);
gl.enableVertexAttribArray(solidTileProgram.attributes.Object);
this.batchBuffers = [];
this.viewBox = new Rect(new Point2D(0.0, 0.0), new Size2D(0.0, 0.0));
// Set up event handlers.
this.canvas.addEventListener('click', event => this.onClick(event), false);
this.solidTileCount = 0;
this.objectCount = 0;
}
redraw(): void {
const gl = this.gl, canvas = this.canvas;
//console.log("viewBox", this.viewBox);
// Initialize timers.
let fillTimerQuery = null, solidTimerQuery = null, maskTimerQuery = null;
if (this.disjointTimerQueryExt != null) {
fillTimerQuery = unwrapNull(gl.createQuery());
solidTimerQuery = unwrapNull(gl.createQuery());
maskTimerQuery = unwrapNull(gl.createQuery());
}
// Clear.
if (solidTimerQuery != null)
gl.beginQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT, solidTimerQuery);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
const framebufferSize = {width: canvas.width, height: canvas.height};
gl.viewport(0, 0, framebufferSize.width, framebufferSize.height);
gl.clearColor(0.85, 0.85, 0.85, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw solid tiles.
gl.bindVertexArray(this.solidVertexArray);
gl.useProgram(this.solidTileProgram.program);
gl.uniform2f(this.solidTileProgram.uniforms.FramebufferSize,
framebufferSize.width,
framebufferSize.height);
gl.uniform2f(this.solidTileProgram.uniforms.TileSize, TILE_SIZE.width, TILE_SIZE.height);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.fillColorsTexture);
gl.uniform1i(this.solidTileProgram.uniforms.FillColorsTexture, 0);
// FIXME(pcwalton): Maybe this should be an ivec2 or uvec2?
gl.uniform2f(this.solidTileProgram.uniforms.FillColorsTextureSize,
this.objectCount,
1.0);
gl.uniform2f(this.solidTileProgram.uniforms.ViewBoxOrigin,
this.viewBox.origin.x,
this.viewBox.origin.y);
gl.disable(gl.BLEND);
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, this.solidTileCount);
if (solidTimerQuery != null)
gl.endQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT);
// Draw batches.
if (fillTimerQuery != null)
gl.beginQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT, fillTimerQuery);
for (const batch of this.batchBuffers) {
// Fill.
gl.bindFramebuffer(gl.FRAMEBUFFER, this.stencilFramebuffer);
gl.viewport(0, 0, STENCIL_FRAMEBUFFER_SIZE.width, STENCIL_FRAMEBUFFER_SIZE.height);
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindVertexArray(batch.fillVertexArray);
gl.useProgram(this.fillProgram.program);
gl.uniform2f(this.fillProgram.uniforms.FramebufferSize,
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height);
gl.uniform2f(this.fillProgram.uniforms.TileSize, TILE_SIZE.width, TILE_SIZE.height);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.areaLUTTexture);
gl.uniform1i(this.fillProgram.uniforms.AreaLUT, 0);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE);
gl.enable(gl.BLEND);
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, unwrapNull(batch.fillPrimitiveCount));
gl.disable(gl.BLEND);
// Read back stencil and dump it.
//this.dumpStencil();
// Draw masked tiles.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, framebufferSize.width, framebufferSize.height);
gl.bindVertexArray(batch.maskVertexArray);
gl.useProgram(this.maskTileProgram.program);
gl.uniform2f(this.maskTileProgram.uniforms.FramebufferSize,
framebufferSize.width,
framebufferSize.height);
gl.uniform2f(this.maskTileProgram.uniforms.TileSize,
TILE_SIZE.width,
TILE_SIZE.height);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.stencilTexture);
gl.uniform1i(this.maskTileProgram.uniforms.StencilTexture, 0);
gl.uniform2f(this.maskTileProgram.uniforms.StencilTextureSize,
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, this.fillColorsTexture);
gl.uniform1i(this.maskTileProgram.uniforms.FillColorsTexture, 1);
// FIXME(pcwalton): Maybe this should be an ivec2 or uvec2?
gl.uniform2f(this.maskTileProgram.uniforms.FillColorsTextureSize,
this.objectCount,
1.0);
gl.uniform2f(this.maskTileProgram.uniforms.ViewBoxOrigin,
this.viewBox.origin.x,
this.viewBox.origin.y);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, batch.maskTileCount);
gl.disable(gl.BLEND);
}
if (fillTimerQuery != null)
gl.endQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT);
// End timer.
if (fillTimerQuery != null && solidTimerQuery != null) {
processQueries(gl, this.disjointTimerQueryExt, {
fill: fillTimerQuery,
solid: solidTimerQuery,
});
}
}
private dumpStencil(): void {
const gl = this.gl;
const totalStencilFramebufferSize = STENCIL_FRAMEBUFFER_SIZE.width *
STENCIL_FRAMEBUFFER_SIZE.height * 4;
const stencilData = new Float32Array(totalStencilFramebufferSize);
gl.readPixels(0, 0,
STENCIL_FRAMEBUFFER_SIZE.width, STENCIL_FRAMEBUFFER_SIZE.height,
gl.RGBA,
gl.FLOAT,
stencilData);
const stencilDumpData = new Uint8ClampedArray(totalStencilFramebufferSize);
for (let i = 0; i < stencilData.length; i++)
stencilDumpData[i] = stencilData[i] * 255.0;
const stencilDumpCanvas = document.createElement('canvas');
stencilDumpCanvas.width = STENCIL_FRAMEBUFFER_SIZE.width;
stencilDumpCanvas.height = STENCIL_FRAMEBUFFER_SIZE.height;
stencilDumpCanvas.style.width =
(STENCIL_FRAMEBUFFER_SIZE.width / window.devicePixelRatio) + "px";
stencilDumpCanvas.style.height =
(STENCIL_FRAMEBUFFER_SIZE.height / window.devicePixelRatio) + "px";
const stencilDumpCanvasContext = unwrapNull(stencilDumpCanvas.getContext('2d'));
const stencilDumpImageData = new ImageData(stencilDumpData,
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height);
stencilDumpCanvasContext.putImageData(stencilDumpImageData, 0, 0);
document.body.appendChild(stencilDumpCanvas);
//console.log(stencilData);
}
private loadFile(): void {
console.log("loadFile");
// TODO(pcwalton)
const file = unwrapNull(unwrapNull(this.openButton.files)[0]);
const reader = new FileReader;
reader.addEventListener('loadend', () => {
const gl = this.gl;
const arrayBuffer = staticCast(reader.result, ArrayBuffer);
const root = new RIFFChunk(new DataView(arrayBuffer));
for (const subchunk of root.subchunks(4)) {
const self = this;
const id = subchunk.stringID();
if (id === 'head') {
const headerData = subchunk.contents();
const version = headerData.getUint32(0, true);
if (version !== 0)
throw new Error("Unknown version!");
// Ignore the batch count and fetch the view box.
this.viewBox = new Rect(new Point2D(headerData.getFloat32(8, true),
headerData.getFloat32(12, true)),
new Size2D(headerData.getFloat32(16, true),
headerData.getFloat32(20, true)));
continue;
} else if (id === 'soli') {
self.solidTileCount = uploadArrayBuffer(subchunk,
this.solidTileVertexBuffer,
SOLID_TILE_INSTANCE_SIZE);
} else if (id === 'shad') {
this.objectCount = subchunk.length() / 4;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.fillColorsTexture);
const textureDataView = subchunk.contents();
const textureData = new Uint8Array(textureDataView.buffer,
textureDataView.byteOffset,
textureDataView.byteLength);
gl.texImage2D(gl.TEXTURE_2D,
0,
gl.RGBA,
this.objectCount,
1,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
textureData);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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);
} else if (id === 'batc') {
const batch = new BatchBuffers(this.gl,
this.fillProgram,
this.maskTileProgram,
this.quadVertexBuffer);
for (const subsubchunk of subchunk.subchunks()) {
const id = subsubchunk.stringID();
console.log("id=", id);
if (id === 'fill') {
batch.fillPrimitiveCount = uploadArrayBuffer(subsubchunk,
batch.fillVertexBuffer,
FILL_INSTANCE_SIZE);
} else if (id === 'mask') {
batch.maskTileCount = uploadArrayBuffer(subsubchunk,
batch.maskTileVertexBuffer,
MASK_TILE_INSTANCE_SIZE);
}
}
this.batchBuffers.push(batch);
}
function uploadArrayBuffer(chunk: RIFFChunk,
buffer: WebGLBuffer,
instanceSize: number):
number {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, chunk.contents(), gl.DYNAMIC_DRAW);
return chunk.length() / instanceSize;
}
}
this.redraw();
}, false);
reader.readAsArrayBuffer(file);
}
private onClick(event: MouseEvent): void {
this.redraw();
}
}
class BatchBuffers {
fillVertexBuffer: WebGLBuffer;
fillVertexArray: WebGLVertexArrayObject;
maskTileVertexBuffer: WebGLBuffer;
maskVertexArray: WebGLVertexArrayObject;
fillPrimitiveCount: number;
maskTileCount: number;
constructor(gl: WebGL2RenderingContext,
fillProgram: FillProgram,
maskTileProgram: MaskTileProgram,
quadVertexBuffer: WebGLBuffer) {
// Initialize fill VBOs.
this.fillVertexBuffer = unwrapNull(gl.createBuffer());
// Initialize fill VAO.
this.fillVertexArray = unwrapNull(gl.createVertexArray());
gl.bindVertexArray(this.fillVertexArray);
gl.useProgram(this.fillProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
gl.useProgram(fillProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, quadVertexBuffer);
gl.vertexAttribPointer(fillProgram.attributes.TessCoord,
2,
gl.UNSIGNED_BYTE,
@ -263,44 +554,14 @@ class App {
gl.enableVertexAttribArray(fillProgram.attributes.ToSubpx);
gl.enableVertexAttribArray(fillProgram.attributes.TileIndex);
// Initialize tile VBOs and IBOs.
this.solidTileVertexBuffer = unwrapNull(gl.createBuffer());
// Initialize tile VBOs.
this.maskTileVertexBuffer = unwrapNull(gl.createBuffer());
// Initialize solid tile VAO.
this.solidVertexArray = unwrapNull(gl.createVertexArray());
gl.bindVertexArray(this.solidVertexArray);
gl.useProgram(this.solidTileProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
gl.vertexAttribPointer(solidTileProgram.attributes.TessCoord,
2,
gl.UNSIGNED_BYTE,
false,
0,
0);
gl.bindBuffer(gl.ARRAY_BUFFER, this.solidTileVertexBuffer);
gl.vertexAttribPointer(solidTileProgram.attributes.TileOrigin,
2,
gl.SHORT,
false,
SOLID_TILE_INSTANCE_SIZE,
0);
gl.vertexAttribDivisor(solidTileProgram.attributes.TileOrigin, 1);
gl.vertexAttribIPointer(solidTileProgram.attributes.Object,
1,
gl.UNSIGNED_SHORT,
SOLID_TILE_INSTANCE_SIZE,
4);
gl.vertexAttribDivisor(solidTileProgram.attributes.Object, 1);
gl.enableVertexAttribArray(solidTileProgram.attributes.TessCoord);
gl.enableVertexAttribArray(solidTileProgram.attributes.TileOrigin);
gl.enableVertexAttribArray(solidTileProgram.attributes.Object);
// Initialize mask tile VAO.
this.maskVertexArray = unwrapNull(gl.createVertexArray());
gl.bindVertexArray(this.maskVertexArray);
gl.useProgram(this.maskTileProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
gl.useProgram(maskTileProgram.program);
gl.bindBuffer(gl.ARRAY_BUFFER, quadVertexBuffer);
gl.vertexAttribPointer(maskTileProgram.attributes.TessCoord,
2,
gl.UNSIGNED_BYTE,
@ -332,244 +593,8 @@ class App {
gl.enableVertexAttribArray(maskTileProgram.attributes.Backdrop);
gl.enableVertexAttribArray(maskTileProgram.attributes.Object);
this.viewBox = new Rect(new Point2D(0.0, 0.0), new Size2D(0.0, 0.0));
// Set up event handlers.
this.canvas.addEventListener('click', event => this.onClick(event), false);
this.fillPrimitiveCount = 0;
this.solidTileCount = 0;
this.maskTileCount = 0;
this.objectCount = 0;
}
redraw(): void {
const gl = this.gl, canvas = this.canvas;
//console.log("viewBox", this.viewBox);
// Initialize timers.
let fillTimerQuery = null, solidTimerQuery = null, maskTimerQuery = null;
if (this.disjointTimerQueryExt != null) {
fillTimerQuery = unwrapNull(gl.createQuery());
solidTimerQuery = unwrapNull(gl.createQuery());
maskTimerQuery = unwrapNull(gl.createQuery());
}
// Fill.
if (fillTimerQuery != null)
gl.beginQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT, fillTimerQuery);
gl.bindFramebuffer(gl.FRAMEBUFFER, this.stencilFramebuffer);
gl.viewport(0, 0, STENCIL_FRAMEBUFFER_SIZE.width, STENCIL_FRAMEBUFFER_SIZE.height);
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindVertexArray(this.fillVertexArray);
gl.useProgram(this.fillProgram.program);
gl.uniform2f(this.fillProgram.uniforms.FramebufferSize,
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height);
gl.uniform2f(this.fillProgram.uniforms.TileSize, TILE_SIZE.width, TILE_SIZE.height);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.areaLUTTexture);
gl.uniform1i(this.fillProgram.uniforms.AreaLUT, 0);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.ONE, gl.ONE);
gl.enable(gl.BLEND);
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, unwrapNull(this.fillPrimitiveCount));
gl.disable(gl.BLEND);
if (fillTimerQuery != null)
gl.endQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT);
// Read back stencil and dump it.
this.dumpStencil();
// Draw solid tiles.
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
const framebufferSize = {width: canvas.width, height: canvas.height};
gl.viewport(0, 0, framebufferSize.width, framebufferSize.height);
gl.clearColor(0.85, 0.85, 0.85, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
if (solidTimerQuery != null)
gl.beginQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT, solidTimerQuery);
gl.bindVertexArray(this.solidVertexArray);
gl.useProgram(this.solidTileProgram.program);
gl.uniform2f(this.solidTileProgram.uniforms.FramebufferSize,
framebufferSize.width,
framebufferSize.height);
gl.uniform2f(this.solidTileProgram.uniforms.TileSize, TILE_SIZE.width, TILE_SIZE.height);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.fillColorsTexture);
gl.uniform1i(this.solidTileProgram.uniforms.FillColorsTexture, 0);
// FIXME(pcwalton): Maybe this should be an ivec2 or uvec2?
gl.uniform2f(this.solidTileProgram.uniforms.FillColorsTextureSize,
this.objectCount,
1.0);
gl.uniform2f(this.solidTileProgram.uniforms.ViewBoxOrigin,
this.viewBox.origin.x,
this.viewBox.origin.y);
gl.disable(gl.BLEND);
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, this.solidTileCount);
if (solidTimerQuery != null)
gl.endQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT);
// Draw masked tiles.
if (maskTimerQuery != null)
gl.beginQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT, maskTimerQuery);
gl.bindVertexArray(this.maskVertexArray);
gl.useProgram(this.maskTileProgram.program);
gl.uniform2f(this.maskTileProgram.uniforms.FramebufferSize,
framebufferSize.width,
framebufferSize.height);
gl.uniform2f(this.maskTileProgram.uniforms.TileSize, TILE_SIZE.width, TILE_SIZE.height);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.stencilTexture);
gl.uniform1i(this.maskTileProgram.uniforms.StencilTexture, 0);
gl.uniform2f(this.maskTileProgram.uniforms.StencilTextureSize,
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, this.fillColorsTexture);
gl.uniform1i(this.maskTileProgram.uniforms.FillColorsTexture, 1);
// FIXME(pcwalton): Maybe this should be an ivec2 or uvec2?
gl.uniform2f(this.maskTileProgram.uniforms.FillColorsTextureSize,
this.objectCount,
1.0);
gl.uniform2f(this.maskTileProgram.uniforms.ViewBoxOrigin,
this.viewBox.origin.x,
this.viewBox.origin.y);
gl.blendEquation(gl.FUNC_ADD);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, this.maskTileCount);
gl.disable(gl.BLEND);
if (maskTimerQuery != null)
gl.endQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT);
// End timer.
if (fillTimerQuery != null && solidTimerQuery != null && maskTimerQuery != null) {
processQueries(gl, this.disjointTimerQueryExt, {
fill: fillTimerQuery,
solid: solidTimerQuery,
mask: maskTimerQuery,
});
}
}
private dumpStencil(): void {
const gl = this.gl;
const totalStencilFramebufferSize = STENCIL_FRAMEBUFFER_SIZE.width *
STENCIL_FRAMEBUFFER_SIZE.height * 4;
const stencilData = new Float32Array(totalStencilFramebufferSize);
gl.readPixels(0, 0,
STENCIL_FRAMEBUFFER_SIZE.width, STENCIL_FRAMEBUFFER_SIZE.height,
gl.RGBA,
gl.FLOAT,
stencilData);
const stencilDumpData = new Uint8ClampedArray(totalStencilFramebufferSize);
for (let i = 0; i < stencilData.length; i++)
stencilDumpData[i] = stencilData[i] * 255.0;
const stencilDumpCanvas = document.createElement('canvas');
stencilDumpCanvas.width = STENCIL_FRAMEBUFFER_SIZE.width;
stencilDumpCanvas.height = STENCIL_FRAMEBUFFER_SIZE.height;
stencilDumpCanvas.style.width =
(STENCIL_FRAMEBUFFER_SIZE.width / window.devicePixelRatio) + "px";
stencilDumpCanvas.style.height =
(STENCIL_FRAMEBUFFER_SIZE.height / window.devicePixelRatio) + "px";
const stencilDumpCanvasContext = unwrapNull(stencilDumpCanvas.getContext('2d'));
const stencilDumpImageData = new ImageData(stencilDumpData,
STENCIL_FRAMEBUFFER_SIZE.width,
STENCIL_FRAMEBUFFER_SIZE.height);
stencilDumpCanvasContext.putImageData(stencilDumpImageData, 0, 0);
document.body.appendChild(stencilDumpCanvas);
//console.log(stencilData);
}
private loadFile(): void {
console.log("loadFile");
// TODO(pcwalton)
const file = unwrapNull(unwrapNull(this.openButton.files)[0]);
const reader = new FileReader;
reader.addEventListener('loadend', () => {
const gl = this.gl;
const arrayBuffer = staticCast(reader.result, ArrayBuffer);
const root = new RIFFChunk(new DataView(arrayBuffer));
for (const subchunk of root.subchunks()) {
const self = this;
const id = subchunk.stringID();
if (id === 'head') {
const headerData = subchunk.contents();
this.viewBox = new Rect(new Point2D(headerData.getFloat32(0, true),
headerData.getFloat32(4, true)),
new Size2D(headerData.getFloat32(8, true),
headerData.getFloat32(12, true)));
continue;
}
switch (id) {
case 'fill':
uploadArrayBuffer(this.fillVertexBuffer,
'fillPrimitiveCount',
FILL_INSTANCE_SIZE);
break;
case 'soli':
uploadArrayBuffer(this.solidTileVertexBuffer,
'solidTileCount',
SOLID_TILE_INSTANCE_SIZE);
break;
case 'mask':
uploadArrayBuffer(this.maskTileVertexBuffer,
'maskTileCount',
MASK_TILE_INSTANCE_SIZE);
break;
case 'shad':
this.objectCount = subchunk.length() / 4;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.fillColorsTexture);
const textureDataView = subchunk.contents();
const textureData = new Uint8Array(textureDataView.buffer,
textureDataView.byteOffset,
textureDataView.byteLength);
gl.texImage2D(gl.TEXTURE_2D,
0,
gl.RGBA,
this.objectCount,
1,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
textureData);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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);
break;
default:
throw new Error("Unexpected subchunk ID: " + id);
}
type CountFieldName = 'fillPrimitiveCount' | 'solidTileCount' | 'maskTileCount';
function uploadArrayBuffer(buffer: WebGLBuffer,
countFieldName: CountFieldName,
instanceSize: number):
void {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, subchunk.contents(), gl.DYNAMIC_DRAW);
self[countFieldName] = subchunk.length() / instanceSize;
}
}
this.redraw();
}, false);
reader.readAsArrayBuffer(file);
}
private onClick(event: MouseEvent): void {
this.redraw();
}
}
@ -647,10 +672,10 @@ class RIFFChunk {
return new DataView(this.data.buffer, this.data.byteOffset + 8, this.length());
}
subchunks(): RIFFChunk[] {
subchunks(initialOffset?: number | undefined): RIFFChunk[] {
const subchunks = [];
const contents = this.contents(), length = this.length();
let offset = 4;
let offset = initialOffset == null ? 0 : initialOffset;
while (offset < length) {
const subchunk = new RIFFChunk(new DataView(contents.buffer,
contents.byteOffset + offset,
@ -665,7 +690,6 @@ class RIFFChunk {
interface Queries {
fill: WebGLQuery;
solid: WebGLQuery;
mask: WebGLQuery;
};
function getQueryResult(gl: WebGL2RenderingContext, disjointTimerQueryExt: any, query: WebGLQuery):
@ -688,10 +712,9 @@ function processQueries(gl: WebGL2RenderingContext, disjointTimerQueryExt: any,
Promise.all([
getQueryResult(gl, disjointTimerQueryExt, queries.fill),
getQueryResult(gl, disjointTimerQueryExt, queries.solid),
getQueryResult(gl, disjointTimerQueryExt, queries.mask),
]).then(results => {
const [fillResult, solidResult, maskResult] = results;
console.log(fillResult, "ms fill,", solidResult, "ms solid,", maskResult, "ms mask");
const [fillResult, solidResult] = results;
console.log(fillResult, "ms fill/mask,", solidResult, "ms solid");
});
}

View File

@ -10,6 +10,7 @@ byteorder = "1.2"
clap = "2.32"
euclid = "0.19"
fixedbitset = "0.1"
hashbrown = "0.1"
jemallocator = "0.1"
lyon_geom = "0.12"
lyon_path = "0.12"

View File

@ -20,6 +20,7 @@ use byteorder::{LittleEndian, WriteBytesExt};
use clap::{App, Arg};
use euclid::{Point2D, Rect, Size2D, Transform2D, Vector2D};
use fixedbitset::FixedBitSet;
use hashbrown::HashMap;
use jemallocator;
use lyon_geom::cubic_bezier::Flattened;
use lyon_geom::math::Transform;
@ -28,7 +29,7 @@ use lyon_path::PathEvent;
use lyon_path::iterator::PathIter;
use pathfinder_path_utils::stroke::{StrokeStyle, StrokeToFillIter};
use rayon::ThreadPoolBuilder;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use std::cmp::Ordering;
use std::fmt::{self, Debug, Formatter};
use std::fs::File;
@ -38,8 +39,8 @@ use std::path::PathBuf;
use std::time::Instant;
use std::u16;
use svgtypes::Color as SvgColor;
use usvg::{Node, NodeExt, NodeKind, Options as UsvgOptions, Paint, PathSegment as UsvgPathSegment};
use usvg::{Rect as UsvgRect, Transform as UsvgTransform, Tree};
use usvg::{Node, NodeExt, NodeKind, Options as UsvgOptions, Paint as UsvgPaint};
use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform, Tree};
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
@ -90,44 +91,35 @@ fn main() {
let usvg = Tree::from_file(&input_path, &UsvgOptions::default()).unwrap();
let scene = Scene::from_tree(usvg);
println!("Scene bounds: {:?} View box: {:?} Object count: {}",
scene.bounds,
scene.view_box,
scene.objects.len());
//println!("{:#?}", scene.objects[0]);
println!("Scene bounds: {:?} View box: {:?}", scene.bounds, scene.view_box);
println!("{} objects, {} paints", scene.objects.len(), scene.paints.len());
let start_time = Instant::now();
let mut built_scene = BuiltScene::new(&scene.view_box, scene.objects.len() as u32);
let mut built_scene = BuiltScene::new(&scene.view_box, vec![]);
for _ in 0..runs {
let built_objects = match jobs {
Some(1) => scene.build_objects_sequentially(),
_ => scene.build_objects(),
};
built_scene = BuiltScene::from_objects(&scene.view_box, &built_objects);
let built_shaders = scene.build_shaders();
built_scene = BuiltScene::from_objects_and_shaders(&scene.view_box,
&built_objects,
built_shaders);
}
let elapsed_time = Instant::now() - start_time;
let elapsed_ms = elapsed_time.as_secs() as f64 * 1000.0 +
elapsed_time.subsec_micros() as f64 / 1000.0;
println!("{:.3}ms elapsed", elapsed_ms / runs as f64);
println!("{} fill primitives generated", built_scene.fills.len());
println!("{} tiles ({} solid, {} mask) generated",
built_scene.solid_tiles.len() + built_scene.mask_tiles.len(),
built_scene.solid_tiles.len(),
built_scene.mask_tiles.len());
/*
println!("solid tiles:");
for (index, tile) in built_scene.solid_tiles.iter().enumerate() {
println!("... {}: {:?}", index, tile);
println!("{} solid tiles", built_scene.solid_tiles.len());
for (batch_index, batch) in built_scene.batches.iter().enumerate() {
println!("Batch {}: {} fills, {} mask tiles",
batch_index,
batch.fills.len(),
batch.mask_tiles.len());
}
println!("fills:");
for (index, fill) in built_scene.fills.iter().enumerate() {
println!("... {}: {:?}", index, fill);
}
*/
if let Some(output_path) = output_path {
built_scene.write(&mut BufWriter::new(File::create(output_path).unwrap())).unwrap();
}
@ -136,7 +128,8 @@ fn main() {
#[derive(Debug)]
struct Scene {
objects: Vec<PathObject>,
styles: Vec<ComputedStyle>,
paints: Vec<Paint>,
paint_cache: HashMap<Paint, PaintId>,
bounds: Rect<f32>,
view_box: Rect<f32>,
}
@ -144,7 +137,7 @@ struct Scene {
#[derive(Debug)]
struct PathObject {
outline: Outline,
style: StyleId,
paint: PaintId,
name: String,
kind: PathObjectKind,
}
@ -155,17 +148,23 @@ pub enum PathObjectKind {
Stroke,
}
#[derive(Debug)]
struct ComputedStyle {
color: Option<SvgColor>,
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
struct Paint {
color: ColorU,
}
#[derive(Clone, Copy, PartialEq, Debug)]
struct StyleId(u32);
struct PaintId(u16);
impl Scene {
fn new() -> Scene {
Scene { objects: vec![], styles: vec![], bounds: Rect::zero(), view_box: Rect::zero() }
Scene {
objects: vec![],
paints: vec![],
paint_cache: HashMap::new(),
bounds: Rect::zero(),
view_box: Rect::zero(),
}
}
fn from_tree(tree: Tree) -> Scene {
@ -184,6 +183,10 @@ impl Scene {
_ => unreachable!(),
};
// FIXME(pcwalton): This is needed to avoid stack exhaustion in debug builds when
// recursively dropping reference counts on very large SVGs. :(
mem::forget(tree);
return scene;
fn process_node(scene: &mut Scene, node: &Node, transform: &Transform2D<f32>) {
@ -198,7 +201,7 @@ impl Scene {
}
NodeKind::Path(ref path) => {
if let Some(ref fill) = path.fill {
let style = scene.push_paint(&fill.paint);
let style = scene.push_paint(&Paint::from_svg_paint(&fill.paint));
let path = UsvgPathToPathEvents::new(path.segments.iter().cloned());
let path = PathTransformingIter::new(path, &transform);
@ -213,7 +216,7 @@ impl Scene {
}
if let Some(ref stroke) = path.stroke {
let style = scene.push_paint(&stroke.paint);
let style = scene.push_paint(&Paint::from_svg_paint(&stroke.paint));
let stroke_width = f32::max(stroke.width.value() as f32,
HAIRLINE_STROKE_WIDTH);
@ -236,153 +239,43 @@ impl Scene {
}
}
}
/*
loop {
match reader.read_event(&mut xml_buffer) {
Ok(Event::Start(ref event)) |
Ok(Event::Empty(ref event)) if event.name() == b"path" => {
scene.push_group_style(&mut reader, event, &mut group_styles, &mut style);
let attributes = event.attributes();
let (mut encoded_path, mut name) = (String::new(), String::new());
for attribute in attributes {
let attribute = attribute.unwrap();
if attribute.key == b"d" {
encoded_path = reader.decode(&attribute.value).to_string();
} else if attribute.key == b"id" {
name = reader.decode(&attribute.value).to_string();
}
}
let computed_style = scene.ensure_style(&mut style, &mut group_styles);
scene.push_svg_path(&encoded_path, computed_style, name);
group_styles.pop();
style = None;
fn push_paint(&mut self, paint: &Paint) -> PaintId {
if let Some(paint_id) = self.paint_cache.get(paint) {
return *paint_id
}
Ok(Event::Start(ref event)) if event.name() == b"g" => {
scene.push_group_style(&mut reader, event, &mut group_styles, &mut style);
let paint_id = PaintId(self.paints.len() as u16);
self.paint_cache.insert(*paint, paint_id);
self.paints.push(*paint);
paint_id
}
Ok(Event::End(ref event)) if event.name() == b"g" => {
group_styles.pop();
style = None;
fn build_shaders(&self) -> Vec<ObjectShader> {
self.paints.iter().map(|paint| ObjectShader { fill_color: paint.color }).collect()
}
Ok(Event::Start(ref event)) if event.name() == b"svg" => {
let attributes = event.attributes();
for attribute in attributes {
let attribute = attribute.unwrap();
if attribute.key == b"viewBox" {
let view_box = reader.decode(&attribute.value);
let mut elements = view_box.split_whitespace()
.map(|value| f32::from_str(value).unwrap());
let view_box = Rect::new(Point2D::new(elements.next().unwrap(),
elements.next().unwrap()),
Size2D::new(elements.next().unwrap(),
elements.next().unwrap()));
scene.view_box = global_transform.transform_rect(&view_box);
}
}
}
Ok(Event::Eof) | Err(_) => break,
Ok(_) => {}
}
xml_buffer.clear();
}
return scene;
*/
}
fn push_paint(&mut self, paint: &Paint) -> StyleId {
let id = StyleId(self.styles.len() as u32);
self.styles.push(ComputedStyle {
color: match *paint {
Paint::Color(color) => Some(color),
Paint::Link(..) => None,
}
});
id
}
fn get_style(&self, style: StyleId) -> &ComputedStyle {
&self.styles[style.0 as usize]
}
fn build_shader(&self, object_index: u16) -> ObjectShader {
let object = &self.objects[object_index as usize];
let style = self.get_style(object.style);
let color = style.color.map(ColorU::from_svg_color).unwrap_or(ColorU::black());
ObjectShader { fill_color: color }
}
// This function exists to make profiling easier.
fn build_objects_sequentially(&self) -> Vec<BuiltObject> {
self.objects.iter().enumerate().map(|(object_index, object)| {
let mut tiler = Tiler::new(&object.outline,
object_index as u16,
&self.view_box,
&self.build_shader(object_index as u16));
self.objects.iter().map(|object| {
let mut tiler = Tiler::new(&object.outline, &self.view_box, ShaderId(object.paint.0));
tiler.generate_tiles();
tiler.built_object
}).collect()
}
fn build_objects(&self) -> Vec<BuiltObject> {
self.objects.par_iter().enumerate().map(|(object_index, object)| {
let mut tiler = Tiler::new(&object.outline,
object_index as u16,
&self.view_box,
&self.build_shader(object_index as u16));
self.objects.par_iter().map(|object| {
let mut tiler = Tiler::new(&object.outline, &self.view_box, ShaderId(object.paint.0));
tiler.generate_tiles();
tiler.built_object
}).collect()
}
/*
fn push_svg_path(&mut self, value: &str, style: StyleId, name: String) {
let global_transform = Transform2D::create_scale(SCALE_FACTOR, SCALE_FACTOR);
let transform = global_transform.pre_mul(&self.get_style(style).transform);
if self.get_style(style).fill_color.is_some() {
let computed_style = self.get_style(style);
let mut path_parser = PathParser::from(&*value);
let path = SvgPathToPathEvents::new(&mut path_parser);
let path = PathTransformingIter::new(path, &transform);
let path = MonotonicConversionIter::new(path);
let outline = Outline::from_path_events(path);
self.bounds = self.bounds.union(&outline.bounds);
self.objects.push(PathObject::new(outline, style, name.clone(), PathObjectKind::Fill));
}
if self.get_style(style).stroke_color.is_some() {
let computed_style = self.get_style(style);
let stroke_width = f32::max(computed_style.stroke_width, HAIRLINE_STROKE_WIDTH);
let mut path_parser = PathParser::from(&*value);
let path = SvgPathToPathEvents::new(&mut path_parser);
let path = PathIter::new(path);
let path = StrokeToFillIter::new(path, StrokeStyle::new(stroke_width));
let path = PathTransformingIter::new(path, &transform);
let path = MonotonicConversionIter::new(path);
let outline = Outline::from_path_events(path);
self.bounds = self.bounds.union(&outline.bounds);
self.objects.push(PathObject::new(outline, style, name, PathObjectKind::Stroke));
}
}
*/
}
impl PathObject {
fn new(outline: Outline, style: StyleId, name: String, kind: PathObjectKind) -> PathObject {
PathObject { outline, style, name, kind }
fn new(outline: Outline, paint: PaintId, name: String, kind: PathObjectKind) -> PathObject {
PathObject { outline, paint, name, kind }
}
}
@ -649,6 +542,8 @@ struct PointIndex(u32);
impl PointIndex {
fn new(contour: u32, point: u32) -> PointIndex {
debug_assert!(contour <= 0xfff);
debug_assert!(point <= 0x000fffff);
PointIndex((contour << 20) | point)
}
@ -783,15 +678,7 @@ impl Segment {
}
}
fn is_degenerate(&self) -> bool {
return f32::abs(self.to.x - self.from.x) < EPSILON ||
f32::abs(self.to.y - self.from.y) < EPSILON;
const EPSILON: f32 = 0.0001;
}
fn split_y(&self, y: f32) -> (Option<Segment>, Option<Segment>) {
// Trivial cases.
if self.from.y <= y && self.to.y <= y {
return (Some(*self), None)
@ -903,7 +790,6 @@ const TILE_HEIGHT: f32 = 16.0;
struct Tiler<'o> {
outline: &'o Outline,
object_index: u16,
built_object: BuiltObject,
view_box: Rect<f32>,
@ -915,14 +801,12 @@ struct Tiler<'o> {
}
impl<'o> Tiler<'o> {
fn new(outline: &'o Outline, object_index: u16, view_box: &Rect<f32>, shader: &ObjectShader)
-> Tiler<'o> {
fn new(outline: &'o Outline, view_box: &Rect<f32>, shader: ShaderId) -> Tiler<'o> {
let bounds = outline.bounds.intersection(&view_box).unwrap_or(Rect::zero());
let built_object = BuiltObject::new(&bounds, shader);
Tiler {
outline,
object_index,
built_object,
view_box: *view_box,
@ -1044,7 +928,7 @@ impl<'o> Tiler<'o> {
}
}
debug_assert_eq!(current_winding, 0);
//debug_assert_eq!(current_winding, 0);
}
fn add_new_active_edge(&mut self, tile_y: i16) {
@ -1136,10 +1020,6 @@ fn process_active_segment(contour: &Contour,
built_object: &mut BuiltObject,
tile_y: i16) {
let mut segment = contour.segment_after(from_endpoint_index);
/*if segment.is_degenerate() {
return
}*/
process_active_edge(&mut segment, built_object, tile_y);
if !segment.is_none() {
@ -1164,20 +1044,23 @@ fn process_active_edge(active_edge: &mut Segment, built_object: &mut BuiltObject
// Scene construction
impl BuiltScene {
fn new(view_box: &Rect<f32>, object_count: u32) -> BuiltScene {
fn new(view_box: &Rect<f32>, shaders: Vec<ObjectShader>) -> BuiltScene {
BuiltScene {
view_box: *view_box,
fills: vec![],
batches: vec![],
solid_tiles: vec![],
mask_tiles: vec![],
shaders: vec![ObjectShader::default(); object_count as usize],
shaders,
tile_rect: round_rect_out_to_tile_bounds(view_box),
}
}
fn from_objects(view_box: &Rect<f32>, objects: &[BuiltObject]) -> BuiltScene {
let mut scene = BuiltScene::new(view_box, objects.len() as u32);
fn from_objects_and_shaders(view_box: &Rect<f32>,
objects: &[BuiltObject],
shaders: Vec<ObjectShader>)
-> BuiltScene {
let mut scene = BuiltScene::new(view_box, shaders);
scene.add_batch();
let tile_area = scene.tile_rect.size.width as usize * scene.tile_rect.size.height as usize;
let mut z_buffer = vec![0; tile_area];
@ -1192,20 +1075,21 @@ impl BuiltScene {
}
let scene_tile_index = scene.scene_tile_index(tile.tile_x, tile.tile_y);
if z_buffer[scene_tile_index as usize] > object_index as u16 {
if z_buffer[scene_tile_index as usize] > object_index as u32 {
// Occluded.
continue
}
z_buffer[scene_tile_index as usize] = object_index as u16;
z_buffer[scene_tile_index as usize] = object_index as u32;
scene.solid_tiles.push(SolidTileScenePrimitive {
tile_x: tile.tile_x,
tile_y: tile.tile_y,
object_index: object_index as u16,
shader: object.shader,
});
}
}
// Build batches.
let mut object_tile_index_to_scene_mask_tile_index = vec![];
for (object_index, object) in objects.iter().enumerate() {
object_tile_index_to_scene_mask_tile_index.clear();
@ -1215,54 +1099,77 @@ impl BuiltScene {
for (tile_index, tile) in object.tiles.iter().enumerate() {
// Skip solid tiles, since we handled them above already.
if object.solid_tiles[tile_index] {
object_tile_index_to_scene_mask_tile_index.push(u16::MAX);
object_tile_index_to_scene_mask_tile_index.push(BLANK);
continue;
}
// Cull occluded tiles.
let scene_tile_index = scene.scene_tile_index(tile.tile_x, tile.tile_y);
if z_buffer[scene_tile_index as usize] as usize > object_index {
object_tile_index_to_scene_mask_tile_index.push(u16::MAX);
object_tile_index_to_scene_mask_tile_index.push(BLANK);
continue;
}
// Visible mask tile.
let scene_mask_tile_index = scene.mask_tiles.len() as u16;
object_tile_index_to_scene_mask_tile_index.push(scene_mask_tile_index);
scene.mask_tiles.push(MaskTileScenePrimitive {
let mut scene_mask_tile_index = scene.batches.last().unwrap().mask_tiles.len() as
u16;
if scene_mask_tile_index == u16::MAX {
scene.add_batch();
scene_mask_tile_index = 0;
}
object_tile_index_to_scene_mask_tile_index.push(SceneMaskTileIndex {
batch_index: scene.batches.len() as u16 - 1,
mask_tile_index: scene_mask_tile_index,
});
scene.batches.last_mut().unwrap().mask_tiles.push(MaskTileBatchPrimitive {
tile: *tile,
object_index: object_index as u16,
shader: object.shader,
});
}
// Remap and copy fills, culling as necessary.
for fill in &object.fills {
let object_tile_index = object.tile_coords_to_index(fill.tile_x, fill.tile_y);
match object_tile_index_to_scene_mask_tile_index[object_tile_index as usize] {
u16::MAX => {}
scene_mask_tile_index => {
scene.fills.push(FillScenePrimitive {
let SceneMaskTileIndex {
batch_index,
mask_tile_index,
} = object_tile_index_to_scene_mask_tile_index[object_tile_index as usize];
if batch_index < u16::MAX {
scene.batches[batch_index as usize].fills.push(FillBatchPrimitive {
from_px: fill.from_px,
to_px: fill.to_px,
from_subpx: fill.from_subpx,
to_subpx: fill.to_subpx,
mask_tile_index: scene_mask_tile_index,
})
mask_tile_index,
});
}
}
}
// Copy shader.
scene.shaders[object_index as usize] = object.shader;
return scene;
#[derive(Clone, Copy, Debug)]
struct SceneMaskTileIndex {
batch_index: u16,
mask_tile_index: u16,
}
scene
const BLANK: SceneMaskTileIndex = SceneMaskTileIndex {
batch_index: 0,
mask_tile_index: 0,
};
}
fn scene_tile_index(&self, tile_x: i16, tile_y: i16) -> u32 {
(tile_y - self.tile_rect.origin.y) as u32 * self.tile_rect.size.width as u32 +
(tile_x - self.tile_rect.origin.x) as u32
}
fn add_batch(&mut self) {
self.batches.push(Batch::new());
}
}
// Primitives
@ -1274,20 +1181,25 @@ struct BuiltObject {
tiles: Vec<TileObjectPrimitive>,
fills: Vec<FillObjectPrimitive>,
solid_tiles: FixedBitSet,
shader: ObjectShader,
shader: ShaderId,
}
#[derive(Debug)]
struct BuiltScene {
view_box: Rect<f32>,
fills: Vec<FillScenePrimitive>,
batches: Vec<Batch>,
solid_tiles: Vec<SolidTileScenePrimitive>,
mask_tiles: Vec<MaskTileScenePrimitive>,
shaders: Vec<ObjectShader>,
tile_rect: Rect<i16>,
}
#[derive(Debug)]
struct Batch {
fills: Vec<FillBatchPrimitive>,
mask_tiles: Vec<MaskTileBatchPrimitive>,
}
#[derive(Clone, Copy, Debug)]
struct FillObjectPrimitive {
from_px: Point2DU4,
@ -1306,7 +1218,7 @@ struct TileObjectPrimitive {
}
#[derive(Clone, Copy, Debug)]
struct FillScenePrimitive {
struct FillBatchPrimitive {
from_px: Point2DU4,
to_px: Point2DU4,
from_subpx: Point2D<u8>,
@ -1318,21 +1230,24 @@ struct FillScenePrimitive {
struct SolidTileScenePrimitive {
tile_x: i16,
tile_y: i16,
object_index: u16,
shader: ShaderId,
}
#[derive(Clone, Copy, Debug)]
struct MaskTileScenePrimitive {
struct MaskTileBatchPrimitive {
tile: TileObjectPrimitive,
object_index: u16,
shader: ShaderId,
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct ShaderId(pub u16);
#[derive(Clone, Copy, Debug, Default)]
struct ObjectShader {
fill_color: ColorU,
}
#[derive(Clone, Copy, Debug, Default)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
struct ColorU {
r: u8,
g: u8,
@ -1343,7 +1258,7 @@ struct ColorU {
// Utilities for built objects
impl BuiltObject {
fn new(bounds: &Rect<f32>, shader: &ObjectShader) -> BuiltObject {
fn new(bounds: &Rect<f32>, shader: ShaderId) -> BuiltObject {
// Compute the tile rect.
let tile_rect = round_rect_out_to_tile_bounds(&bounds);
@ -1365,7 +1280,7 @@ impl BuiltObject {
tiles,
fills: vec![],
solid_tiles,
shader: *shader,
shader,
}
}
@ -1442,41 +1357,63 @@ impl BuiltObject {
}
}
impl Paint {
fn from_svg_paint(svg_paint: &UsvgPaint) -> Paint {
Paint {
color: match *svg_paint {
UsvgPaint::Color(color) => ColorU::from_svg_color(color),
UsvgPaint::Link(_) => {
// TODO(pcwalton)
ColorU::black()
}
},
}
}
}
// Scene serialization
impl BuiltScene {
fn write<W>(&self, writer: &mut W) -> io::Result<()> where W: Write {
writer.write_all(b"RIFF")?;
let header_size = 4 * 4;
let fill_size = self.fills.len() * mem::size_of::<FillScenePrimitive>();
let header_size = 4 * 6;
let solid_tiles_size = self.solid_tiles.len() * mem::size_of::<SolidTileScenePrimitive>();
let mask_tiles_size = self.mask_tiles.len() * mem::size_of::<MaskTileScenePrimitive>();
let batch_sizes: Vec<_> = self.batches.iter().map(|batch| {
BatchSizes {
fills: (batch.fills.len() * mem::size_of::<FillBatchPrimitive>()),
mask_tiles: (batch.mask_tiles.len() * mem::size_of::<MaskTileBatchPrimitive>()),
}
}).collect();
let total_batch_sizes: usize = batch_sizes.iter().map(|sizes| 8 + sizes.total()).sum();
let shaders_size = self.shaders.len() * mem::size_of::<ObjectShader>();
writer.write_u32::<LittleEndian>((4 +
8 + header_size +
8 + fill_size +
8 + solid_tiles_size +
8 + mask_tiles_size +
8 + shaders_size) as u32)?;
8 + shaders_size +
total_batch_sizes) as u32)?;
writer.write_all(b"PF3S")?;
writer.write_all(b"head")?;
writer.write_u32::<LittleEndian>(header_size as u32)?;
writer.write_u32::<LittleEndian>(FILE_VERSION)?;
writer.write_u32::<LittleEndian>(self.batches.len() as u32)?;
writer.write_f32::<LittleEndian>(self.view_box.origin.x)?;
writer.write_f32::<LittleEndian>(self.view_box.origin.y)?;
writer.write_f32::<LittleEndian>(self.view_box.size.width)?;
writer.write_f32::<LittleEndian>(self.view_box.size.height)?;
writer.write_all(b"fill")?;
writer.write_u32::<LittleEndian>(fill_size as u32)?;
for fill_primitive in &self.fills {
writer.write_u8(fill_primitive.from_px.0)?;
writer.write_u8(fill_primitive.to_px.0)?;
write_point2d_u8(writer, fill_primitive.from_subpx)?;
write_point2d_u8(writer, fill_primitive.to_subpx)?;
writer.write_u16::<LittleEndian>(fill_primitive.mask_tile_index)?;
writer.write_all(b"shad")?;
writer.write_u32::<LittleEndian>(shaders_size as u32)?;
for &shader in &self.shaders {
let fill_color = shader.fill_color;
writer.write_all(&[fill_color.r, fill_color.g, fill_color.b, fill_color.a])?;
}
writer.write_all(b"soli")?;
@ -1484,23 +1421,31 @@ impl BuiltScene {
for &tile_primitive in &self.solid_tiles {
writer.write_i16::<LittleEndian>(tile_primitive.tile_x)?;
writer.write_i16::<LittleEndian>(tile_primitive.tile_y)?;
writer.write_u16::<LittleEndian>(tile_primitive.object_index)?;
writer.write_u16::<LittleEndian>(tile_primitive.shader.0)?;
}
for (batch, sizes) in self.batches.iter().zip(batch_sizes.iter()) {
writer.write_all(b"batc")?;
writer.write_u32::<LittleEndian>(sizes.total() as u32)?;
writer.write_all(b"fill")?;
writer.write_u32::<LittleEndian>(sizes.fills as u32)?;
for fill_primitive in &batch.fills {
writer.write_u8(fill_primitive.from_px.0)?;
writer.write_u8(fill_primitive.to_px.0)?;
write_point2d_u8(writer, fill_primitive.from_subpx)?;
write_point2d_u8(writer, fill_primitive.to_subpx)?;
writer.write_u16::<LittleEndian>(fill_primitive.mask_tile_index)?;
}
writer.write_all(b"mask")?;
writer.write_u32::<LittleEndian>(mask_tiles_size as u32)?;
for &tile_primitive in &self.mask_tiles {
writer.write_u32::<LittleEndian>(sizes.mask_tiles as u32)?;
for &tile_primitive in &batch.mask_tiles {
writer.write_i16::<LittleEndian>(tile_primitive.tile.tile_x)?;
writer.write_i16::<LittleEndian>(tile_primitive.tile.tile_y)?;
writer.write_i16::<LittleEndian>(tile_primitive.tile.backdrop)?;
writer.write_u16::<LittleEndian>(tile_primitive.object_index)?;
writer.write_u16::<LittleEndian>(tile_primitive.shader.0)?;
}
writer.write_all(b"shad")?;
writer.write_u32::<LittleEndian>(shaders_size as u32)?;
for &shader in &self.shaders {
let fill_color = shader.fill_color;
writer.write_all(&[fill_color.r, fill_color.g, fill_color.b, fill_color.a])?;
}
return Ok(());
@ -1511,12 +1456,25 @@ impl BuiltScene {
writer.write_u8(point.y)?;
Ok(())
}
const FILE_VERSION: u32 = 0;
struct BatchSizes {
fills: usize,
mask_tiles: usize,
}
impl BatchSizes {
fn total(&self) -> usize {
8 + self.fills + 8 + self.mask_tiles
}
}
}
}
impl SolidTileScenePrimitive {
fn new(tile_x: i16, tile_y: i16, object_index: u16) -> SolidTileScenePrimitive {
SolidTileScenePrimitive { tile_x, tile_y, object_index }
impl Batch {
fn new() -> Batch {
Batch { fills: vec![], mask_tiles: vec![] }
}
}