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.

This commit is contained in:
Avi Weinstock 2021-04-26 23:52:10 -04:00
parent dffc7db8f5
commit cdc2eccda8
7 changed files with 79 additions and 45 deletions

View File

@ -89,9 +89,11 @@ pub struct TallPacking {
}
impl PackingFormula for TallPacking {
#[inline(always)]
fn dimensions(&self, dims: Vec3<u32>) -> (u32, u32) { (dims.x, dims.y * dims.z) }
#[allow(clippy::many_single_char_names)]
#[inline(always)]
fn index(&self, dims: Vec3<u32>, 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<const FLIP_X: bool>();
impl<const FLIP_X: bool> PackingFormula for WidePacking<FLIP_X> {
#[inline(always)]
fn dimensions(&self, dims: Vec3<u32>) -> (u32, u32) { (dims.x * dims.z, dims.y) }
#[allow(clippy::many_single_char_names)]
#[inline(always)]
fn index(&self, dims: Vec3<u32>, 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, 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<u32>, 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<const N: u32> VoxelImageEncoding for QuadPngEncoding<N> {
)
}
#[inline(always)]
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.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<const N: u32> VoxelImageEncoding for QuadPngEncoding<N> {
let png = image::codecs::png::PngEncoder::new_with_quality(
&mut buf,
CompressionType::Rle,
FilterType::Paeth,
FilterType::Sub,
);
png.encode(
&*ws.3.as_raw(),

View File

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

View File

@ -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<TerrainChunk>),
PngPngPngJpeg(WireChonk<MixedEncoding, TallPacking, TerrainChunkMeta, TerrainChunkSize>),
QuadPng(WireChonk<QuadPngEncoding<4>, TallPacking, TerrainChunkMeta, TerrainChunkSize>),
TriPng(WireChonk<TriPngEncoding, TallPacking, TerrainChunkMeta, TerrainChunkSize>),
QuadPng(WireChonk<QuadPngEncoding<4>, WidePacking<true>, TerrainChunkMeta, TerrainChunkSize>),
TriPng(WireChonk<TriPngEncoding, WidePacking<true>, 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<TerrainChunk> {
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(),
}

View File

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

View File

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

View File

@ -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,
}),
}));

View File

@ -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::<true>(), &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()