From c0c0daa427aaeac677d8a717b418e362bb2fe97b Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 2 Jan 2019 13:53:21 -0800 Subject: [PATCH] Compress primitives. --- demo2/cover.vs.glsl | 2 +- demo2/opaque.vs.glsl | 2 +- demo2/pathfinder.ts | 156 +++++++++++++++++++++++++++---------- demo2/stencil.fs.glsl | 3 +- demo2/stencil.vs.glsl | 13 ++-- utils/tile-svg/src/main.rs | 113 ++++++++++++++++++--------- 6 files changed, 198 insertions(+), 91 deletions(-) diff --git a/demo2/cover.vs.glsl b/demo2/cover.vs.glsl index 80c6d8f8..d77c5932 100644 --- a/demo2/cover.vs.glsl +++ b/demo2/cover.vs.glsl @@ -22,7 +22,7 @@ uniform vec2 uViewBoxOrigin; in vec2 aTessCoord; in vec2 aTileOrigin; in int aBackdrop; -in int aObject; +in uint aObject; out vec2 vTexCoord; out float vBackdrop; diff --git a/demo2/opaque.vs.glsl b/demo2/opaque.vs.glsl index 695a6b93..5f9ba286 100644 --- a/demo2/opaque.vs.glsl +++ b/demo2/opaque.vs.glsl @@ -20,7 +20,7 @@ uniform vec2 uViewBoxOrigin; in vec2 aTessCoord; in vec2 aTileOrigin; -in int aObject; +in uint aObject; out vec4 vColor; diff --git a/demo2/pathfinder.ts b/demo2/pathfinder.ts index e732198e..848bef5e 100644 --- a/demo2/pathfinder.ts +++ b/demo2/pathfinder.ts @@ -33,9 +33,9 @@ const QUAD_VERTEX_POSITIONS: Uint8Array = new Uint8Array([ 0, 1, ]); -const FILL_INSTANCE_SIZE: number = 20; -const SOLID_TILE_INSTANCE_SIZE: number = 8; -const MASK_TILE_INSTANCE_SIZE: number = 12; +const FILL_INSTANCE_SIZE: number = 8; +const SOLID_TILE_INSTANCE_SIZE: number = 6; +const MASK_TILE_INSTANCE_SIZE: number = 8; interface Color { r: number; @@ -58,7 +58,10 @@ class App { private stencilTexture: WebGLTexture; private stencilFramebuffer: WebGLFramebuffer; private fillProgram: Program<'FramebufferSize' | 'TileSize' | 'AreaLUT', - 'TessCoord' | 'From' | 'To' | 'TileIndex'>; + 'TessCoord' | + 'FromPx' | 'ToPx' | + 'FromSubpx' | 'ToSubpx' | + 'TileIndex'>; private solidTileProgram: Program<'FramebufferSize' | 'TileSize' | 'FillColorsTexture' | 'FillColorsTextureSize' | @@ -116,6 +119,19 @@ class App { this.fillColorsTexture = unwrapNull(gl.createTexture()); + /* + const benchData = new Uint8Array(1600 * 1600 * 4); + for (let i = 0; i < benchData.length; i++) + benchData[i] = (Math.random() * 256) | 0; + const benchTexture = unwrapNull(gl.createTexture()); + gl.bindTexture(gl.TEXTURE_2D, benchTexture); + const startTime = performance.now(); + for (let i = 0; i < 100; i++) + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1600, 1600, 0, gl.RGBA, gl.UNSIGNED_BYTE, benchData); + const elapsedTime = (performance.now() - startTime) / 100; + console.log("texture upload: ", elapsedTime, "ms"); + */ + this.stencilTexture = unwrapNull(gl.createTexture()); gl.bindTexture(gl.TEXTURE_2D, this.stencilTexture); gl.texImage2D(gl.TEXTURE_2D, @@ -177,10 +193,15 @@ class App { this.solidTileProgram = solidTileProgram; const fillProgram = new Program(gl, - STENCIL_VERTEX_SHADER_SOURCE, - STENCIL_FRAGMENT_SHADER_SOURCE, - ['FramebufferSize', 'TileSize', 'AreaLUT'], - ['TessCoord', 'From', 'To', 'TileIndex']); + STENCIL_VERTEX_SHADER_SOURCE, + STENCIL_FRAGMENT_SHADER_SOURCE, + ['FramebufferSize', 'TileSize', 'AreaLUT'], + [ + 'TessCoord', + 'FromPx', 'ToPx', + 'FromSubpx', 'ToSubpx', + 'TileIndex' + ]); this.fillProgram = fillProgram; // Initialize quad VBO. @@ -203,29 +224,43 @@ class App { 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, this.fillVertexBuffer); - gl.vertexAttribPointer(fillProgram.attributes.From, + gl.vertexAttribIPointer(fillProgram.attributes.FromPx, + 1, + gl.UNSIGNED_BYTE, + FILL_INSTANCE_SIZE, + 0); + gl.vertexAttribDivisor(fillProgram.attributes.FromPx, 1); + gl.vertexAttribIPointer(fillProgram.attributes.ToPx, + 1, + gl.UNSIGNED_BYTE, + FILL_INSTANCE_SIZE, + 1); + gl.vertexAttribDivisor(fillProgram.attributes.ToPx, 1); + gl.vertexAttribPointer(fillProgram.attributes.FromSubpx, 2, - gl.FLOAT, - false, + gl.UNSIGNED_BYTE, + true, FILL_INSTANCE_SIZE, - 0); - gl.vertexAttribDivisor(fillProgram.attributes.From, 1); - gl.vertexAttribPointer(fillProgram.attributes.To, + 2); + gl.vertexAttribDivisor(fillProgram.attributes.FromSubpx, 1); + gl.vertexAttribPointer(fillProgram.attributes.ToSubpx, 2, - gl.FLOAT, - false, + gl.UNSIGNED_BYTE, + true, FILL_INSTANCE_SIZE, - 8); - gl.vertexAttribDivisor(fillProgram.attributes.To, 1); + 4); + gl.vertexAttribDivisor(fillProgram.attributes.ToSubpx, 1); gl.vertexAttribIPointer(fillProgram.attributes.TileIndex, 1, - gl.UNSIGNED_INT, + gl.UNSIGNED_SHORT, FILL_INSTANCE_SIZE, - 16); + 6); gl.vertexAttribDivisor(fillProgram.attributes.TileIndex, 1); gl.enableVertexAttribArray(fillProgram.attributes.TessCoord); - gl.enableVertexAttribArray(fillProgram.attributes.From); - gl.enableVertexAttribArray(fillProgram.attributes.To); + gl.enableVertexAttribArray(fillProgram.attributes.FromPx); + gl.enableVertexAttribArray(fillProgram.attributes.ToPx); + gl.enableVertexAttribArray(fillProgram.attributes.FromSubpx); + gl.enableVertexAttribArray(fillProgram.attributes.ToSubpx); gl.enableVertexAttribArray(fillProgram.attributes.TileIndex); // Initialize tile VBOs and IBOs. @@ -253,7 +288,7 @@ class App { gl.vertexAttribDivisor(solidTileProgram.attributes.TileOrigin, 1); gl.vertexAttribIPointer(solidTileProgram.attributes.Object, 1, - gl.INT, + gl.UNSIGNED_SHORT, SOLID_TILE_INSTANCE_SIZE, 4); gl.vertexAttribDivisor(solidTileProgram.attributes.Object, 1); @@ -282,15 +317,15 @@ class App { gl.vertexAttribDivisor(maskTileProgram.attributes.TileOrigin, 1); gl.vertexAttribIPointer(maskTileProgram.attributes.Backdrop, 1, - gl.INT, + gl.SHORT, MASK_TILE_INSTANCE_SIZE, 4); gl.vertexAttribDivisor(maskTileProgram.attributes.Backdrop, 1); gl.vertexAttribIPointer(maskTileProgram.attributes.Object, 1, - gl.INT, + gl.UNSIGNED_SHORT, MASK_TILE_INSTANCE_SIZE, - 8); + 6); gl.vertexAttribDivisor(maskTileProgram.attributes.Object, 1); gl.enableVertexAttribArray(maskTileProgram.attributes.TessCoord); gl.enableVertexAttribArray(maskTileProgram.attributes.TileOrigin); @@ -313,14 +348,17 @@ class App { //console.log("viewBox", this.viewBox); - // Start timer. - let timerQuery = null; + // Initialize timers. + let fillTimerQuery = null, solidTimerQuery = null, maskTimerQuery = null; if (this.disjointTimerQueryExt != null) { - timerQuery = unwrapNull(gl.createQuery()); - gl.beginQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT, timerQuery); + 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); @@ -340,6 +378,8 @@ class App { 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(); @@ -351,6 +391,8 @@ class App { 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, @@ -369,8 +411,12 @@ class App { 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, @@ -398,11 +444,16 @@ class App { 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 (timerQuery != null) { - gl.endQuery(this.disjointTimerQueryExt.TIME_ELAPSED_EXT); - waitForQuery(gl, this.disjointTimerQueryExt, timerQuery); + if (fillTimerQuery != null && solidTimerQuery != null && maskTimerQuery != null) { + processQueries(gl, this.disjointTimerQueryExt, { + fill: fillTimerQuery, + solid: solidTimerQuery, + mask: maskTimerQuery, + }); } } @@ -611,16 +662,37 @@ class RIFFChunk { } } -function waitForQuery(gl: WebGL2RenderingContext, disjointTimerQueryExt: any, query: WebGLQuery): - void { - const queryResultAvailable = disjointTimerQueryExt.QUERY_RESULT_AVAILABLE_EXT; - const queryResult = disjointTimerQueryExt.QUERY_RESULT_EXT; - if (!disjointTimerQueryExt.getQueryObjectEXT(query, queryResultAvailable)) { - setTimeout(() => waitForQuery(gl, disjointTimerQueryExt, query), 10); - return; +interface Queries { + fill: WebGLQuery; + solid: WebGLQuery; + mask: WebGLQuery; +}; + +function getQueryResult(gl: WebGL2RenderingContext, disjointTimerQueryExt: any, query: WebGLQuery): + Promise { + function go(resolve: (n: number) => void): void { + const queryResultAvailable = disjointTimerQueryExt.QUERY_RESULT_AVAILABLE_EXT; + const queryResult = disjointTimerQueryExt.QUERY_RESULT_EXT; + if (!disjointTimerQueryExt.getQueryObjectEXT(query, queryResultAvailable)) { + setTimeout(() => go(resolve), 10); + return; + } + resolve(disjointTimerQueryExt.getQueryObjectEXT(query, queryResult) / 1000000.0); } - const elapsed = disjointTimerQueryExt.getQueryObjectEXT(query, queryResult) / 1000000.0; - console.log(elapsed + "ms elapsed"); + + return new Promise((resolve, reject) => go(resolve)); +} + +function processQueries(gl: WebGL2RenderingContext, disjointTimerQueryExt: any, queries: Queries): + void { + 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"); + }); } function loadAreaLUT(): Promise { diff --git a/demo2/stencil.fs.glsl b/demo2/stencil.fs.glsl index a90bdaa9..b1f39c8f 100644 --- a/demo2/stencil.fs.glsl +++ b/demo2/stencil.fs.glsl @@ -15,14 +15,13 @@ precision highp float; uniform sampler2D uAreaLUT; in vec2 vFrom; -in vec2 vCtrl; in vec2 vTo; out vec4 oFragColor; void main() { // Unpack. - vec2 from = vFrom, ctrl = vCtrl, to = vTo; + vec2 from = vFrom, to = vTo; // Determine winding, and sort into a consistent order so we only need to find one root below. bool winding = from.x < to.x; diff --git a/demo2/stencil.vs.glsl b/demo2/stencil.vs.glsl index 47943334..e91cea4d 100644 --- a/demo2/stencil.vs.glsl +++ b/demo2/stencil.vs.glsl @@ -16,12 +16,13 @@ uniform vec2 uFramebufferSize; uniform vec2 uTileSize; in vec2 aTessCoord; -in vec2 aFrom; -in vec2 aTo; +in uint aFromPx; +in uint aToPx; +in vec2 aFromSubpx; +in vec2 aToSubpx; in uint aTileIndex; out vec2 vFrom; -out vec2 vCtrl; out vec2 vTo; vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) { @@ -33,8 +34,8 @@ vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) { void main() { vec2 tileOrigin = computeTileOffset(aTileIndex, uFramebufferSize.x); - vec2 from = clamp(aFrom, vec2(0.0), uTileSize); - vec2 to = clamp(aTo, vec2(0.0), uTileSize); + vec2 from = vec2(aFromPx & 15u, aFromPx >> 4u) + aFromSubpx; + vec2 to = vec2(aToPx & 15u, aToPx >> 4u) + aToSubpx; vec2 position; bool zeroArea = !(abs(from.x - to.x) > 0.1) || !(abs(uTileSize.y - min(from.y, to.y)) > 0.1); @@ -50,8 +51,6 @@ void main() { vFrom = from - position; vTo = to - position; - vCtrl = mix(vFrom, vTo, 0.5); - if (zeroArea) gl_Position = vec4(0.0); else diff --git a/utils/tile-svg/src/main.rs b/utils/tile-svg/src/main.rs index f640709a..ba1b2d7e 100644 --- a/utils/tile-svg/src/main.rs +++ b/utils/tile-svg/src/main.rs @@ -40,7 +40,7 @@ use std::ops::Range; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::Instant; -use std::u32; +use std::u16; use svgtypes::{Color as SvgColor, PathParser, PathSegment as SvgPathSegment, TransformListParser}; use svgtypes::{TransformListToken}; @@ -339,7 +339,7 @@ impl Scene { &self.styles[style.0 as usize] } - fn build_shader(&self, object_index: u32) -> ObjectShader { + fn build_shader(&self, object_index: u16) -> ObjectShader { ObjectShader { fill_color: self.objects[object_index as usize].color, } @@ -349,9 +349,9 @@ impl Scene { fn build_objects_sequentially(&self) -> Vec { self.objects.iter().enumerate().map(|(object_index, object)| { let mut tiler = Tiler::new(&object.outline, - object_index as u32, + object_index as u16, &self.view_box, - &self.build_shader(object_index as u32)); + &self.build_shader(object_index as u16)); tiler.generate_tiles(); tiler.built_object }).collect() @@ -360,9 +360,9 @@ impl Scene { fn build_objects(&self) -> Vec { self.objects.par_iter().enumerate().map(|(object_index, object)| { let mut tiler = Tiler::new(&object.outline, - object_index as u32, + object_index as u16, &self.view_box, - &self.build_shader(object_index as u32)); + &self.build_shader(object_index as u16)); tiler.generate_tiles(); tiler.built_object }).collect() @@ -946,7 +946,7 @@ const TILE_HEIGHT: f32 = 16.0; struct Tiler<'o> { outline: &'o Outline, - object_index: u32, + object_index: u16, built_object: BuiltObject, view_box: Rect, @@ -958,7 +958,7 @@ struct Tiler<'o> { } impl<'o> Tiler<'o> { - fn new(outline: &'o Outline, object_index: u32, view_box: &Rect, shader: &ObjectShader) + fn new(outline: &'o Outline, object_index: u16, view_box: &Rect, shader: &ObjectShader) -> Tiler<'o> { let bounds = outline.bounds.intersection(&view_box).unwrap_or(Rect::zero()); let built_object = BuiltObject::new(&bounds, shader); @@ -1255,16 +1255,16 @@ 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 { + if z_buffer[scene_tile_index as usize] > object_index as u16 { // Occluded. continue } - z_buffer[scene_tile_index as usize] = object_index; + z_buffer[scene_tile_index as usize] = object_index as u16; scene.solid_tiles.push(SolidTileScenePrimitive { tile_x: tile.tile_x, tile_y: tile.tile_y, - object_index: object_index as u32, + object_index: object_index as u16, }); } } @@ -1278,23 +1278,23 @@ 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(u32::MAX); + object_tile_index_to_scene_mask_tile_index.push(u16::MAX); 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] > object_index { - object_tile_index_to_scene_mask_tile_index.push(u32::MAX); + if z_buffer[scene_tile_index as usize] as usize > object_index { + object_tile_index_to_scene_mask_tile_index.push(u16::MAX); continue; } // Visible mask tile. - let scene_mask_tile_index = scene.mask_tiles.len() as u32; + 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 { tile: *tile, - object_index: object_index as u32, + object_index: object_index as u16, }); } @@ -1302,11 +1302,13 @@ impl BuiltScene { 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] { - u32::MAX => {} + u16::MAX => {} scene_mask_tile_index => { scene.fills.push(FillScenePrimitive { - from: fill.from, - to: fill.to, + 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, }) } @@ -1351,8 +1353,10 @@ struct BuiltScene { #[derive(Clone, Copy, Debug)] struct FillObjectPrimitive { - from: Point2D, - to: Point2D, + from_px: Point2DU4, + to_px: Point2DU4, + from_subpx: Point2D, + to_subpx: Point2D, tile_x: i16, tile_y: i16, } @@ -1361,27 +1365,29 @@ struct FillObjectPrimitive { struct TileObjectPrimitive { tile_x: i16, tile_y: i16, - backdrop: i32, + backdrop: i16, } #[derive(Clone, Copy, Debug)] struct FillScenePrimitive { - from: Point2D, - to: Point2D, - mask_tile_index: u32, + from_px: Point2DU4, + to_px: Point2DU4, + from_subpx: Point2D, + to_subpx: Point2D, + mask_tile_index: u16, } #[derive(Clone, Copy, Debug)] struct SolidTileScenePrimitive { tile_x: i16, tile_y: i16, - object_index: u32, + object_index: u16, } #[derive(Clone, Copy, Debug)] struct MaskTileScenePrimitive { tile: TileObjectPrimitive, - object_index: u32, + object_index: u16, } #[derive(Clone, Copy, Debug, Default)] @@ -1431,6 +1437,11 @@ impl BuiltObject { let tile_index = self.tile_coords_to_index(tile_x, tile_y); let (from, to) = (*from - tile_origin, *to - tile_origin); + let from = Point2D::new(clamp(from.x, 0.0, MAX_U12), clamp(from.y, 0.0, MAX_U12)); + let to = Point2D::new(clamp(to.x, 0.0, MAX_U12), clamp(to.y, 0.0, MAX_U12)); + + const MAX_U12: f32 = 16.0 - 1.0 / 256.0; + /* println!("from={:?} to={:?}", from, to); debug_assert!(from.x > -EPSILON); @@ -1443,7 +1454,18 @@ impl BuiltObject { debug_assert!(to.y < TILE_HEIGHT + EPSILON); */ - self.fills.push(FillObjectPrimitive { from, to, tile_x, tile_y }); + let from_px = Point2DU4::new(from.x as u8, from.y as u8); + let to_px = Point2DU4::new(to.x as u8, to.y as u8); + let from_subpx = Point2D::new((from.x.fract() * 256.0) as u8, + (from.y.fract() * 256.0) as u8); + let to_subpx = Point2D::new((to.x.fract() * 256.0) as u8, (to.y.fract() * 256.0) as u8); + + self.fills.push(FillObjectPrimitive { + from_px, to_px, + from_subpx, to_subpx, + tile_x, tile_y, + }); + self.solid_tiles.set(tile_index as usize, false); // FIXME(pcwalton): This is really sloppy! @@ -1496,9 +1518,11 @@ impl BuiltScene { writer.write_all(b"fill")?; writer.write_u32::(fill_size as u32)?; for fill_primitive in &self.fills { - write_point(writer, &fill_primitive.from)?; - write_point(writer, &fill_primitive.to)?; - writer.write_u32::(fill_primitive.mask_tile_index)?; + 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"soli")?; @@ -1506,7 +1530,7 @@ 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_u32::(tile_primitive.object_index)?; + writer.write_u16::(tile_primitive.object_index)?; } writer.write_all(b"mask")?; @@ -1514,8 +1538,8 @@ impl BuiltScene { 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_i32::(tile_primitive.tile.backdrop)?; - writer.write_u32::(tile_primitive.object_index)?; + writer.write_i16::(tile_primitive.tile.backdrop)?; + writer.write_u16::(tile_primitive.object_index)?; } writer.write_all(b"shad")?; @@ -1527,16 +1551,17 @@ impl BuiltScene { return Ok(()); - fn write_point(writer: &mut W, point: &Point2D) -> io::Result<()> where W: Write { - writer.write_f32::(point.x)?; - writer.write_f32::(point.y)?; + fn write_point2d_u8(writer: &mut W, point: Point2D) + -> io::Result<()> where W: Write { + writer.write_u8(point.x)?; + writer.write_u8(point.y)?; Ok(()) } } } impl SolidTileScenePrimitive { - fn new(tile_x: i16, tile_y: i16, object_index: u32) -> SolidTileScenePrimitive { + fn new(tile_x: i16, tile_y: i16, object_index: u16) -> SolidTileScenePrimitive { SolidTileScenePrimitive { tile_x, tile_y, object_index } } } @@ -1987,6 +2012,18 @@ impl PartialOrd for ActiveEdge { } } +// Geometry + +#[derive(Clone, Copy, Debug)] +struct Point2DU4(pub u8); + +impl Point2DU4 { + fn new(x: u8, y: u8) -> Point2DU4 { Point2DU4(x | (y << 4)) } + + fn x(self) -> u8 { self.0 & 0xf } + fn y(self) -> u8 { self.0 >> 4 } +} + // Path utilities /*