diff --git a/common/net/src/msg/compression.rs b/common/net/src/msg/compression.rs index e047b943c7..52777fea3a 100644 --- a/common/net/src/msg/compression.rs +++ b/common/net/src/msg/compression.rs @@ -184,9 +184,11 @@ impl<'a, VIE: VoxelImageDecoding> VoxelImageDecoding for &'a VIE { } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct QuadPngEncoding(); +pub struct QuadPngEncoding(); -impl VoxelImageEncoding for QuadPngEncoding { +impl VoxelImageEncoding + for QuadPngEncoding +{ type Output = CompressedData<(Vec, [usize; 3], Vec<[u8; 3]>)>; type Workspace = ( ImageBuffer, Vec>, @@ -194,6 +196,7 @@ impl VoxelImageEncoding for QuadPngEncoding { ImageBuffer, Vec>, ImageBuffer, Vec>, Vec<[u8; 3]>, + HashMap<[u8; 3], u16>, ); fn create(width: u32, height: u32) -> Self::Workspace { @@ -203,6 +206,7 @@ impl VoxelImageEncoding for QuadPngEncoding { ImageBuffer::new(width, height), ImageBuffer::new(width / N, height / N), Vec::new(), + HashMap::new(), ) } @@ -221,12 +225,24 @@ impl VoxelImageEncoding for QuadPngEncoding { kind: BlockKind, sprite_data: [u8; 3], ) { - let index: u16 = - ws.4.len() - .try_into() - .expect("Cannot have more than 2^16 sprites in one chunk"); - let index = index.to_be_bytes(); - ws.4.push(sprite_data); + let index = if HASH_CONS_SPRITES { + let index = ws.5.entry(sprite_data).or_insert_with(|| { + let index = + ws.4.len() + .try_into() + .expect("Cannot have more than 2^16 sprites in one chunk"); + ws.4.push(sprite_data); + index + }); + index.to_be_bytes() + } else { + let index: u16 = + ws.4.len() + .try_into() + .expect("Cannot have more than 2^16 sprites in one chunk"); + ws.4.push(sprite_data); + index.to_be_bytes() + }; ws.0.put_pixel(x, y, image::Luma([kind as u8])); ws.1.put_pixel(x, y, image::Luma([index[0]])); @@ -323,7 +339,9 @@ const fn gen_lanczos_lookup( array } -impl VoxelImageDecoding for QuadPngEncoding { +impl VoxelImageDecoding + for QuadPngEncoding +{ fn start(data: &Self::Output) -> Option { use image::codecs::png::PngDecoder; let (quad, indices, sprite_data) = data.decompress()?; @@ -337,7 +355,7 @@ impl VoxelImageDecoding for QuadPngEncoding { 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(PngDecoder::new(&quad[ranges[3].clone()]).ok()?)?; - Some((a, b, c, d, sprite_data)) + Some((a, b, c, d, sprite_data, HashMap::new())) } fn get_block(ws: &Self::Workspace, x: u32, y: u32, is_border: bool) -> Block { @@ -461,9 +479,11 @@ impl VoxelImageDecoding for QuadPngEncoding { } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct TriPngEncoding(); +pub struct TriPngEncoding(); -impl VoxelImageEncoding for TriPngEncoding { +impl VoxelImageEncoding + for TriPngEncoding +{ type Output = CompressedData<(Vec, Vec>, [usize; 3], Vec<[u8; 3]>)>; type Workspace = ( ImageBuffer, Vec>, @@ -471,6 +491,7 @@ impl VoxelImageEncoding for TriPngEncoding, Vec>, HashMap, usize>>, Vec<[u8; 3]>, + HashMap<[u8; 3], u16>, ); fn create(width: u32, height: u32) -> Self::Workspace { @@ -480,6 +501,7 @@ impl VoxelImageEncoding for TriPngEncoding VoxelImageEncoding for TriPngEncoding VoxelImageEncoding for TriPngEncoding VoxelImageDecoding for TriPngEncoding { +impl VoxelImageDecoding + for TriPngEncoding +{ fn start(data: &Self::Output) -> Option { use image::codecs::png::PngDecoder; let (quad, palette, indices, sprite_data) = data.decompress()?; @@ -585,7 +621,7 @@ impl VoxelImageDecoding for TriPngEncoding Block { diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs index 89c79d76fa..367d2fb420 100644 --- a/common/net/src/msg/server.rs +++ b/common/net/src/msg/server.rs @@ -83,8 +83,17 @@ pub type ServerRegisterAnswer = Result<(), RegisterError>; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SerializedTerrainChunk { DeflatedChonk(CompressedData), - QuadPng(WireChonk, WidePacking, TerrainChunkMeta, TerrainChunkSize>), - TriPng(WireChonk, WidePacking, TerrainChunkMeta, TerrainChunkSize>), + QuadPng( + WireChonk, WidePacking, TerrainChunkMeta, TerrainChunkSize>, + ), + TriPng( + WireChonk< + TriPngEncoding, + WidePacking, + TerrainChunkMeta, + TerrainChunkSize, + >, + ), } impl SerializedTerrainChunk { diff --git a/world/examples/chunk_compression_benchmarks.rs b/world/examples/chunk_compression_benchmarks.rs index eee88c6816..17e9b40a97 100644 --- a/world/examples/chunk_compression_benchmarks.rs +++ b/world/examples/chunk_compression_benchmarks.rs @@ -21,6 +21,7 @@ use serde::{Deserialize, Serialize}; use std::{ collections::BTreeMap, io::{Read, Write}, + mem, sync::Arc, time::Instant, }; @@ -980,10 +981,33 @@ fn main() { let pngchonk_post = Instant::now(); sizes.extend_from_slice(&[ - ("jpegchonkgrid", jpegchonkgrid.0.len() as f32 / n as f32), - ("jpegchonktall", jpegchonktall.0.len() as f32 / n as f32), - ("jpegchonkflip", jpegchonkflip.0.len() as f32 / n as f32), - ("pngchonk", pngchonk.0.len() as f32 / n as f32), + ( + "jpegchonkgrid", + (jpegchonkgrid.0.len() + + jpegchonkgrid.1.len() * mem::size_of::<[u8; 3]>()) + as f32 + / n as f32, + ), + ( + "jpegchonktall", + (jpegchonktall.0.len() + + jpegchonktall.1.len() * mem::size_of::<[u8; 3]>()) + as f32 + / n as f32, + ), + ( + "jpegchonkflip", + (jpegchonkflip.0.len() + + jpegchonkflip.1.len() * mem::size_of::<[u8; 3]>()) + as f32 + / n as f32, + ), + ( + "pngchonk", + (pngchonk.0.len() + pngchonk.1.len() * mem::size_of::<[u8; 3]>()) + as f32 + / n as f32, + ), ]); #[rustfmt::skip] timings.extend_from_slice(&[ @@ -1026,7 +1050,7 @@ fn main() { let quadpngfull_pre = Instant::now(); let quadpngfull = image_terrain_chonk( - &QuadPngEncoding::<1>(), + &QuadPngEncoding::<1, true>(), TallPacking { flip_y: true }, &chunk, ) @@ -1035,7 +1059,7 @@ fn main() { let quadpnghalf_pre = Instant::now(); let quadpnghalf = image_terrain_chonk( - &QuadPngEncoding::<2>(), + &QuadPngEncoding::<2, true>(), TallPacking { flip_y: true }, &chunk, ) @@ -1044,30 +1068,55 @@ fn main() { let quadpngquarttall_pre = Instant::now(); let quadpngquarttall = image_terrain_chonk( - &QuadPngEncoding::<4>(), + &QuadPngEncoding::<4, false>(), TallPacking { flip_y: true }, &chunk, ) .unwrap(); let quadpngquarttall_post = Instant::now(); + let quadpngquarttallhash_pre = Instant::now(); + let quadpngquarttallhash = image_terrain_chonk( + &QuadPngEncoding::<4, true>(), + TallPacking { flip_y: true }, + &chunk, + ) + .unwrap(); + let quadpngquarttallhash_post = Instant::now(); let quadpngquartwide_pre = Instant::now(); - let quadpngquartwide = - image_terrain_chonk(&QuadPngEncoding::<4>(), WidePacking::(), &chunk) - .unwrap(); + let quadpngquartwide = image_terrain_chonk( + &QuadPngEncoding::<4, true>(), + WidePacking::(), + &chunk, + ) + .unwrap(); let quadpngquartwide_post = Instant::now(); let tripngaverage_pre = Instant::now(); - let tripngaverage = - image_terrain_chonk(&TriPngEncoding::(), WidePacking::(), &chunk) - .unwrap(); + let tripngaverage = image_terrain_chonk( + &TriPngEncoding::(), + WidePacking::(), + &chunk, + ) + .unwrap(); let tripngaverage_post = Instant::now(); - let tripngconst_pre = Instant::now(); - let tripngconst = - image_terrain_chonk(&TriPngEncoding::(), WidePacking::(), &chunk) - .unwrap(); - let tripngconst_post = Instant::now(); + let tripngconstbump_pre = Instant::now(); + let tripngconstbump = image_terrain_chonk( + &TriPngEncoding::(), + WidePacking::(), + &chunk, + ) + .unwrap(); + let tripngconstbump_post = Instant::now(); + let tripngconsthash_pre = Instant::now(); + let tripngconsthash = image_terrain_chonk( + &TriPngEncoding::(), + WidePacking::(), + &chunk, + ) + .unwrap(); + let tripngconsthash_post = Instant::now(); let palette_kdtree_pre = Instant::now(); let palette_kdtree = image_terrain_chonk( @@ -1092,9 +1141,11 @@ fn main() { ("quadpngfull", quadpngfull.data.len() as f32 / n as f32), ("quadpnghalf", quadpnghalf.data.len() as f32 / n as f32), ("quadpngquarttall", quadpngquarttall.data.len() as f32 / n as f32), + ("quadpngquarttallhash", quadpngquarttallhash.data.len() as f32 / n as f32), ("quadpngquartwide", quadpngquartwide.data.len() as f32 / n as f32), ("tripngaverage", tripngaverage.data.len() as f32 / n as f32), - ("tripngconst", tripngconst.data.len() as f32 / n as f32), + ("tripngconstbump", tripngconstbump.data.len() as f32 / n as f32), + ("tripngconsthash", tripngconsthash.data.len() as f32 / n as f32), ("palette_kdtree", palette_kdtree.data.len() as f32 / n as f32), ("palette_rtree", palette_rtree.data.len() as f32 / n as f32), ]); @@ -1114,9 +1165,11 @@ fn main() { ("quadpngfull", (quadpngfull_post - quadpngfull_pre).subsec_nanos()), ("quadpnghalf", (quadpnghalf_post - quadpnghalf_pre).subsec_nanos()), ("quadpngquarttall", (quadpngquarttall_post - quadpngquarttall_pre).subsec_nanos()), + ("quadpngquarttallhash", (quadpngquarttallhash_post - quadpngquarttallhash_pre).subsec_nanos()), ("quadpngquartwide", (quadpngquartwide_post - quadpngquartwide_pre).subsec_nanos()), ("tripngaverage", (tripngaverage_post - tripngaverage_pre).subsec_nanos()), - ("tripngconst", (tripngconst_post - tripngconst_pre).subsec_nanos()), + ("tripngconstbump", (tripngconstbump_post - tripngconstbump_pre).subsec_nanos()), + ("tripngconsthash", (tripngconsthash_post - tripngconsthash_pre).subsec_nanos()), ("palette_kdtree", (palette_kdtree_post - palette_kdtree_pre).subsec_nanos()), ("palette_rtree", (palette_rtree_post - palette_rtree_pre).subsec_nanos()), ]); @@ -1156,7 +1209,7 @@ fn main() { .entry(chunk.get_max_z() - chunk.get_min_z()) .or_insert((0, 0.0)); bucket.0 += 1; - bucket.1 += (tripngconst_post - tripngconst_pre).subsec_nanos() as f32; + bucket.1 += (tripngconstbump_post - tripngconstbump_pre).subsec_nanos() as f32; } if true { let bucket = z_buckets