From cdc2eccda81b9dad1f9b343ed018087a26efefdb Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Mon, 26 Apr 2021 23:52:10 -0400 Subject: [PATCH] Improve `quadpng` by adding `WidePacking`, which makes a wider image, which is faster due to PNG compressing by row. Heuristically switch between quadpng and deflate based on chunk height to reduce variance. --- common/net/src/msg/compression.rs | 33 +++++++++++++-- common/net/src/msg/mod.rs | 2 +- common/net/src/msg/server.rs | 42 ++++++------------- server/src/sys/msg/terrain.rs | 4 +- server/src/sys/terrain.rs | 2 +- server/src/sys/terrain_sync.rs | 2 +- world/src/bin/chunk_compression_benchmarks.rs | 39 +++++++++++++---- 7 files changed, 79 insertions(+), 45 deletions(-) diff --git a/common/net/src/msg/compression.rs b/common/net/src/msg/compression.rs index 9d9aa52b62..9dd6c114ab 100644 --- a/common/net/src/msg/compression.rs +++ b/common/net/src/msg/compression.rs @@ -89,9 +89,11 @@ pub struct TallPacking { } impl PackingFormula for TallPacking { + #[inline(always)] fn dimensions(&self, dims: Vec3) -> (u32, u32) { (dims.x, dims.y * dims.z) } #[allow(clippy::many_single_char_names)] + #[inline(always)] fn index(&self, dims: Vec3, x: u32, y: u32, z: u32) -> (u32, u32) { let i = x; let j0 = if self.flip_y { @@ -104,6 +106,27 @@ impl PackingFormula for TallPacking { } } +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct WidePacking(); + +impl PackingFormula for WidePacking { + #[inline(always)] + fn dimensions(&self, dims: Vec3) -> (u32, u32) { (dims.x * dims.z, dims.y) } + + #[allow(clippy::many_single_char_names)] + #[inline(always)] + fn index(&self, dims: Vec3, x: u32, y: u32, z: u32) -> (u32, u32) { + let i0 = if FLIP_X { + if z % 2 == 0 { x } else { dims.x - x - 1 } + } else { + x + }; + let i = z * dims.x + i0; + let j = y; + (i, j) + } +} + /// 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 /// levels isn't a perfect square. @@ -111,12 +134,14 @@ impl PackingFormula for TallPacking { pub struct GridLtrPacking; impl PackingFormula for GridLtrPacking { + #[inline(always)] fn dimensions(&self, dims: Vec3) -> (u32, u32) { let rootz = (dims.z as f64).sqrt().ceil() as u32; (dims.x * rootz, dims.y * rootz) } #[allow(clippy::many_single_char_names)] + #[inline(always)] fn index(&self, dims: Vec3, x: u32, y: u32, z: u32) -> (u32, u32) { let rootz = (dims.z as f64).sqrt().ceil() as u32; let i = x + (z % rootz) * dims.x; @@ -371,13 +396,15 @@ impl VoxelImageEncoding for QuadPngEncoding { ) } + #[inline(always)] fn put_solid(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb) { 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.1.put_pixel(x, y, image::Luma([0])); + //ws.2.put_pixel(x, y, image::Luma([0])); ws.3.put_pixel(x / N, y / N, image::Rgb([rgb.r, rgb.g, rgb.b])); } + #[inline(always)] fn put_sprite( ws: &mut Self::Workspace, x: u32, @@ -414,7 +441,7 @@ impl VoxelImageEncoding for QuadPngEncoding { let png = image::codecs::png::PngEncoder::new_with_quality( &mut buf, CompressionType::Rle, - FilterType::Paeth, + FilterType::Sub, ); png.encode( &*ws.3.as_raw(), diff --git a/common/net/src/msg/mod.rs b/common/net/src/msg/mod.rs index 58ceecee9b..65fea7d8cb 100644 --- a/common/net/src/msg/mod.rs +++ b/common/net/src/msg/mod.rs @@ -9,7 +9,7 @@ pub use self::{ client::{ClientGeneral, ClientMsg, ClientRegister, ClientType}, compression::{ CompressedData, GridLtrPacking, JpegEncoding, MixedEncoding, PackingFormula, PngEncoding, - QuadPngEncoding, TallPacking, TriPngEncoding, VoxelImageEncoding, WireChonk, + QuadPngEncoding, TallPacking, TriPngEncoding, VoxelImageEncoding, WidePacking, WireChonk, }, ecs_packet::EcsCompPacket, server::{ diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs index 59e8c37877..25cef048ee 100644 --- a/common/net/src/msg/server.rs +++ b/common/net/src/msg/server.rs @@ -1,6 +1,6 @@ use super::{ - world_msg::EconomyInfo, ClientType, CompressedData, EcsCompPacket, MixedEncoding, PingMsg, - QuadPngEncoding, TallPacking, TriPngEncoding, WireChonk, + world_msg::EconomyInfo, ClientType, CompressedData, EcsCompPacket, PingMsg, QuadPngEncoding, + TriPngEncoding, WidePacking, WireChonk, }; use crate::sync; use common::{ @@ -69,39 +69,25 @@ pub type ServerRegisterAnswer = Result<(), RegisterError>; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SerializedTerrainChunk { DeflatedChonk(CompressedData), - PngPngPngJpeg(WireChonk), - QuadPng(WireChonk, TallPacking, TerrainChunkMeta, TerrainChunkSize>), - TriPng(WireChonk), + QuadPng(WireChonk, WidePacking, TerrainChunkMeta, TerrainChunkSize>), + TriPng(WireChonk, TerrainChunkMeta, TerrainChunkSize>), } impl SerializedTerrainChunk { - pub fn image(chunk: &TerrainChunk) -> Self { - match 2 { - 0 => Self::deflate(chunk), - 1 => Self::jpeg(chunk), - 2 => Self::quadpng(chunk), - _ => Self::tripng(chunk), - } - } - - pub fn deflate(chunk: &TerrainChunk) -> Self { - Self::DeflatedChonk(CompressedData::compress(chunk, 5)) - } - - pub fn jpeg(chunk: &TerrainChunk) -> Self { - if let Some(wc) = WireChonk::from_chonk(MixedEncoding, TallPacking { flip_y: true }, chunk) - { - Self::PngPngPngJpeg(wc) + pub fn via_heuristic(chunk: &TerrainChunk) -> Self { + if chunk.get_max_z() - chunk.get_min_z() < 128 { + Self::quadpng(chunk) } else { - warn!("Image encoding failure occurred, falling back to deflate"); Self::deflate(chunk) } } + pub fn deflate(chunk: &TerrainChunk) -> Self { + Self::DeflatedChonk(CompressedData::compress(chunk, 1)) + } + pub fn quadpng(chunk: &TerrainChunk) -> Self { - if let Some(wc) = - WireChonk::from_chonk(QuadPngEncoding(), TallPacking { flip_y: true }, chunk) - { + if let Some(wc) = WireChonk::from_chonk(QuadPngEncoding(), WidePacking(), chunk) { Self::QuadPng(wc) } else { warn!("Image encoding failure occurred, falling back to deflate"); @@ -110,8 +96,7 @@ impl SerializedTerrainChunk { } pub fn tripng(chunk: &TerrainChunk) -> Self { - if let Some(wc) = WireChonk::from_chonk(TriPngEncoding, TallPacking { flip_y: true }, chunk) - { + if let Some(wc) = WireChonk::from_chonk(TriPngEncoding, WidePacking(), chunk) { Self::TriPng(wc) } else { warn!("Image encoding failure occurred, falling back to deflate"); @@ -122,7 +107,6 @@ impl SerializedTerrainChunk { pub fn to_chunk(&self) -> Option { match self { Self::DeflatedChonk(chonk) => chonk.decompress(), - Self::PngPngPngJpeg(wc) => wc.to_chonk(), Self::QuadPng(wc) => wc.to_chonk(), Self::TriPng(wc) => wc.to_chonk(), } diff --git a/server/src/sys/msg/terrain.rs b/server/src/sys/msg/terrain.rs index 6793f7c743..e3902af7cf 100644 --- a/server/src/sys/msg/terrain.rs +++ b/server/src/sys/msg/terrain.rs @@ -79,7 +79,9 @@ impl<'a> System<'a> for Sys { network_metrics.chunks_served_from_memory.inc(); client.send(ServerGeneral::TerrainChunkUpdate { key, - chunk: Ok(SerializedTerrainChunk::image(&chunk)), + chunk: Ok(SerializedTerrainChunk::via_heuristic( + &chunk, + )), })? }, None => { diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 88e3beca63..03aeedba99 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -224,7 +224,7 @@ impl<'a> System<'a> for Sys { new_chunks.into_par_iter().for_each(|(key, chunk)| { let mut msg = Some(ServerGeneral::TerrainChunkUpdate { key, - chunk: Ok(SerializedTerrainChunk::image(&*chunk)), + chunk: Ok(SerializedTerrainChunk::via_heuristic(&*chunk)), }); let mut lazy_msg = None; diff --git a/server/src/sys/terrain_sync.rs b/server/src/sys/terrain_sync.rs index ca8fd251a8..178fc0f604 100644 --- a/server/src/sys/terrain_sync.rs +++ b/server/src/sys/terrain_sync.rs @@ -38,7 +38,7 @@ impl<'a> System<'a> for Sys { lazy_msg = Some(client.prepare(ServerGeneral::TerrainChunkUpdate { key: *chunk_key, chunk: Ok(match terrain.get_key(*chunk_key) { - Some(chunk) => SerializedTerrainChunk::image(&chunk), + Some(chunk) => SerializedTerrainChunk::via_heuristic(&chunk), None => break 'chunk, }), })); diff --git a/world/src/bin/chunk_compression_benchmarks.rs b/world/src/bin/chunk_compression_benchmarks.rs index 7e51bc3c27..75a61415c8 100644 --- a/world/src/bin/chunk_compression_benchmarks.rs +++ b/world/src/bin/chunk_compression_benchmarks.rs @@ -10,6 +10,7 @@ use common::{ use common_net::msg::compression::{ image_terrain_chonk, image_terrain_volgrid, CompressedData, GridLtrPacking, JpegEncoding, MixedEncoding, PngEncoding, QuadPngEncoding, TallPacking, TriPngEncoding, VoxelImageEncoding, + WidePacking, }; use hashbrown::HashMap; use image::ImageBuffer; @@ -417,7 +418,7 @@ fn main() { bucket.0 += 1; bucket.1 += (lz4chonk_post - lz4chonk_pre).subsec_nanos() as f32; } - { + if false { let bucket = z_buckets .entry("rle") .or_default() @@ -426,7 +427,7 @@ fn main() { bucket.0 += 1; bucket.1 += (rlechonk_post - rlechonk_pre).subsec_nanos() as f32; } - { + if false { let bucket = z_buckets .entry("deflate0") .or_default() @@ -604,24 +605,32 @@ fn main() { .unwrap(); let quadpnghalf_post = Instant::now(); - let quadpngquart_pre = Instant::now(); - let quadpngquart = image_terrain_chonk( + let quadpngquarttall_pre = Instant::now(); + let quadpngquarttall = image_terrain_chonk( QuadPngEncoding::<4>(), TallPacking { flip_y: true }, &chunk, ) .unwrap(); - let quadpngquart_post = Instant::now(); + let quadpngquarttall_post = Instant::now(); + + let quadpngquartwide_pre = Instant::now(); + let quadpngquartwide = + image_terrain_chonk(QuadPngEncoding::<4>(), WidePacking::(), &chunk) + .unwrap(); + let quadpngquartwide_post = Instant::now(); let tripng_pre = Instant::now(); let tripng = image_terrain_chonk(TriPngEncoding, TallPacking { flip_y: true }, &chunk) .unwrap(); let tripng_post = Instant::now(); + #[rustfmt::skip] sizes.extend_from_slice(&[ ("quadpngfull", quadpngfull.data.len() as f32 / n as f32), ("quadpnghalf", quadpnghalf.data.len() as f32 / n as f32), - ("quadpngquart", quadpngquart.data.len() as f32 / n as f32), + ("quadpngquarttall", quadpngquarttall.data.len() as f32 / n as f32), + ("quadpngquartwide", quadpngquartwide.data.len() as f32 / n as f32), ("tripng", tripng.data.len() as f32 / n as f32), ]); let best_idx = sizes @@ -639,19 +648,31 @@ fn main() { timings.extend_from_slice(&[ ("quadpngfull", (quadpngfull_post - quadpngfull_pre).subsec_nanos()), ("quadpnghalf", (quadpnghalf_post - quadpnghalf_pre).subsec_nanos()), - ("quadpngquart", (quadpngquart_post - quadpngquart_pre).subsec_nanos()), + ("quadpngquarttall", (quadpngquarttall_post - quadpngquarttall_pre).subsec_nanos()), + ("quadpngquartwide", (quadpngquartwide_post - quadpngquartwide_pre).subsec_nanos()), ("tripng", (tripng_post - tripng_pre).subsec_nanos()), ]); { let bucket = z_buckets - .entry("quadpngquart") + .entry("quadpngquarttall") .or_default() .entry(chunk.get_max_z() - chunk.get_min_z()) .or_insert((0, 0.0)); bucket.0 += 1; - bucket.1 += (quadpngquart_post - quadpngquart_pre).subsec_nanos() as f32; + bucket.1 += + (quadpngquarttall_post - quadpngquarttall_pre).subsec_nanos() as f32; } { + let bucket = z_buckets + .entry("quadpngquartwide") + .or_default() + .entry(chunk.get_max_z() - chunk.get_min_z()) + .or_insert((0, 0.0)); + bucket.0 += 1; + bucket.1 += + (quadpngquartwide_post - quadpngquartwide_pre).subsec_nanos() as f32; + } + if false { let bucket = z_buckets .entry("tripng") .or_default()