diff --git a/Cargo.lock b/Cargo.lock index 5ad9a257..9f27e02d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/demo2/pathfinder.ts b/demo2/pathfinder.ts index fa6a0cba..68854559 100644 --- a/demo2/pathfinder.ts +++ b/demo2/pathfinder.ts @@ -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"); }); } diff --git a/utils/tile-svg/Cargo.toml b/utils/tile-svg/Cargo.toml index 57580b2d..cea66183 100644 --- a/utils/tile-svg/Cargo.toml +++ b/utils/tile-svg/Cargo.toml @@ -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" diff --git a/utils/tile-svg/src/main.rs b/utils/tile-svg/src/main.rs index 2dce5a9d..de662d69 100644 --- a/utils/tile-svg/src/main.rs +++ b/utils/tile-svg/src/main.rs @@ -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, - styles: Vec, + paints: Vec, + paint_cache: HashMap, bounds: Rect, view_box: Rect, } @@ -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, +#[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) { @@ -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; - } - - Ok(Event::Start(ref event)) if event.name() == b"g" => { - scene.push_group_style(&mut reader, event, &mut group_styles, &mut style); - } - - Ok(Event::End(ref event)) if event.name() == b"g" => { - group_styles.pop(); - 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(); + fn push_paint(&mut self, paint: &Paint) -> PaintId { + if let Some(paint_id) = self.paint_cache.get(paint) { + return *paint_id } - return scene; - - */ + let paint_id = PaintId(self.paints.len() as u16); + self.paint_cache.insert(*paint, paint_id); + self.paints.push(*paint); + paint_id } - 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 build_shaders(&self) -> Vec { + self.paints.iter().map(|paint| ObjectShader { fill_color: paint.color }).collect() } - 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 { - 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 { - 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, Option) { - // 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, @@ -915,14 +801,12 @@ struct Tiler<'o> { } impl<'o> Tiler<'o> { - fn new(outline: &'o Outline, object_index: u16, view_box: &Rect, shader: &ObjectShader) - -> Tiler<'o> { + fn new(outline: &'o Outline, view_box: &Rect, 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, object_count: u32) -> BuiltScene { + fn new(view_box: &Rect, shaders: Vec) -> 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, objects: &[BuiltObject]) -> BuiltScene { - let mut scene = BuiltScene::new(view_box, objects.len() as u32); + fn from_objects_and_shaders(view_box: &Rect, + objects: &[BuiltObject], + shaders: Vec) + -> 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 { - 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, - }) - } + 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, + }); } } - - // Copy shader. - scene.shaders[object_index as usize] = object.shader; } - scene + return scene; + + #[derive(Clone, Copy, Debug)] + struct SceneMaskTileIndex { + batch_index: u16, + mask_tile_index: u16, + } + + 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, fills: Vec, solid_tiles: FixedBitSet, - shader: ObjectShader, + shader: ShaderId, } #[derive(Debug)] struct BuiltScene { view_box: Rect, - fills: Vec, + batches: Vec, solid_tiles: Vec, - mask_tiles: Vec, shaders: Vec, tile_rect: Rect, } +#[derive(Debug)] +struct Batch { + fills: Vec, + mask_tiles: Vec, +} + #[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, @@ -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, shader: &ObjectShader) -> BuiltObject { + fn new(bounds: &Rect, 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(&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::(); + let header_size = 4 * 6; + let solid_tiles_size = self.solid_tiles.len() * mem::size_of::(); - let mask_tiles_size = self.mask_tiles.len() * mem::size_of::(); + + let batch_sizes: Vec<_> = self.batches.iter().map(|batch| { + BatchSizes { + fills: (batch.fills.len() * mem::size_of::()), + mask_tiles: (batch.mask_tiles.len() * mem::size_of::()), + } + }).collect(); + + let total_batch_sizes: usize = batch_sizes.iter().map(|sizes| 8 + sizes.total()).sum(); + let shaders_size = self.shaders.len() * mem::size_of::(); + writer.write_u32::((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::(header_size as u32)?; + writer.write_u32::(FILE_VERSION)?; + writer.write_u32::(self.batches.len() as u32)?; writer.write_f32::(self.view_box.origin.x)?; writer.write_f32::(self.view_box.origin.y)?; writer.write_f32::(self.view_box.size.width)?; writer.write_f32::(self.view_box.size.height)?; - writer.write_all(b"fill")?; - writer.write_u32::(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::(fill_primitive.mask_tile_index)?; + writer.write_all(b"shad")?; + writer.write_u32::(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::(tile_primitive.tile_x)?; writer.write_i16::(tile_primitive.tile_y)?; - writer.write_u16::(tile_primitive.object_index)?; + writer.write_u16::(tile_primitive.shader.0)?; } - writer.write_all(b"mask")?; - writer.write_u32::(mask_tiles_size as u32)?; - for &tile_primitive in &self.mask_tiles { - writer.write_i16::(tile_primitive.tile.tile_x)?; - writer.write_i16::(tile_primitive.tile.tile_y)?; - writer.write_i16::(tile_primitive.tile.backdrop)?; - writer.write_u16::(tile_primitive.object_index)?; - } + for (batch, sizes) in self.batches.iter().zip(batch_sizes.iter()) { + writer.write_all(b"batc")?; + writer.write_u32::(sizes.total() as u32)?; - writer.write_all(b"shad")?; - writer.write_u32::(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"fill")?; + writer.write_u32::(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::(fill_primitive.mask_tile_index)?; + } + + writer.write_all(b"mask")?; + writer.write_u32::(sizes.mask_tiles as u32)?; + for &tile_primitive in &batch.mask_tiles { + writer.write_i16::(tile_primitive.tile.tile_x)?; + writer.write_i16::(tile_primitive.tile.tile_y)?; + writer.write_i16::(tile_primitive.tile.backdrop)?; + writer.write_u16::(tile_primitive.shader.0)?; + } } 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![] } } }