Merge identical shaders into one
This commit is contained in:
parent
eb7d3b46ef
commit
2109049269
|
@ -608,6 +608,15 @@ name = "half"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
|
@ -1517,6 +1526,7 @@ dependencies = [
|
||||||
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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)",
|
"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_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)",
|
"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 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 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 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 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 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"
|
"checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2"
|
||||||
|
|
|
@ -22,7 +22,7 @@ import {staticCast, unwrapNull} from "./util";
|
||||||
const SVGPath: (path: string) => SVGPath = require('svgpath');
|
const SVGPath: (path: string) => SVGPath = require('svgpath');
|
||||||
|
|
||||||
const STENCIL_FRAMEBUFFER_SIZE: Size2D = {
|
const STENCIL_FRAMEBUFFER_SIZE: Size2D = {
|
||||||
width: TILE_SIZE.width * 128,
|
width: TILE_SIZE.width * 256,
|
||||||
height: TILE_SIZE.height * 256,
|
height: TILE_SIZE.height * 256,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,6 +46,23 @@ interface Color {
|
||||||
|
|
||||||
type Edge = 'left' | 'top' | 'right' | 'bottom';
|
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 {
|
class App {
|
||||||
private canvas: HTMLCanvasElement;
|
private canvas: HTMLCanvasElement;
|
||||||
private openButton: HTMLInputElement;
|
private openButton: HTMLInputElement;
|
||||||
|
@ -57,36 +74,17 @@ class App {
|
||||||
private fillColorsTexture: WebGLTexture;
|
private fillColorsTexture: WebGLTexture;
|
||||||
private stencilTexture: WebGLTexture;
|
private stencilTexture: WebGLTexture;
|
||||||
private stencilFramebuffer: WebGLFramebuffer;
|
private stencilFramebuffer: WebGLFramebuffer;
|
||||||
private fillProgram: Program<'FramebufferSize' | 'TileSize' | 'AreaLUT',
|
private fillProgram: FillProgram;
|
||||||
'TessCoord' |
|
private solidTileProgram: SolidTileProgram;
|
||||||
'FromPx' | 'ToPx' |
|
private maskTileProgram: MaskTileProgram;
|
||||||
'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 quadVertexBuffer: WebGLBuffer;
|
private quadVertexBuffer: WebGLBuffer;
|
||||||
private fillVertexBuffer: WebGLBuffer;
|
|
||||||
private fillVertexArray: WebGLVertexArrayObject;
|
|
||||||
private solidTileVertexBuffer: WebGLBuffer;
|
private solidTileVertexBuffer: WebGLBuffer;
|
||||||
private solidVertexArray: WebGLVertexArrayObject;
|
private solidVertexArray: WebGLVertexArrayObject;
|
||||||
private maskTileVertexBuffer: WebGLBuffer;
|
private batchBuffers: BatchBuffers[];
|
||||||
private maskVertexArray: WebGLVertexArrayObject;
|
|
||||||
|
|
||||||
private viewBox: Rect;
|
private viewBox: Rect;
|
||||||
|
|
||||||
private fillPrimitiveCount: number;
|
|
||||||
private solidTileCount: number;
|
private solidTileCount: number;
|
||||||
private maskTileCount: number;
|
|
||||||
private objectCount: number;
|
private objectCount: number;
|
||||||
|
|
||||||
constructor(areaLUT: HTMLImageElement) {
|
constructor(areaLUT: HTMLImageElement) {
|
||||||
|
@ -209,14 +207,307 @@ class App {
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTEX_POSITIONS, gl.STATIC_DRAW);
|
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.
|
// Initialize fill VBOs.
|
||||||
this.fillVertexBuffer = unwrapNull(gl.createBuffer());
|
this.fillVertexBuffer = unwrapNull(gl.createBuffer());
|
||||||
|
|
||||||
// Initialize fill VAO.
|
// Initialize fill VAO.
|
||||||
this.fillVertexArray = unwrapNull(gl.createVertexArray());
|
this.fillVertexArray = unwrapNull(gl.createVertexArray());
|
||||||
gl.bindVertexArray(this.fillVertexArray);
|
gl.bindVertexArray(this.fillVertexArray);
|
||||||
gl.useProgram(this.fillProgram.program);
|
gl.useProgram(fillProgram.program);
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, quadVertexBuffer);
|
||||||
gl.vertexAttribPointer(fillProgram.attributes.TessCoord,
|
gl.vertexAttribPointer(fillProgram.attributes.TessCoord,
|
||||||
2,
|
2,
|
||||||
gl.UNSIGNED_BYTE,
|
gl.UNSIGNED_BYTE,
|
||||||
|
@ -263,44 +554,14 @@ class App {
|
||||||
gl.enableVertexAttribArray(fillProgram.attributes.ToSubpx);
|
gl.enableVertexAttribArray(fillProgram.attributes.ToSubpx);
|
||||||
gl.enableVertexAttribArray(fillProgram.attributes.TileIndex);
|
gl.enableVertexAttribArray(fillProgram.attributes.TileIndex);
|
||||||
|
|
||||||
// Initialize tile VBOs and IBOs.
|
// Initialize tile VBOs.
|
||||||
this.solidTileVertexBuffer = unwrapNull(gl.createBuffer());
|
|
||||||
this.maskTileVertexBuffer = unwrapNull(gl.createBuffer());
|
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.
|
// Initialize mask tile VAO.
|
||||||
this.maskVertexArray = unwrapNull(gl.createVertexArray());
|
this.maskVertexArray = unwrapNull(gl.createVertexArray());
|
||||||
gl.bindVertexArray(this.maskVertexArray);
|
gl.bindVertexArray(this.maskVertexArray);
|
||||||
gl.useProgram(this.maskTileProgram.program);
|
gl.useProgram(maskTileProgram.program);
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertexBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, quadVertexBuffer);
|
||||||
gl.vertexAttribPointer(maskTileProgram.attributes.TessCoord,
|
gl.vertexAttribPointer(maskTileProgram.attributes.TessCoord,
|
||||||
2,
|
2,
|
||||||
gl.UNSIGNED_BYTE,
|
gl.UNSIGNED_BYTE,
|
||||||
|
@ -332,244 +593,8 @@ class App {
|
||||||
gl.enableVertexAttribArray(maskTileProgram.attributes.Backdrop);
|
gl.enableVertexAttribArray(maskTileProgram.attributes.Backdrop);
|
||||||
gl.enableVertexAttribArray(maskTileProgram.attributes.Object);
|
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.fillPrimitiveCount = 0;
|
||||||
this.solidTileCount = 0;
|
|
||||||
this.maskTileCount = 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());
|
return new DataView(this.data.buffer, this.data.byteOffset + 8, this.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
subchunks(): RIFFChunk[] {
|
subchunks(initialOffset?: number | undefined): RIFFChunk[] {
|
||||||
const subchunks = [];
|
const subchunks = [];
|
||||||
const contents = this.contents(), length = this.length();
|
const contents = this.contents(), length = this.length();
|
||||||
let offset = 4;
|
let offset = initialOffset == null ? 0 : initialOffset;
|
||||||
while (offset < length) {
|
while (offset < length) {
|
||||||
const subchunk = new RIFFChunk(new DataView(contents.buffer,
|
const subchunk = new RIFFChunk(new DataView(contents.buffer,
|
||||||
contents.byteOffset + offset,
|
contents.byteOffset + offset,
|
||||||
|
@ -665,7 +690,6 @@ class RIFFChunk {
|
||||||
interface Queries {
|
interface Queries {
|
||||||
fill: WebGLQuery;
|
fill: WebGLQuery;
|
||||||
solid: WebGLQuery;
|
solid: WebGLQuery;
|
||||||
mask: WebGLQuery;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getQueryResult(gl: WebGL2RenderingContext, disjointTimerQueryExt: any, query: WebGLQuery):
|
function getQueryResult(gl: WebGL2RenderingContext, disjointTimerQueryExt: any, query: WebGLQuery):
|
||||||
|
@ -688,10 +712,9 @@ function processQueries(gl: WebGL2RenderingContext, disjointTimerQueryExt: any,
|
||||||
Promise.all([
|
Promise.all([
|
||||||
getQueryResult(gl, disjointTimerQueryExt, queries.fill),
|
getQueryResult(gl, disjointTimerQueryExt, queries.fill),
|
||||||
getQueryResult(gl, disjointTimerQueryExt, queries.solid),
|
getQueryResult(gl, disjointTimerQueryExt, queries.solid),
|
||||||
getQueryResult(gl, disjointTimerQueryExt, queries.mask),
|
|
||||||
]).then(results => {
|
]).then(results => {
|
||||||
const [fillResult, solidResult, maskResult] = results;
|
const [fillResult, solidResult] = results;
|
||||||
console.log(fillResult, "ms fill,", solidResult, "ms solid,", maskResult, "ms mask");
|
console.log(fillResult, "ms fill/mask,", solidResult, "ms solid");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ byteorder = "1.2"
|
||||||
clap = "2.32"
|
clap = "2.32"
|
||||||
euclid = "0.19"
|
euclid = "0.19"
|
||||||
fixedbitset = "0.1"
|
fixedbitset = "0.1"
|
||||||
|
hashbrown = "0.1"
|
||||||
jemallocator = "0.1"
|
jemallocator = "0.1"
|
||||||
lyon_geom = "0.12"
|
lyon_geom = "0.12"
|
||||||
lyon_path = "0.12"
|
lyon_path = "0.12"
|
||||||
|
|
|
@ -20,6 +20,7 @@ use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use euclid::{Point2D, Rect, Size2D, Transform2D, Vector2D};
|
use euclid::{Point2D, Rect, Size2D, Transform2D, Vector2D};
|
||||||
use fixedbitset::FixedBitSet;
|
use fixedbitset::FixedBitSet;
|
||||||
|
use hashbrown::HashMap;
|
||||||
use jemallocator;
|
use jemallocator;
|
||||||
use lyon_geom::cubic_bezier::Flattened;
|
use lyon_geom::cubic_bezier::Flattened;
|
||||||
use lyon_geom::math::Transform;
|
use lyon_geom::math::Transform;
|
||||||
|
@ -28,7 +29,7 @@ use lyon_path::PathEvent;
|
||||||
use lyon_path::iterator::PathIter;
|
use lyon_path::iterator::PathIter;
|
||||||
use pathfinder_path_utils::stroke::{StrokeStyle, StrokeToFillIter};
|
use pathfinder_path_utils::stroke::{StrokeStyle, StrokeToFillIter};
|
||||||
use rayon::ThreadPoolBuilder;
|
use rayon::ThreadPoolBuilder;
|
||||||
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
@ -38,8 +39,8 @@ use std::path::PathBuf;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::u16;
|
use std::u16;
|
||||||
use svgtypes::Color as SvgColor;
|
use svgtypes::Color as SvgColor;
|
||||||
use usvg::{Node, NodeExt, NodeKind, Options as UsvgOptions, Paint, PathSegment as UsvgPathSegment};
|
use usvg::{Node, NodeExt, NodeKind, Options as UsvgOptions, Paint as UsvgPaint};
|
||||||
use usvg::{Rect as UsvgRect, Transform as UsvgTransform, Tree};
|
use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform, Tree};
|
||||||
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||||
|
@ -90,44 +91,35 @@ fn main() {
|
||||||
let usvg = Tree::from_file(&input_path, &UsvgOptions::default()).unwrap();
|
let usvg = Tree::from_file(&input_path, &UsvgOptions::default()).unwrap();
|
||||||
let scene = Scene::from_tree(usvg);
|
let scene = Scene::from_tree(usvg);
|
||||||
|
|
||||||
println!("Scene bounds: {:?} View box: {:?} Object count: {}",
|
println!("Scene bounds: {:?} View box: {:?}", scene.bounds, scene.view_box);
|
||||||
scene.bounds,
|
println!("{} objects, {} paints", scene.objects.len(), scene.paints.len());
|
||||||
scene.view_box,
|
|
||||||
scene.objects.len());
|
|
||||||
//println!("{:#?}", scene.objects[0]);
|
|
||||||
|
|
||||||
let start_time = Instant::now();
|
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 {
|
for _ in 0..runs {
|
||||||
let built_objects = match jobs {
|
let built_objects = match jobs {
|
||||||
Some(1) => scene.build_objects_sequentially(),
|
Some(1) => scene.build_objects_sequentially(),
|
||||||
_ => scene.build_objects(),
|
_ => 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_time = Instant::now() - start_time;
|
||||||
|
|
||||||
let elapsed_ms = elapsed_time.as_secs() as f64 * 1000.0 +
|
let elapsed_ms = elapsed_time.as_secs() as f64 * 1000.0 +
|
||||||
elapsed_time.subsec_micros() as f64 / 1000.0;
|
elapsed_time.subsec_micros() as f64 / 1000.0;
|
||||||
println!("{:.3}ms elapsed", elapsed_ms / runs as f64);
|
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", built_scene.solid_tiles.len());
|
||||||
println!("solid tiles:");
|
for (batch_index, batch) in built_scene.batches.iter().enumerate() {
|
||||||
for (index, tile) in built_scene.solid_tiles.iter().enumerate() {
|
println!("Batch {}: {} fills, {} mask tiles",
|
||||||
println!("... {}: {:?}", index, tile);
|
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 {
|
if let Some(output_path) = output_path {
|
||||||
built_scene.write(&mut BufWriter::new(File::create(output_path).unwrap())).unwrap();
|
built_scene.write(&mut BufWriter::new(File::create(output_path).unwrap())).unwrap();
|
||||||
}
|
}
|
||||||
|
@ -136,7 +128,8 @@ fn main() {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Scene {
|
struct Scene {
|
||||||
objects: Vec<PathObject>,
|
objects: Vec<PathObject>,
|
||||||
styles: Vec<ComputedStyle>,
|
paints: Vec<Paint>,
|
||||||
|
paint_cache: HashMap<Paint, PaintId>,
|
||||||
bounds: Rect<f32>,
|
bounds: Rect<f32>,
|
||||||
view_box: Rect<f32>,
|
view_box: Rect<f32>,
|
||||||
}
|
}
|
||||||
|
@ -144,7 +137,7 @@ struct Scene {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct PathObject {
|
struct PathObject {
|
||||||
outline: Outline,
|
outline: Outline,
|
||||||
style: StyleId,
|
paint: PaintId,
|
||||||
name: String,
|
name: String,
|
||||||
kind: PathObjectKind,
|
kind: PathObjectKind,
|
||||||
}
|
}
|
||||||
|
@ -155,17 +148,23 @@ pub enum PathObjectKind {
|
||||||
Stroke,
|
Stroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
struct ComputedStyle {
|
struct Paint {
|
||||||
color: Option<SvgColor>,
|
color: ColorU,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
struct StyleId(u32);
|
struct PaintId(u16);
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
fn new() -> 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 {
|
fn from_tree(tree: Tree) -> Scene {
|
||||||
|
@ -184,6 +183,10 @@ impl Scene {
|
||||||
_ => unreachable!(),
|
_ => 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;
|
return scene;
|
||||||
|
|
||||||
fn process_node(scene: &mut Scene, node: &Node, transform: &Transform2D<f32>) {
|
fn process_node(scene: &mut Scene, node: &Node, transform: &Transform2D<f32>) {
|
||||||
|
@ -198,7 +201,7 @@ impl Scene {
|
||||||
}
|
}
|
||||||
NodeKind::Path(ref path) => {
|
NodeKind::Path(ref path) => {
|
||||||
if let Some(ref fill) = path.fill {
|
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 = UsvgPathToPathEvents::new(path.segments.iter().cloned());
|
||||||
let path = PathTransformingIter::new(path, &transform);
|
let path = PathTransformingIter::new(path, &transform);
|
||||||
|
@ -213,7 +216,7 @@ impl Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref stroke) = path.stroke {
|
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,
|
let stroke_width = f32::max(stroke.width.value() as f32,
|
||||||
HAIRLINE_STROKE_WIDTH);
|
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);
|
fn push_paint(&mut self, paint: &Paint) -> PaintId {
|
||||||
scene.push_svg_path(&encoded_path, computed_style, name);
|
if let Some(paint_id) = self.paint_cache.get(paint) {
|
||||||
|
return *paint_id
|
||||||
group_styles.pop();
|
|
||||||
style = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Event::Start(ref event)) if event.name() == b"g" => {
|
let paint_id = PaintId(self.paints.len() as u16);
|
||||||
scene.push_group_style(&mut reader, event, &mut group_styles, &mut style);
|
self.paint_cache.insert(*paint, paint_id);
|
||||||
|
self.paints.push(*paint);
|
||||||
|
paint_id
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Event::End(ref event)) if event.name() == b"g" => {
|
fn build_shaders(&self) -> Vec<ObjectShader> {
|
||||||
group_styles.pop();
|
self.paints.iter().map(|paint| ObjectShader { fill_color: paint.color }).collect()
|
||||||
style = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
fn build_objects_sequentially(&self) -> Vec<BuiltObject> {
|
||||||
self.objects.iter().enumerate().map(|(object_index, object)| {
|
self.objects.iter().map(|object| {
|
||||||
let mut tiler = Tiler::new(&object.outline,
|
let mut tiler = Tiler::new(&object.outline, &self.view_box, ShaderId(object.paint.0));
|
||||||
object_index as u16,
|
|
||||||
&self.view_box,
|
|
||||||
&self.build_shader(object_index as u16));
|
|
||||||
tiler.generate_tiles();
|
tiler.generate_tiles();
|
||||||
tiler.built_object
|
tiler.built_object
|
||||||
}).collect()
|
}).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_objects(&self) -> Vec<BuiltObject> {
|
fn build_objects(&self) -> Vec<BuiltObject> {
|
||||||
self.objects.par_iter().enumerate().map(|(object_index, object)| {
|
self.objects.par_iter().map(|object| {
|
||||||
let mut tiler = Tiler::new(&object.outline,
|
let mut tiler = Tiler::new(&object.outline, &self.view_box, ShaderId(object.paint.0));
|
||||||
object_index as u16,
|
|
||||||
&self.view_box,
|
|
||||||
&self.build_shader(object_index as u16));
|
|
||||||
tiler.generate_tiles();
|
tiler.generate_tiles();
|
||||||
tiler.built_object
|
tiler.built_object
|
||||||
}).collect()
|
}).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 {
|
impl PathObject {
|
||||||
fn new(outline: Outline, style: StyleId, name: String, kind: PathObjectKind) -> PathObject {
|
fn new(outline: Outline, paint: PaintId, name: String, kind: PathObjectKind) -> PathObject {
|
||||||
PathObject { outline, style, name, kind }
|
PathObject { outline, paint, name, kind }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,6 +542,8 @@ struct PointIndex(u32);
|
||||||
|
|
||||||
impl PointIndex {
|
impl PointIndex {
|
||||||
fn new(contour: u32, point: u32) -> PointIndex {
|
fn new(contour: u32, point: u32) -> PointIndex {
|
||||||
|
debug_assert!(contour <= 0xfff);
|
||||||
|
debug_assert!(point <= 0x000fffff);
|
||||||
PointIndex((contour << 20) | point)
|
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>) {
|
fn split_y(&self, y: f32) -> (Option<Segment>, Option<Segment>) {
|
||||||
|
|
||||||
// Trivial cases.
|
// Trivial cases.
|
||||||
if self.from.y <= y && self.to.y <= y {
|
if self.from.y <= y && self.to.y <= y {
|
||||||
return (Some(*self), None)
|
return (Some(*self), None)
|
||||||
|
@ -903,7 +790,6 @@ const TILE_HEIGHT: f32 = 16.0;
|
||||||
|
|
||||||
struct Tiler<'o> {
|
struct Tiler<'o> {
|
||||||
outline: &'o Outline,
|
outline: &'o Outline,
|
||||||
object_index: u16,
|
|
||||||
built_object: BuiltObject,
|
built_object: BuiltObject,
|
||||||
|
|
||||||
view_box: Rect<f32>,
|
view_box: Rect<f32>,
|
||||||
|
@ -915,14 +801,12 @@ struct Tiler<'o> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'o> Tiler<'o> {
|
impl<'o> Tiler<'o> {
|
||||||
fn new(outline: &'o Outline, object_index: u16, view_box: &Rect<f32>, shader: &ObjectShader)
|
fn new(outline: &'o Outline, view_box: &Rect<f32>, shader: ShaderId) -> Tiler<'o> {
|
||||||
-> Tiler<'o> {
|
|
||||||
let bounds = outline.bounds.intersection(&view_box).unwrap_or(Rect::zero());
|
let bounds = outline.bounds.intersection(&view_box).unwrap_or(Rect::zero());
|
||||||
let built_object = BuiltObject::new(&bounds, shader);
|
let built_object = BuiltObject::new(&bounds, shader);
|
||||||
|
|
||||||
Tiler {
|
Tiler {
|
||||||
outline,
|
outline,
|
||||||
object_index,
|
|
||||||
built_object,
|
built_object,
|
||||||
|
|
||||||
view_box: *view_box,
|
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) {
|
fn add_new_active_edge(&mut self, tile_y: i16) {
|
||||||
|
@ -1136,10 +1020,6 @@ fn process_active_segment(contour: &Contour,
|
||||||
built_object: &mut BuiltObject,
|
built_object: &mut BuiltObject,
|
||||||
tile_y: i16) {
|
tile_y: i16) {
|
||||||
let mut segment = contour.segment_after(from_endpoint_index);
|
let mut segment = contour.segment_after(from_endpoint_index);
|
||||||
/*if segment.is_degenerate() {
|
|
||||||
return
|
|
||||||
}*/
|
|
||||||
|
|
||||||
process_active_edge(&mut segment, built_object, tile_y);
|
process_active_edge(&mut segment, built_object, tile_y);
|
||||||
|
|
||||||
if !segment.is_none() {
|
if !segment.is_none() {
|
||||||
|
@ -1164,20 +1044,23 @@ fn process_active_edge(active_edge: &mut Segment, built_object: &mut BuiltObject
|
||||||
// Scene construction
|
// Scene construction
|
||||||
|
|
||||||
impl BuiltScene {
|
impl BuiltScene {
|
||||||
fn new(view_box: &Rect<f32>, object_count: u32) -> BuiltScene {
|
fn new(view_box: &Rect<f32>, shaders: Vec<ObjectShader>) -> BuiltScene {
|
||||||
BuiltScene {
|
BuiltScene {
|
||||||
view_box: *view_box,
|
view_box: *view_box,
|
||||||
fills: vec![],
|
batches: vec![],
|
||||||
solid_tiles: vec![],
|
solid_tiles: vec![],
|
||||||
mask_tiles: vec![],
|
shaders,
|
||||||
shaders: vec![ObjectShader::default(); object_count as usize],
|
|
||||||
|
|
||||||
tile_rect: round_rect_out_to_tile_bounds(view_box),
|
tile_rect: round_rect_out_to_tile_bounds(view_box),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_objects(view_box: &Rect<f32>, objects: &[BuiltObject]) -> BuiltScene {
|
fn from_objects_and_shaders(view_box: &Rect<f32>,
|
||||||
let mut scene = BuiltScene::new(view_box, objects.len() as u32);
|
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 tile_area = scene.tile_rect.size.width as usize * scene.tile_rect.size.height as usize;
|
||||||
let mut z_buffer = vec![0; tile_area];
|
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);
|
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.
|
// Occluded.
|
||||||
continue
|
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 {
|
scene.solid_tiles.push(SolidTileScenePrimitive {
|
||||||
tile_x: tile.tile_x,
|
tile_x: tile.tile_x,
|
||||||
tile_y: tile.tile_y,
|
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![];
|
let mut object_tile_index_to_scene_mask_tile_index = vec![];
|
||||||
for (object_index, object) in objects.iter().enumerate() {
|
for (object_index, object) in objects.iter().enumerate() {
|
||||||
object_tile_index_to_scene_mask_tile_index.clear();
|
object_tile_index_to_scene_mask_tile_index.clear();
|
||||||
|
@ -1215,54 +1099,77 @@ impl BuiltScene {
|
||||||
for (tile_index, tile) in object.tiles.iter().enumerate() {
|
for (tile_index, tile) in object.tiles.iter().enumerate() {
|
||||||
// Skip solid tiles, since we handled them above already.
|
// Skip solid tiles, since we handled them above already.
|
||||||
if object.solid_tiles[tile_index] {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cull occluded tiles.
|
// Cull occluded tiles.
|
||||||
let scene_tile_index = scene.scene_tile_index(tile.tile_x, tile.tile_y);
|
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 {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible mask tile.
|
// Visible mask tile.
|
||||||
let scene_mask_tile_index = scene.mask_tiles.len() as u16;
|
let mut scene_mask_tile_index = scene.batches.last().unwrap().mask_tiles.len() as
|
||||||
object_tile_index_to_scene_mask_tile_index.push(scene_mask_tile_index);
|
u16;
|
||||||
scene.mask_tiles.push(MaskTileScenePrimitive {
|
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,
|
tile: *tile,
|
||||||
object_index: object_index as u16,
|
shader: object.shader,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remap and copy fills, culling as necessary.
|
// Remap and copy fills, culling as necessary.
|
||||||
for fill in &object.fills {
|
for fill in &object.fills {
|
||||||
let object_tile_index = object.tile_coords_to_index(fill.tile_x, fill.tile_y);
|
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] {
|
let SceneMaskTileIndex {
|
||||||
u16::MAX => {}
|
batch_index,
|
||||||
scene_mask_tile_index => {
|
mask_tile_index,
|
||||||
scene.fills.push(FillScenePrimitive {
|
} = 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,
|
from_px: fill.from_px,
|
||||||
to_px: fill.to_px,
|
to_px: fill.to_px,
|
||||||
from_subpx: fill.from_subpx,
|
from_subpx: fill.from_subpx,
|
||||||
to_subpx: fill.to_subpx,
|
to_subpx: fill.to_subpx,
|
||||||
mask_tile_index: scene_mask_tile_index,
|
mask_tile_index,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy shader.
|
return scene;
|
||||||
scene.shaders[object_index as usize] = object.shader;
|
|
||||||
|
#[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 {
|
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_y - self.tile_rect.origin.y) as u32 * self.tile_rect.size.width as u32 +
|
||||||
(tile_x - self.tile_rect.origin.x) as u32
|
(tile_x - self.tile_rect.origin.x) as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_batch(&mut self) {
|
||||||
|
self.batches.push(Batch::new());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Primitives
|
// Primitives
|
||||||
|
@ -1274,20 +1181,25 @@ struct BuiltObject {
|
||||||
tiles: Vec<TileObjectPrimitive>,
|
tiles: Vec<TileObjectPrimitive>,
|
||||||
fills: Vec<FillObjectPrimitive>,
|
fills: Vec<FillObjectPrimitive>,
|
||||||
solid_tiles: FixedBitSet,
|
solid_tiles: FixedBitSet,
|
||||||
shader: ObjectShader,
|
shader: ShaderId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct BuiltScene {
|
struct BuiltScene {
|
||||||
view_box: Rect<f32>,
|
view_box: Rect<f32>,
|
||||||
fills: Vec<FillScenePrimitive>,
|
batches: Vec<Batch>,
|
||||||
solid_tiles: Vec<SolidTileScenePrimitive>,
|
solid_tiles: Vec<SolidTileScenePrimitive>,
|
||||||
mask_tiles: Vec<MaskTileScenePrimitive>,
|
|
||||||
shaders: Vec<ObjectShader>,
|
shaders: Vec<ObjectShader>,
|
||||||
|
|
||||||
tile_rect: Rect<i16>,
|
tile_rect: Rect<i16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Batch {
|
||||||
|
fills: Vec<FillBatchPrimitive>,
|
||||||
|
mask_tiles: Vec<MaskTileBatchPrimitive>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct FillObjectPrimitive {
|
struct FillObjectPrimitive {
|
||||||
from_px: Point2DU4,
|
from_px: Point2DU4,
|
||||||
|
@ -1306,7 +1218,7 @@ struct TileObjectPrimitive {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct FillScenePrimitive {
|
struct FillBatchPrimitive {
|
||||||
from_px: Point2DU4,
|
from_px: Point2DU4,
|
||||||
to_px: Point2DU4,
|
to_px: Point2DU4,
|
||||||
from_subpx: Point2D<u8>,
|
from_subpx: Point2D<u8>,
|
||||||
|
@ -1318,21 +1230,24 @@ struct FillScenePrimitive {
|
||||||
struct SolidTileScenePrimitive {
|
struct SolidTileScenePrimitive {
|
||||||
tile_x: i16,
|
tile_x: i16,
|
||||||
tile_y: i16,
|
tile_y: i16,
|
||||||
object_index: u16,
|
shader: ShaderId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct MaskTileScenePrimitive {
|
struct MaskTileBatchPrimitive {
|
||||||
tile: TileObjectPrimitive,
|
tile: TileObjectPrimitive,
|
||||||
object_index: u16,
|
shader: ShaderId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
struct ShaderId(pub u16);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
struct ObjectShader {
|
struct ObjectShader {
|
||||||
fill_color: ColorU,
|
fill_color: ColorU,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
|
||||||
struct ColorU {
|
struct ColorU {
|
||||||
r: u8,
|
r: u8,
|
||||||
g: u8,
|
g: u8,
|
||||||
|
@ -1343,7 +1258,7 @@ struct ColorU {
|
||||||
// Utilities for built objects
|
// Utilities for built objects
|
||||||
|
|
||||||
impl BuiltObject {
|
impl BuiltObject {
|
||||||
fn new(bounds: &Rect<f32>, shader: &ObjectShader) -> BuiltObject {
|
fn new(bounds: &Rect<f32>, shader: ShaderId) -> BuiltObject {
|
||||||
// Compute the tile rect.
|
// Compute the tile rect.
|
||||||
let tile_rect = round_rect_out_to_tile_bounds(&bounds);
|
let tile_rect = round_rect_out_to_tile_bounds(&bounds);
|
||||||
|
|
||||||
|
@ -1365,7 +1280,7 @@ impl BuiltObject {
|
||||||
tiles,
|
tiles,
|
||||||
fills: vec![],
|
fills: vec![],
|
||||||
solid_tiles,
|
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
|
// Scene serialization
|
||||||
|
|
||||||
impl BuiltScene {
|
impl BuiltScene {
|
||||||
fn write<W>(&self, writer: &mut W) -> io::Result<()> where W: Write {
|
fn write<W>(&self, writer: &mut W) -> io::Result<()> where W: Write {
|
||||||
writer.write_all(b"RIFF")?;
|
writer.write_all(b"RIFF")?;
|
||||||
|
|
||||||
let header_size = 4 * 4;
|
let header_size = 4 * 6;
|
||||||
let fill_size = self.fills.len() * mem::size_of::<FillScenePrimitive>();
|
|
||||||
let solid_tiles_size = self.solid_tiles.len() * mem::size_of::<SolidTileScenePrimitive>();
|
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>();
|
let shaders_size = self.shaders.len() * mem::size_of::<ObjectShader>();
|
||||||
|
|
||||||
writer.write_u32::<LittleEndian>((4 +
|
writer.write_u32::<LittleEndian>((4 +
|
||||||
8 + header_size +
|
8 + header_size +
|
||||||
8 + fill_size +
|
|
||||||
8 + solid_tiles_size +
|
8 + solid_tiles_size +
|
||||||
8 + mask_tiles_size +
|
8 + shaders_size +
|
||||||
8 + shaders_size) as u32)?;
|
total_batch_sizes) as u32)?;
|
||||||
|
|
||||||
writer.write_all(b"PF3S")?;
|
writer.write_all(b"PF3S")?;
|
||||||
|
|
||||||
writer.write_all(b"head")?;
|
writer.write_all(b"head")?;
|
||||||
writer.write_u32::<LittleEndian>(header_size as u32)?;
|
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.x)?;
|
||||||
writer.write_f32::<LittleEndian>(self.view_box.origin.y)?;
|
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.width)?;
|
||||||
writer.write_f32::<LittleEndian>(self.view_box.size.height)?;
|
writer.write_f32::<LittleEndian>(self.view_box.size.height)?;
|
||||||
|
|
||||||
writer.write_all(b"fill")?;
|
writer.write_all(b"shad")?;
|
||||||
writer.write_u32::<LittleEndian>(fill_size as u32)?;
|
writer.write_u32::<LittleEndian>(shaders_size as u32)?;
|
||||||
for fill_primitive in &self.fills {
|
for &shader in &self.shaders {
|
||||||
writer.write_u8(fill_primitive.from_px.0)?;
|
let fill_color = shader.fill_color;
|
||||||
writer.write_u8(fill_primitive.to_px.0)?;
|
writer.write_all(&[fill_color.r, fill_color.g, fill_color.b, fill_color.a])?;
|
||||||
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"soli")?;
|
writer.write_all(b"soli")?;
|
||||||
|
@ -1484,23 +1421,31 @@ impl BuiltScene {
|
||||||
for &tile_primitive in &self.solid_tiles {
|
for &tile_primitive in &self.solid_tiles {
|
||||||
writer.write_i16::<LittleEndian>(tile_primitive.tile_x)?;
|
writer.write_i16::<LittleEndian>(tile_primitive.tile_x)?;
|
||||||
writer.write_i16::<LittleEndian>(tile_primitive.tile_y)?;
|
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_all(b"mask")?;
|
||||||
writer.write_u32::<LittleEndian>(mask_tiles_size as u32)?;
|
writer.write_u32::<LittleEndian>(sizes.mask_tiles as u32)?;
|
||||||
for &tile_primitive in &self.mask_tiles {
|
for &tile_primitive in &batch.mask_tiles {
|
||||||
writer.write_i16::<LittleEndian>(tile_primitive.tile.tile_x)?;
|
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.tile_y)?;
|
||||||
writer.write_i16::<LittleEndian>(tile_primitive.tile.backdrop)?;
|
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(());
|
return Ok(());
|
||||||
|
@ -1511,12 +1456,25 @@ impl BuiltScene {
|
||||||
writer.write_u8(point.y)?;
|
writer.write_u8(point.y)?;
|
||||||
Ok(())
|
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 {
|
impl Batch {
|
||||||
fn new(tile_x: i16, tile_y: i16, object_index: u16) -> SolidTileScenePrimitive {
|
fn new() -> Batch {
|
||||||
SolidTileScenePrimitive { tile_x, tile_y, object_index }
|
Batch { fills: vec![], mask_tiles: vec![] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue