Compress primitives.

This commit is contained in:
Patrick Walton 2019-01-02 13:53:21 -08:00
parent 68e0e791a4
commit c0c0daa427
6 changed files with 198 additions and 91 deletions

View File

@ -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;

View File

@ -20,7 +20,7 @@ uniform vec2 uViewBoxOrigin;
in vec2 aTessCoord;
in vec2 aTileOrigin;
in int aObject;
in uint aObject;
out vec4 vColor;

View File

@ -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<number> {
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<HTMLImageElement> {

View File

@ -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;

View File

@ -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

View File

@ -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<BuiltObject> {
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<BuiltObject> {
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<f32>,
@ -958,7 +958,7 @@ struct Tiler<'o> {
}
impl<'o> Tiler<'o> {
fn new(outline: &'o Outline, object_index: u32, view_box: &Rect<f32>, shader: &ObjectShader)
fn new(outline: &'o Outline, object_index: u16, view_box: &Rect<f32>, 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<f32>,
to: Point2D<f32>,
from_px: Point2DU4,
to_px: Point2DU4,
from_subpx: Point2D<u8>,
to_subpx: Point2D<u8>,
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<f32>,
to: Point2D<f32>,
mask_tile_index: u32,
from_px: Point2DU4,
to_px: Point2DU4,
from_subpx: Point2D<u8>,
to_subpx: Point2D<u8>,
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::<LittleEndian>(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::<LittleEndian>(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::<LittleEndian>(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::<LittleEndian>(tile_primitive.tile_x)?;
writer.write_i16::<LittleEndian>(tile_primitive.tile_y)?;
writer.write_u32::<LittleEndian>(tile_primitive.object_index)?;
writer.write_u16::<LittleEndian>(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::<LittleEndian>(tile_primitive.tile.tile_x)?;
writer.write_i16::<LittleEndian>(tile_primitive.tile.tile_y)?;
writer.write_i32::<LittleEndian>(tile_primitive.tile.backdrop)?;
writer.write_u32::<LittleEndian>(tile_primitive.object_index)?;
writer.write_i16::<LittleEndian>(tile_primitive.tile.backdrop)?;
writer.write_u16::<LittleEndian>(tile_primitive.object_index)?;
}
writer.write_all(b"shad")?;
@ -1527,16 +1551,17 @@ impl BuiltScene {
return Ok(());
fn write_point<W>(writer: &mut W, point: &Point2D<f32>) -> io::Result<()> where W: Write {
writer.write_f32::<LittleEndian>(point.x)?;
writer.write_f32::<LittleEndian>(point.y)?;
fn write_point2d_u8<W>(writer: &mut W, point: Point2D<u8>)
-> 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<ActiveEdge> 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
/*