Get PngPngPngJpeg terrain working in the actual game.

This commit is contained in:
Avi Weinstock 2021-04-23 20:18:06 -04:00
parent 64ab1c2151
commit 5861603a34
10 changed files with 468 additions and 138 deletions

1
Cargo.lock generated
View File

@ -5719,6 +5719,7 @@ dependencies = [
"flate2", "flate2",
"hashbrown", "hashbrown",
"image", "image",
"num-traits",
"serde", "serde",
"specs", "specs",
"specs-idvs", "specs-idvs",

View File

@ -1929,7 +1929,7 @@ impl Client {
fn handle_server_terrain_msg(&mut self, msg: ServerGeneral) -> Result<(), Error> { fn handle_server_terrain_msg(&mut self, msg: ServerGeneral) -> Result<(), Error> {
match msg { match msg {
ServerGeneral::TerrainChunkUpdate { key, chunk } => { ServerGeneral::TerrainChunkUpdate { key, chunk } => {
if let Some(chunk) = chunk.ok().and_then(|c| c.decompress()) { if let Some(chunk) = chunk.ok().and_then(|c| c.to_chunk()) {
self.state.insert_chunk(key, Arc::new(chunk)); self.state.insert_chunk(key, Arc::new(chunk));
} }
self.pending_chunks.remove(&key); self.pending_chunks.remove(&key);

View File

@ -17,9 +17,11 @@ common = {package = "veloren-common", path = "../../common"}
bincode = "1.3.3" bincode = "1.3.3"
flate2 = "1.0.20" flate2 = "1.0.20"
image = { version = "0.23.12", default-features = false, features = ["png", "jpeg"] } image = { version = "0.23.12", default-features = false, features = ["png", "jpeg"] }
num-traits = "0.2"
sum_type = "0.2.0" sum_type = "0.2.0"
vek = { version = "=0.14.1", features = ["serde"] } vek = { version = "=0.14.1", features = ["serde"] }
tracing = { version = "0.1", default-features = false } tracing = { version = "0.1", default-features = false }
#inline_tweak = "1.0.2"
# Data structures # Data structures
hashbrown = { version = "0.9", features = ["rayon", "serde", "nightly"] } hashbrown = { version = "0.9", features = ["rayon", "serde", "nightly"] }

View File

@ -1,16 +1,17 @@
use common::{ use common::{
terrain::{chonk::Chonk, Block, BlockKind, SpriteKind}, terrain::{chonk::Chonk, Block, BlockKind, SpriteKind},
vol::{BaseVol, IntoVolIterator, ReadVol, RectVolSize, SizedVol, WriteVol}, vol::{BaseVol, ReadVol, RectVolSize, WriteVol},
volumes::vol_grid_2d::VolGrid2d, volumes::vol_grid_2d::VolGrid2d,
}; };
use hashbrown::HashMap; use image::{ImageBuffer, ImageDecoder, Pixel};
use num_traits::cast::FromPrimitive;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
fmt::Debug, fmt::Debug,
io::{Read, Write}, io::{Read, Write},
marker::PhantomData, marker::PhantomData,
}; };
use tracing::trace; use tracing::{trace, warn};
use vek::*; use vek::*;
/// Wrapper for compressed, serialized data (for stuff that doesn't use the /// Wrapper for compressed, serialized data (for stuff that doesn't use the
@ -71,7 +72,7 @@ impl<T: for<'a> Deserialize<'a>> CompressedData<T> {
} }
/// Formula for packing voxel data into a 2d array /// Formula for packing voxel data into a 2d array
pub trait PackingFormula { pub trait PackingFormula: Copy {
fn dimensions(&self, dims: Vec3<u32>) -> (u32, u32); fn dimensions(&self, dims: Vec3<u32>) -> (u32, u32);
fn index(&self, dims: Vec3<u32>, x: u32, y: u32, z: u32) -> (u32, u32); fn index(&self, dims: Vec3<u32>, x: u32, y: u32, z: u32) -> (u32, u32);
} }
@ -79,6 +80,7 @@ pub trait PackingFormula {
/// A tall, thin image, with no wasted space, but which most image viewers don't /// A tall, thin image, with no wasted space, but which most image viewers don't
/// handle well. Z levels increase from top to bottom, xy-slices are stacked /// handle well. Z levels increase from top to bottom, xy-slices are stacked
/// vertically. /// vertically.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct TallPacking { pub struct TallPacking {
/// Making the borders go back and forth based on z-parity preserves spatial /// Making the borders go back and forth based on z-parity preserves spatial
/// locality better, but is more confusing to look at /// locality better, but is more confusing to look at
@ -88,6 +90,7 @@ pub struct TallPacking {
impl PackingFormula for TallPacking { impl PackingFormula for TallPacking {
fn dimensions(&self, dims: Vec3<u32>) -> (u32, u32) { (dims.x, dims.y * dims.z) } fn dimensions(&self, dims: Vec3<u32>) -> (u32, u32) { (dims.x, dims.y * dims.z) }
#[allow(clippy::many_single_char_names)]
fn index(&self, dims: Vec3<u32>, x: u32, y: u32, z: u32) -> (u32, u32) { fn index(&self, dims: Vec3<u32>, x: u32, y: u32, z: u32) -> (u32, u32) {
let i = x; let i = x;
let j0 = if self.flip_y { let j0 = if self.flip_y {
@ -103,6 +106,7 @@ impl PackingFormula for TallPacking {
/// A grid of the z levels, left to right, top to bottom, like English prose. /// A grid of the z levels, left to right, top to bottom, like English prose.
/// Convenient for visualizing terrain, but wastes space if the number of z /// Convenient for visualizing terrain, but wastes space if the number of z
/// levels isn't a perfect square. /// levels isn't a perfect square.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct GridLtrPacking; pub struct GridLtrPacking;
impl PackingFormula for GridLtrPacking { impl PackingFormula for GridLtrPacking {
@ -111,6 +115,7 @@ impl PackingFormula for GridLtrPacking {
(dims.x * rootz, dims.y * rootz) (dims.x * rootz, dims.y * rootz)
} }
#[allow(clippy::many_single_char_names)]
fn index(&self, dims: Vec3<u32>, x: u32, y: u32, z: u32) -> (u32, u32) { fn index(&self, dims: Vec3<u32>, x: u32, y: u32, z: u32) -> (u32, u32) {
let rootz = (dims.z as f64).sqrt().ceil() as u32; let rootz = (dims.z as f64).sqrt().ceil() as u32;
let i = x + (z % rootz) * dims.x; let i = x + (z % rootz) * dims.x;
@ -119,23 +124,36 @@ impl PackingFormula for GridLtrPacking {
} }
} }
pub trait VoxelImageEncoding { pub trait VoxelImageEncoding: Copy {
type Workspace; type Workspace;
type Output; type Output;
fn create(width: u32, height: u32) -> Self::Workspace; fn create(width: u32, height: u32) -> Self::Workspace;
fn put_solid(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>); fn put_solid(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>);
fn put_sprite(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, sprite: SpriteKind, ori: Option<u8>); fn put_sprite(
fn finish(ws: &Self::Workspace) -> Self::Output; ws: &mut Self::Workspace,
x: u32,
y: u32,
kind: BlockKind,
sprite: SpriteKind,
ori: Option<u8>,
);
fn finish(ws: &Self::Workspace) -> Option<Self::Output>;
} }
pub trait VoxelImageDecoding: VoxelImageEncoding {
fn start(ws: &Self::Output) -> Option<Self::Workspace>;
fn get_block(ws: &Self::Workspace, x: u32, y: u32) -> Block;
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct PngEncoding; pub struct PngEncoding;
impl VoxelImageEncoding for PngEncoding { impl VoxelImageEncoding for PngEncoding {
type Output = Vec<u8>; type Output = Vec<u8>;
type Workspace = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>; type Workspace = ImageBuffer<image::Rgba<u8>, Vec<u8>>;
fn create(width: u32, height: u32) -> Self::Workspace { fn create(width: u32, height: u32) -> Self::Workspace {
use image::{ImageBuffer, Rgba}; use image::Rgba;
ImageBuffer::<Rgba<u8>, Vec<u8>>::new(width, height) ImageBuffer::<Rgba<u8>, Vec<u8>>::new(width, height)
} }
@ -143,11 +161,22 @@ impl VoxelImageEncoding for PngEncoding {
ws.put_pixel(x, y, image::Rgba([rgb.r, rgb.g, rgb.b, 255 - kind as u8])); ws.put_pixel(x, y, image::Rgba([rgb.r, rgb.g, rgb.b, 255 - kind as u8]));
} }
fn put_sprite(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, sprite: SpriteKind, ori: Option<u8>) { fn put_sprite(
ws.put_pixel(x, y, image::Rgba([kind as u8, sprite as u8, ori.unwrap_or(0), 255])); ws: &mut Self::Workspace,
x: u32,
y: u32,
kind: BlockKind,
sprite: SpriteKind,
ori: Option<u8>,
) {
ws.put_pixel(
x,
y,
image::Rgba([kind as u8, sprite as u8, ori.unwrap_or(0), 255]),
);
} }
fn finish(ws: &Self::Workspace) -> Self::Output { fn finish(ws: &Self::Workspace) -> Option<Self::Output> {
use image::codecs::png::{CompressionType, FilterType}; use image::codecs::png::{CompressionType, FilterType};
let mut buf = Vec::new(); let mut buf = Vec::new();
let png = image::codecs::png::PngEncoder::new_with_quality( let png = image::codecs::png::PngEncoder::new_with_quality(
@ -161,19 +190,20 @@ impl VoxelImageEncoding for PngEncoding {
ws.height(), ws.height(),
image::ColorType::Rgba8, image::ColorType::Rgba8,
) )
.unwrap(); .ok()?;
buf Some(buf)
} }
} }
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct JpegEncoding; pub struct JpegEncoding;
impl VoxelImageEncoding for JpegEncoding { impl VoxelImageEncoding for JpegEncoding {
type Output = Vec<u8>; type Output = Vec<u8>;
type Workspace = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>; type Workspace = ImageBuffer<image::Rgba<u8>, Vec<u8>>;
fn create(width: u32, height: u32) -> Self::Workspace { fn create(width: u32, height: u32) -> Self::Workspace {
use image::{ImageBuffer, Rgba}; use image::Rgba;
ImageBuffer::<Rgba<u8>, Vec<u8>>::new(width, height) ImageBuffer::<Rgba<u8>, Vec<u8>>::new(width, height)
} }
@ -181,31 +211,39 @@ impl VoxelImageEncoding for JpegEncoding {
ws.put_pixel(x, y, image::Rgba([rgb.r, rgb.g, rgb.b, 255 - kind as u8])); ws.put_pixel(x, y, image::Rgba([rgb.r, rgb.g, rgb.b, 255 - kind as u8]));
} }
fn put_sprite(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, sprite: SpriteKind, _: Option<u8>) { fn put_sprite(
ws: &mut Self::Workspace,
x: u32,
y: u32,
kind: BlockKind,
sprite: SpriteKind,
_: Option<u8>,
) {
ws.put_pixel(x, y, image::Rgba([kind as u8, sprite as u8, 255, 255])); ws.put_pixel(x, y, image::Rgba([kind as u8, sprite as u8, 255, 255]));
} }
fn finish(ws: &Self::Workspace) -> Self::Output { fn finish(ws: &Self::Workspace) -> Option<Self::Output> {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1); let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1);
jpeg.encode_image(ws).unwrap(); jpeg.encode_image(ws).ok()?;
buf Some(buf)
} }
} }
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct MixedEncoding; pub struct MixedEncoding;
impl VoxelImageEncoding for MixedEncoding { impl VoxelImageEncoding for MixedEncoding {
type Output = (Vec<u8>, [usize; 3]); type Output = (Vec<u8>, [usize; 3]);
#[allow(clippy::type_complexity)]
type Workspace = ( type Workspace = (
image::ImageBuffer<image::Luma<u8>, Vec<u8>>, ImageBuffer<image::Luma<u8>, Vec<u8>>,
image::ImageBuffer<image::Luma<u8>, Vec<u8>>, ImageBuffer<image::Luma<u8>, Vec<u8>>,
image::ImageBuffer<image::Luma<u8>, Vec<u8>>, ImageBuffer<image::Luma<u8>, Vec<u8>>,
image::ImageBuffer<image::Rgb<u8>, Vec<u8>>, ImageBuffer<image::Rgb<u8>, Vec<u8>>,
); );
fn create(width: u32, height: u32) -> Self::Workspace { fn create(width: u32, height: u32) -> Self::Workspace {
use image::ImageBuffer;
( (
ImageBuffer::new(width, height), ImageBuffer::new(width, height),
ImageBuffer::new(width, height), ImageBuffer::new(width, height),
@ -221,39 +259,174 @@ impl VoxelImageEncoding for MixedEncoding {
ws.3.put_pixel(x, y, image::Rgb([rgb.r, rgb.g, rgb.b])); ws.3.put_pixel(x, y, image::Rgb([rgb.r, rgb.g, rgb.b]));
} }
fn put_sprite(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, sprite: SpriteKind, ori: Option<u8>) { fn put_sprite(
ws: &mut Self::Workspace,
x: u32,
y: u32,
kind: BlockKind,
sprite: SpriteKind,
ori: Option<u8>,
) {
ws.0.put_pixel(x, y, image::Luma([kind as u8])); ws.0.put_pixel(x, y, image::Luma([kind as u8]));
ws.1.put_pixel(x, y, image::Luma([sprite as u8])); ws.1.put_pixel(x, y, image::Luma([sprite as u8]));
ws.2.put_pixel(x, y, image::Luma([ori.unwrap_or(0)])); ws.2.put_pixel(x, y, image::Luma([ori.unwrap_or(0)]));
ws.3.put_pixel(x, y, image::Rgb([0; 3])); ws.3.put_pixel(x, y, image::Rgb([0; 3]));
} }
fn finish(ws: &Self::Workspace) -> Self::Output { fn finish(ws: &Self::Workspace) -> Option<Self::Output> {
let mut buf = Vec::new(); let mut buf = Vec::new();
use image::codecs::png::{CompressionType, FilterType}; use image::codecs::png::{CompressionType, FilterType};
let mut indices = [0; 3]; let mut indices = [0; 3];
let mut f = |x: &image::ImageBuffer<_, Vec<u8>>, i| { let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| {
let png = image::codecs::png::PngEncoder::new_with_quality( let png = image::codecs::png::PngEncoder::new_with_quality(
&mut buf, &mut buf,
CompressionType::Fast, CompressionType::Fast,
FilterType::Up, FilterType::Up,
); );
png.encode( png.encode(&*x.as_raw(), x.width(), x.height(), image::ColorType::L8)
&*x.as_raw(), .ok()?;
x.width(),
x.height(),
image::ColorType::L8,
)
.unwrap();
indices[i] = buf.len(); indices[i] = buf.len();
Some(())
}; };
f(&ws.0, 0); f(&ws.0, 0)?;
f(&ws.1, 1); f(&ws.1, 1)?;
f(&ws.2, 2); f(&ws.2, 2)?;
let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1); let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 10);
jpeg.encode_image(&ws.3).unwrap(); jpeg.encode_image(&ws.3).ok()?;
(buf, indices) Some((buf, indices))
}
}
fn image_from_bytes<'a, I: ImageDecoder<'a>, P: 'static + Pixel<Subpixel = u8>>(
decoder: I,
) -> Option<ImageBuffer<P, Vec<u8>>> {
let (w, h) = decoder.dimensions();
let mut buf = vec![0; decoder.total_bytes() as usize];
decoder.read_image(&mut buf).ok()?;
ImageBuffer::from_raw(w, h, buf)
}
impl VoxelImageDecoding for MixedEncoding {
fn start((quad, indices): &Self::Output) -> Option<Self::Workspace> {
use image::codecs::{jpeg::JpegDecoder, png::PngDecoder};
let ranges: [_; 4] = [
0..indices[0],
indices[0]..indices[1],
indices[1]..indices[2],
indices[2]..quad.len(),
];
tracing::info!("{:?} {:?}", ranges, indices);
let a = image_from_bytes(PngDecoder::new(&quad[ranges[0].clone()]).ok()?)?;
let b = image_from_bytes(PngDecoder::new(&quad[ranges[1].clone()]).ok()?)?;
let c = image_from_bytes(PngDecoder::new(&quad[ranges[2].clone()]).ok()?)?;
let d = image_from_bytes(JpegDecoder::new(&quad[ranges[3].clone()]).ok()?)?;
Some((a, b, c, d))
}
fn get_block(ws: &Self::Workspace, x: u32, y: u32) -> Block {
if let Some(kind) = BlockKind::from_u8(ws.0.get_pixel(x, y).0[0]) {
if kind.is_filled() {
let rgb = ws.3.get_pixel(x, y);
Block::new(kind, Rgb {
r: rgb[0],
g: rgb[1],
b: rgb[2],
})
} else {
let mut block = Block::new(kind, Rgb { r: 0, g: 0, b: 0 });
if let Some(spritekind) = SpriteKind::from_u8(ws.1.get_pixel(x, y).0[0]) {
block = block.with_sprite(spritekind);
}
if let Some(oriblock) = block.with_ori(ws.2.get_pixel(x, y).0[0]) {
block = oriblock;
}
block
}
} else {
Block::empty()
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct QuadPngEncoding;
impl VoxelImageEncoding for QuadPngEncoding {
type Output = CompressedData<(Vec<u8>, [usize; 3])>;
#[allow(clippy::type_complexity)]
type Workspace = (
ImageBuffer<image::Luma<u8>, Vec<u8>>,
ImageBuffer<image::Luma<u8>, Vec<u8>>,
ImageBuffer<image::Luma<u8>, Vec<u8>>,
ImageBuffer<image::Rgb<u8>, Vec<u8>>,
);
fn create(width: u32, height: u32) -> Self::Workspace {
(
ImageBuffer::new(width, height),
ImageBuffer::new(width, height),
ImageBuffer::new(width, height),
ImageBuffer::new(width, height),
)
}
fn put_solid(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>) {
ws.0.put_pixel(x, y, image::Luma([kind as u8]));
ws.1.put_pixel(x, y, image::Luma([0]));
ws.2.put_pixel(x, y, image::Luma([0]));
ws.3.put_pixel(x, y, image::Rgb([rgb.r, rgb.g, rgb.b]));
}
fn put_sprite(
ws: &mut Self::Workspace,
x: u32,
y: u32,
kind: BlockKind,
sprite: SpriteKind,
ori: Option<u8>,
) {
ws.0.put_pixel(x, y, image::Luma([kind as u8]));
ws.1.put_pixel(x, y, image::Luma([sprite as u8]));
ws.2.put_pixel(x, y, image::Luma([ori.unwrap_or(0)]));
ws.3.put_pixel(x, y, image::Rgb([0; 3]));
}
fn finish(ws: &Self::Workspace) -> Option<Self::Output> {
let mut buf = Vec::new();
use image::codecs::png::{CompressionType, FilterType};
let mut indices = [0; 3];
let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| {
let png = image::codecs::png::PngEncoder::new_with_quality(
&mut buf,
CompressionType::Fast,
FilterType::Up,
);
png.encode(&*x.as_raw(), x.width(), x.height(), image::ColorType::L8)
.ok()?;
indices[i] = buf.len();
Some(())
};
f(&ws.0, 0)?;
f(&ws.1, 1)?;
f(&ws.2, 2)?;
{
let png = image::codecs::png::PngEncoder::new_with_quality(
&mut buf,
CompressionType::Fast,
FilterType::Paeth,
);
png.encode(
&*ws.3.as_raw(),
ws.3.width(),
ws.3.height(),
image::ColorType::Rgb8,
)
.ok()?;
}
Some(CompressedData::compress(&(buf, indices), 4))
} }
} }
@ -261,7 +434,7 @@ pub fn image_terrain_chonk<S: RectVolSize, M: Clone, P: PackingFormula, VIE: Vox
vie: VIE, vie: VIE,
packing: P, packing: P,
chonk: &Chonk<Block, S, M>, chonk: &Chonk<Block, S, M>,
) -> VIE::Output { ) -> Option<VIE::Output> {
image_terrain( image_terrain(
vie, vie,
packing, packing,
@ -280,7 +453,7 @@ pub fn image_terrain_volgrid<
vie: VIE, vie: VIE,
packing: P, packing: P,
volgrid: &VolGrid2d<Chonk<Block, S, M>>, volgrid: &VolGrid2d<Chonk<Block, S, M>>,
) -> VIE::Output { ) -> Option<VIE::Output> {
let mut lo = Vec3::broadcast(i32::MAX); let mut lo = Vec3::broadcast(i32::MAX);
let mut hi = Vec3::broadcast(i32::MIN); let mut hi = Vec3::broadcast(i32::MIN);
for (pos, chonk) in volgrid.iter() { for (pos, chonk) in volgrid.iter() {
@ -306,8 +479,13 @@ pub fn image_terrain<
vol: &V, vol: &V,
lo: Vec3<u32>, lo: Vec3<u32>,
hi: Vec3<u32>, hi: Vec3<u32>,
) -> VIE::Output { ) -> Option<VIE::Output> {
let dims = hi - lo; tracing::info!("image_terrain: {:?} {:?}", lo, hi);
let dims = Vec3::new(
hi.x.wrapping_sub(lo.x),
hi.y.wrapping_sub(lo.y),
hi.z.wrapping_sub(lo.z),
);
let (width, height) = packing.dimensions(dims); let (width, height) = packing.dimensions(dims);
let mut image = VIE::create(width, height); let mut image = VIE::create(width, height);
@ -317,7 +495,14 @@ pub fn image_terrain<
let (i, j) = packing.index(dims, x, y, z); let (i, j) = packing.index(dims, x, y, z);
let block = *vol let block = *vol
.get(Vec3::new(x + lo.x, y + lo.y, z + lo.z).as_()) .get(
Vec3::new(
x.wrapping_add(lo.x),
y.wrapping_add(lo.y),
z.wrapping_add(lo.z),
)
.as_(),
)
.unwrap_or(&Block::empty()); .unwrap_or(&Block::empty());
match (block.get_color(), block.get_sprite()) { match (block.get_color(), block.get_sprite()) {
(Some(rgb), None) => { (Some(rgb), None) => {
@ -339,69 +524,85 @@ pub fn image_terrain<
VIE::finish(&image) VIE::finish(&image)
} }
pub struct MixedEncodingDenseSprites; pub fn write_image_terrain<
V: BaseVol<Vox = Block> + WriteVol,
impl VoxelImageEncoding for MixedEncodingDenseSprites { P: PackingFormula,
type Output = (Vec<u8>, [usize; 3]); VIE: VoxelImageEncoding + VoxelImageDecoding,
type Workspace = ( >(
image::ImageBuffer<image::Luma<u8>, Vec<u8>>, _: VIE,
Vec<u8>, packing: P,
Vec<u8>, vol: &mut V,
image::ImageBuffer<image::Rgb<u8>, Vec<u8>>, data: &VIE::Output,
lo: Vec3<u32>,
hi: Vec3<u32>,
) -> Option<()> {
let ws = VIE::start(data)?;
let dims = Vec3::new(
hi.x.wrapping_sub(lo.x),
hi.y.wrapping_sub(lo.y),
hi.z.wrapping_sub(lo.z),
); );
for z in 0..dims.z {
for y in 0..dims.y {
for x in 0..dims.x {
let (i, j) = packing.index(dims, x, y, z);
let block = VIE::get_block(&ws, i, j);
if let Err(e) = vol.set(lo.as_() + Vec3::new(x, y, z).as_(), block) {
warn!(
"Error placing a block into a volume at {:?}: {:?}",
(x, y, z),
e
);
}
}
}
}
Some(())
}
fn create(width: u32, height: u32) -> Self::Workspace { #[derive(Debug, Clone, Serialize, Deserialize)]
use image::ImageBuffer; pub struct WireChonk<VIE: VoxelImageEncoding, P: PackingFormula, M: Clone, S: RectVolSize> {
( zmin: i32,
ImageBuffer::new(width, height), zmax: i32,
Vec::new(), data: VIE::Output,
Vec::new(), below: Block,
ImageBuffer::new(width, height), above: Block,
) meta: M,
vie: VIE,
packing: P,
size: PhantomData<S>,
}
impl<VIE: VoxelImageEncoding + VoxelImageDecoding, P: PackingFormula, M: Clone, S: RectVolSize>
WireChonk<VIE, P, M, S>
{
pub fn from_chonk(vie: VIE, packing: P, chonk: &Chonk<Block, S, M>) -> Option<Self> {
let data = image_terrain_chonk(vie, packing, chonk)?;
Some(Self {
zmin: chonk.get_min_z(),
zmax: chonk.get_max_z(),
data,
below: *chonk
.get(Vec3::new(0, 0, chonk.get_min_z().saturating_sub(1)))
.ok()?,
above: *chonk.get(Vec3::new(0, 0, chonk.get_max_z() + 1)).ok()?,
meta: chonk.meta().clone(),
vie,
packing,
size: PhantomData,
})
} }
fn put_solid(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>) { pub fn to_chonk(&self) -> Option<Chonk<Block, S, M>> {
ws.0.put_pixel(x, y, image::Luma([kind as u8])); let mut chonk = Chonk::new(self.zmin, self.below, self.above, self.meta.clone());
ws.3.put_pixel(x, y, image::Rgb([rgb.r, rgb.g, rgb.b])); write_image_terrain(
} self.vie,
self.packing,
fn put_sprite(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, sprite: SpriteKind, ori: Option<u8>) { &mut chonk,
ws.0.put_pixel(x, y, image::Luma([kind as u8])); &self.data,
ws.1.push(sprite as u8); Vec3::new(0, 0, self.zmin as u32),
ws.2.push(ori.unwrap_or(0)); Vec3::new(S::RECT_SIZE.x, S::RECT_SIZE.y, self.zmax as u32),
ws.3.put_pixel(x, y, image::Rgb([0; 3])); )?;
} Some(chonk)
fn finish(ws: &Self::Workspace) -> Self::Output {
let mut buf = Vec::new();
use image::codecs::png::{CompressionType, FilterType};
let mut indices = [0; 3];
let mut f = |x: &image::ImageBuffer<_, Vec<u8>>, i| {
let png = image::codecs::png::PngEncoder::new_with_quality(
&mut buf,
CompressionType::Fast,
FilterType::Up,
);
png.encode(
&*x.as_raw(),
x.width(),
x.height(),
image::ColorType::L8,
)
.unwrap();
indices[i] = buf.len();
};
f(&ws.0, 0);
let mut g = |x: &[u8], i| {
buf.extend_from_slice(&*CompressedData::compress(&x, 4).data);
indices[i] = buf.len();
};
g(&ws.1, 1);
g(&ws.2, 2);
let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1);
jpeg.encode_image(&ws.3).unwrap();
(buf, indices)
} }
} }

View File

@ -9,12 +9,13 @@ pub use self::{
client::{ClientGeneral, ClientMsg, ClientRegister, ClientType}, client::{ClientGeneral, ClientMsg, ClientRegister, ClientType},
compression::{ compression::{
CompressedData, GridLtrPacking, JpegEncoding, MixedEncoding, PackingFormula, PngEncoding, CompressedData, GridLtrPacking, JpegEncoding, MixedEncoding, PackingFormula, PngEncoding,
TallPacking, VoxelImageEncoding, TallPacking, VoxelImageEncoding, WireChonk,
}, },
ecs_packet::EcsCompPacket, ecs_packet::EcsCompPacket,
server::{ server::{
CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate, CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate,
RegisterError, ServerGeneral, ServerInfo, ServerInit, ServerMsg, ServerRegisterAnswer, RegisterError, SerializedTerrainChunk, ServerGeneral, ServerInfo, ServerInit, ServerMsg,
ServerRegisterAnswer,
}, },
world_msg::WorldMapMsg, world_msg::WorldMapMsg,
}; };

View File

@ -1,4 +1,7 @@
use super::{world_msg::EconomyInfo, ClientType, CompressedData, EcsCompPacket, PingMsg}; use super::{
world_msg::EconomyInfo, ClientType, CompressedData, EcsCompPacket, MixedEncoding, PingMsg,
TallPacking, WireChonk,
};
use crate::sync; use crate::sync;
use common::{ use common::{
character::{self, CharacterItem}, character::{self, CharacterItem},
@ -6,13 +9,14 @@ use common::{
outcome::Outcome, outcome::Outcome,
recipe::RecipeBook, recipe::RecipeBook,
resources::TimeOfDay, resources::TimeOfDay,
terrain::{Block, TerrainChunk}, terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
trade::{PendingTrade, SitePrices, TradeId, TradeResult}, trade::{PendingTrade, SitePrices, TradeId, TradeResult},
uid::Uid, uid::Uid,
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::Duration; use std::time::Duration;
use tracing::warn;
use vek::*; use vek::*;
///This struct contains all messages the server might send (on different ///This struct contains all messages the server might send (on different
@ -62,6 +66,35 @@ pub enum ServerInit {
pub type ServerRegisterAnswer = Result<(), RegisterError>; pub type ServerRegisterAnswer = Result<(), RegisterError>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SerializedTerrainChunk {
DeflatedChonk(CompressedData<TerrainChunk>),
PngPngPngJpeg(WireChonk<MixedEncoding, TallPacking, TerrainChunkMeta, TerrainChunkSize>),
}
impl SerializedTerrainChunk {
pub fn deflate(chunk: &TerrainChunk) -> Self {
Self::DeflatedChonk(CompressedData::compress(chunk, 5))
}
pub fn image(chunk: &TerrainChunk) -> Self {
if let Some(wc) = WireChonk::from_chonk(MixedEncoding, TallPacking { flip_y: true }, chunk)
{
Self::PngPngPngJpeg(wc)
} else {
warn!("Image encoding failure occurred, falling back to deflate");
Self::deflate(chunk)
}
}
pub fn to_chunk(&self) -> Option<TerrainChunk> {
match self {
Self::DeflatedChonk(chonk) => chonk.decompress(),
Self::PngPngPngJpeg(wc) => wc.to_chonk(),
}
}
}
/// Messages sent from the server to the client /// Messages sent from the server to the client
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerGeneral { pub enum ServerGeneral {
@ -106,7 +139,7 @@ pub enum ServerGeneral {
// Ingame related AND terrain stream // Ingame related AND terrain stream
TerrainChunkUpdate { TerrainChunkUpdate {
key: Vec2<i32>, key: Vec2<i32>,
chunk: Result<CompressedData<TerrainChunk>, ()>, chunk: Result<SerializedTerrainChunk, ()>,
}, },
TerrainBlockUpdates(CompressedData<HashMap<Vec3<i32>, Block>>), TerrainBlockUpdates(CompressedData<HashMap<Vec3<i32>, Block>>),
// Always possible // Always possible

View File

@ -6,7 +6,7 @@ use common::{
vol::RectVolSize, vol::RectVolSize,
}; };
use common_ecs::{Job, Origin, ParMode, Phase, System}; use common_ecs::{Job, Origin, ParMode, Phase, System};
use common_net::msg::{ClientGeneral, CompressedData, ServerGeneral}; use common_net::msg::{ClientGeneral, SerializedTerrainChunk, ServerGeneral};
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage}; use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage};
use tracing::{debug, trace}; use tracing::{debug, trace};
@ -79,7 +79,7 @@ impl<'a> System<'a> for Sys {
network_metrics.chunks_served_from_memory.inc(); network_metrics.chunks_served_from_memory.inc();
client.send(ServerGeneral::TerrainChunkUpdate { client.send(ServerGeneral::TerrainChunkUpdate {
key, key,
chunk: Ok(CompressedData::compress(&chunk, 5)), chunk: Ok(SerializedTerrainChunk::image(&chunk)),
})? })?
}, },
None => { None => {

View File

@ -14,7 +14,7 @@ use common::{
LoadoutBuilder, SkillSetBuilder, LoadoutBuilder, SkillSetBuilder,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{CompressedData, ServerGeneral}; use common_net::msg::{SerializedTerrainChunk, ServerGeneral};
use common_state::TerrainChanges; use common_state::TerrainChanges;
use comp::Behavior; use comp::Behavior;
use specs::{Join, Read, ReadStorage, Write, WriteExpect}; use specs::{Join, Read, ReadStorage, Write, WriteExpect};
@ -224,7 +224,7 @@ impl<'a> System<'a> for Sys {
new_chunks.into_par_iter().for_each(|(key, chunk)| { new_chunks.into_par_iter().for_each(|(key, chunk)| {
let mut msg = Some(ServerGeneral::TerrainChunkUpdate { let mut msg = Some(ServerGeneral::TerrainChunkUpdate {
key, key,
chunk: Ok(CompressedData::compress(&*chunk, 5)), chunk: Ok(SerializedTerrainChunk::image(&*chunk)),
}); });
let mut lazy_msg = None; let mut lazy_msg = None;

View File

@ -1,7 +1,7 @@
use crate::{client::Client, presence::Presence}; use crate::{client::Client, presence::Presence};
use common::{comp::Pos, terrain::TerrainGrid}; use common::{comp::Pos, terrain::TerrainGrid};
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{CompressedData, ServerGeneral}; use common_net::msg::{CompressedData, SerializedTerrainChunk, ServerGeneral};
use common_state::TerrainChanges; use common_state::TerrainChanges;
use specs::{Join, Read, ReadExpect, ReadStorage}; use specs::{Join, Read, ReadExpect, ReadStorage};
@ -38,7 +38,7 @@ impl<'a> System<'a> for Sys {
lazy_msg = Some(client.prepare(ServerGeneral::TerrainChunkUpdate { lazy_msg = Some(client.prepare(ServerGeneral::TerrainChunkUpdate {
key: *chunk_key, key: *chunk_key,
chunk: Ok(match terrain.get_key(*chunk_key) { chunk: Ok(match terrain.get_key(*chunk_key) {
Some(chunk) => CompressedData::compress(&chunk, 5), Some(chunk) => SerializedTerrainChunk::image(&chunk),
None => break 'chunk, None => break 'chunk,
}), }),
})); }));

View File

@ -9,10 +9,11 @@ use common::{
}; };
use common_net::msg::compression::{ use common_net::msg::compression::{
image_terrain, image_terrain_chonk, image_terrain_volgrid, CompressedData, GridLtrPacking, image_terrain, image_terrain_chonk, image_terrain_volgrid, CompressedData, GridLtrPacking,
JpegEncoding, MixedEncoding, MixedEncodingDenseSprites, PackingFormula, PngEncoding, JpegEncoding, MixedEncoding, PackingFormula, PngEncoding, QuadPngEncoding, TallPacking,
TallPacking, VoxelImageEncoding, VoxelImageEncoding,
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use image::ImageBuffer;
use std::{ use std::{
io::{Read, Write}, io::{Read, Write},
sync::Arc, sync::Arc,
@ -120,6 +121,7 @@ fn channelize_dyna<M: Clone, A: Access>(
(blocks, r, g, b, sprites) (blocks, r, g, b, sprites)
} }
#[derive(Debug, Clone, Copy)]
pub struct MixedEncodingSparseSprites; pub struct MixedEncodingSparseSprites;
impl VoxelImageEncoding for MixedEncodingSparseSprites { impl VoxelImageEncoding for MixedEncodingSparseSprites {
@ -135,7 +137,6 @@ impl VoxelImageEncoding for MixedEncodingSparseSprites {
); );
fn create(width: u32, height: u32) -> Self::Workspace { fn create(width: u32, height: u32) -> Self::Workspace {
use image::ImageBuffer;
( (
ImageBuffer::new(width, height), ImageBuffer::new(width, height),
ImageBuffer::new(width, height), ImageBuffer::new(width, height),
@ -161,7 +162,7 @@ impl VoxelImageEncoding for MixedEncodingSparseSprites {
ws.2.insert(Vec2::new(x, y), (sprite, ori.unwrap_or(0))); ws.2.insert(Vec2::new(x, y), (sprite, ori.unwrap_or(0)));
} }
fn finish(ws: &Self::Workspace) -> Self::Output { fn finish(ws: &Self::Workspace) -> Option<Self::Output> {
let mut buf = Vec::new(); let mut buf = Vec::new();
use image::codecs::png::{CompressionType, FilterType}; use image::codecs::png::{CompressionType, FilterType};
let png = image::codecs::png::PngEncoder::new_with_quality( let png = image::codecs::png::PngEncoder::new_with_quality(
@ -175,11 +176,81 @@ impl VoxelImageEncoding for MixedEncodingSparseSprites {
ws.0.height(), ws.0.height(),
image::ColorType::L8, image::ColorType::L8,
) )
.unwrap(); .ok()?;
let index = buf.len(); let index = buf.len();
let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1); let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1);
jpeg.encode_image(&ws.1).unwrap(); jpeg.encode_image(&ws.1).ok()?;
(buf, index, CompressedData::compress(&ws.2, 4)) Some((buf, index, CompressedData::compress(&ws.2, 4)))
}
}
#[derive(Debug, Clone, Copy)]
pub struct MixedEncodingDenseSprites;
impl VoxelImageEncoding for MixedEncodingDenseSprites {
type Output = (Vec<u8>, [usize; 3]);
type Workspace = (
ImageBuffer<image::Luma<u8>, Vec<u8>>,
Vec<u8>,
Vec<u8>,
ImageBuffer<image::Rgb<u8>, Vec<u8>>,
);
fn create(width: u32, height: u32) -> Self::Workspace {
(
ImageBuffer::new(width, height),
Vec::new(),
Vec::new(),
ImageBuffer::new(width, height),
)
}
fn put_solid(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>) {
ws.0.put_pixel(x, y, image::Luma([kind as u8]));
ws.3.put_pixel(x, y, image::Rgb([rgb.r, rgb.g, rgb.b]));
}
fn put_sprite(
ws: &mut Self::Workspace,
x: u32,
y: u32,
kind: BlockKind,
sprite: SpriteKind,
ori: Option<u8>,
) {
ws.0.put_pixel(x, y, image::Luma([kind as u8]));
ws.1.push(sprite as u8);
ws.2.push(ori.unwrap_or(0));
ws.3.put_pixel(x, y, image::Rgb([0; 3]));
}
fn finish(ws: &Self::Workspace) -> Option<Self::Output> {
let mut buf = Vec::new();
use image::codecs::png::{CompressionType, FilterType};
let mut indices = [0; 3];
let mut f = |x: &ImageBuffer<_, Vec<u8>>, i| {
let png = image::codecs::png::PngEncoder::new_with_quality(
&mut buf,
CompressionType::Fast,
FilterType::Up,
);
png.encode(&*x.as_raw(), x.width(), x.height(), image::ColorType::L8)
.ok()?;
indices[i] = buf.len();
Some(())
};
f(&ws.0, 0)?;
let mut g = |x: &[u8], i| {
buf.extend_from_slice(&*CompressedData::compress(&x, 4).data);
indices[i] = buf.len();
};
g(&ws.1, 1);
g(&ws.2, 2);
let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1);
jpeg.encode_image(&ws.3).ok()?;
Some((buf, indices))
} }
} }
@ -258,8 +329,8 @@ fn main() {
)); ));
for (sitename, sitepos) in sites.iter() { for (sitename, sitepos) in sites.iter() {
let mut totals = [0.0; 12]; let mut totals = [0.0; 13];
let mut total_timings = [0.0; 9]; let mut total_timings = [0.0; 10];
let mut count = 0; let mut count = 0;
let mut volgrid = VolGrid2d::new().unwrap(); let mut volgrid = VolGrid2d::new().unwrap();
for (i, spiralpos) in Spiral2d::new() for (i, spiralpos) in Spiral2d::new()
@ -305,7 +376,8 @@ fn main() {
do_deflate_flate2(&bincode::serialize(&channelize_dyna(&dyna)).unwrap()); do_deflate_flate2(&bincode::serialize(&channelize_dyna(&dyna)).unwrap());
let jpegchonkgrid_pre = Instant::now(); let jpegchonkgrid_pre = Instant::now();
let jpegchonkgrid = image_terrain_chonk(JpegEncoding, GridLtrPacking, &chunk); let jpegchonkgrid =
image_terrain_chonk(JpegEncoding, GridLtrPacking, &chunk).unwrap();
let jpegchonkgrid_post = Instant::now(); let jpegchonkgrid_post = Instant::now();
if false { if false {
@ -320,29 +392,42 @@ fn main() {
let jpegchonktall_pre = Instant::now(); let jpegchonktall_pre = Instant::now();
let jpegchonktall = let jpegchonktall =
image_terrain_chonk(JpegEncoding, TallPacking { flip_y: false }, &chunk); image_terrain_chonk(JpegEncoding, TallPacking { flip_y: false }, &chunk)
.unwrap();
let jpegchonktall_post = Instant::now(); let jpegchonktall_post = Instant::now();
let jpegchonkflip_pre = Instant::now(); let jpegchonkflip_pre = Instant::now();
let jpegchonkflip = let jpegchonkflip =
image_terrain_chonk(JpegEncoding, TallPacking { flip_y: true }, &chunk); image_terrain_chonk(JpegEncoding, TallPacking { flip_y: true }, &chunk)
.unwrap();
let jpegchonkflip_post = Instant::now(); let jpegchonkflip_post = Instant::now();
let mixedchonk_pre = Instant::now(); let mixedchonk_pre = Instant::now();
let mixedchonk = let mixedchonk =
image_terrain_chonk(MixedEncoding, TallPacking { flip_y: true }, &chunk); image_terrain_chonk(MixedEncoding, TallPacking { flip_y: true }, &chunk)
.unwrap();
let mixedchonk_post = Instant::now(); let mixedchonk_post = Instant::now();
let mixeddeflate = CompressedData::compress(&mixedchonk, 1); let mixeddeflate = CompressedData::compress(&mixedchonk, 1);
let mixeddeflate_post = Instant::now(); let mixeddeflate_post = Instant::now();
let mixeddense_pre = Instant::now(); let mixeddense_pre = Instant::now();
let mixeddense = let mixeddense = image_terrain_chonk(
image_terrain_chonk(MixedEncodingDenseSprites, TallPacking { flip_y: true }, &chunk); MixedEncodingDenseSprites,
TallPacking { flip_y: true },
&chunk,
)
.unwrap();
let mixeddense_post = Instant::now(); let mixeddense_post = Instant::now();
let quadpng_pre = Instant::now();
let quadpng =
image_terrain_chonk(QuadPngEncoding, TallPacking { flip_y: true }, &chunk)
.unwrap();
let quadpng_post = Instant::now();
let pngchonk_pre = Instant::now(); let pngchonk_pre = Instant::now();
let pngchonk = image_terrain_chonk(PngEncoding, GridLtrPacking, &chunk); let pngchonk = image_terrain_chonk(PngEncoding, GridLtrPacking, &chunk).unwrap();
let pngchonk_post = Instant::now(); let pngchonk_post = Instant::now();
let n = uncompressed.len(); let n = uncompressed.len();
@ -358,6 +443,7 @@ fn main() {
mixedchonk.0.len() as f32 / n as f32, mixedchonk.0.len() as f32 / n as f32,
mixeddeflate.data.len() as f32 / n as f32, mixeddeflate.data.len() as f32 / n as f32,
mixeddense.0.len() as f32 / n as f32, mixeddense.0.len() as f32 / n as f32,
quadpng.data.len() as f32 / n as f32,
pngchonk.len() as f32 / n as f32, pngchonk.len() as f32 / n as f32,
]; ];
let best_idx = sizes let best_idx = sizes
@ -380,6 +466,7 @@ fn main() {
(mixedchonk_post - mixedchonk_pre).subsec_nanos(), (mixedchonk_post - mixedchonk_pre).subsec_nanos(),
(mixeddeflate_post - mixedchonk_pre).subsec_nanos(), (mixeddeflate_post - mixedchonk_pre).subsec_nanos(),
(mixeddense_post - mixeddense_pre).subsec_nanos(), (mixeddense_post - mixeddense_pre).subsec_nanos(),
(quadpng_post - quadpng_pre).subsec_nanos(),
(pngchonk_post - pngchonk_pre).subsec_nanos(), (pngchonk_post - pngchonk_pre).subsec_nanos(),
]; ];
trace!( trace!(
@ -408,12 +495,12 @@ fn main() {
let mut f = let mut f =
File::create(&format!("chonkjpegs/{}_{}.jpg", sitename, count)).unwrap(); File::create(&format!("chonkjpegs/{}_{}.jpg", sitename, count)).unwrap();
let jpeg_volgrid = let jpeg_volgrid =
image_terrain_volgrid(JpegEncoding, GridLtrPacking, &volgrid); image_terrain_volgrid(JpegEncoding, GridLtrPacking, &volgrid).unwrap();
f.write_all(&*jpeg_volgrid).unwrap(); f.write_all(&*jpeg_volgrid).unwrap();
let mixedgrid_pre = Instant::now(); let mixedgrid_pre = Instant::now();
let (mixed_volgrid, indices) = let (mixed_volgrid, indices) =
image_terrain_volgrid(MixedEncoding, GridLtrPacking, &volgrid); image_terrain_volgrid(MixedEncoding, GridLtrPacking, &volgrid).unwrap();
let mixedgrid_post = Instant::now(); let mixedgrid_post = Instant::now();
let seconds = (mixedgrid_post - mixedgrid_pre).as_secs_f64(); let seconds = (mixedgrid_post - mixedgrid_pre).as_secs_f64();
println!( println!(
@ -455,7 +542,8 @@ fn main() {
println!("Average mixedchonk: {}", totals[8] / count as f32); println!("Average mixedchonk: {}", totals[8] / count as f32);
println!("Average mixeddeflate: {}", totals[9] / count as f32); println!("Average mixeddeflate: {}", totals[9] / count as f32);
println!("Average mixeddense: {}", totals[10] / count as f32); println!("Average mixeddense: {}", totals[10] / count as f32);
println!("Average pngchonk: {}", totals[11] / count as f32); println!("Average quadpng: {}", totals[11] / count as f32);
println!("Average pngchonk: {}", totals[12] / count as f32);
println!(""); println!("");
println!( println!(
"Average lz4_chonk nanos : {:02}", "Average lz4_chonk nanos : {:02}",
@ -490,9 +578,13 @@ fn main() {
total_timings[7] / count as f32 total_timings[7] / count as f32
); );
println!( println!(
"Average pngchonk nanos: {:02}", "Average quadpng nanos: {:02}",
total_timings[8] / count as f32 total_timings[8] / count as f32
); );
println!(
"Average pngchonk nanos: {:02}",
total_timings[9] / count as f32
);
println!("-----"); println!("-----");
} }
if i % 256 == 0 { if i % 256 == 0 {