Make terrain compression a checkbox instead of a bandwidth (throughput?) heuristic.

This commit is contained in:
Avi Weinstock 2021-05-01 14:28:20 -04:00
parent f0824af80a
commit be39054767
17 changed files with 185 additions and 80 deletions

View File

@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Custom map markers can be placed now
- Fundamentals/prototype for wiring system
- Mountain peak and lake markers on the map
- There's now a checkbox in the graphics tab to opt-in to receiving lossily-compressed terrain colors.
### Changed

View File

@ -77,6 +77,7 @@
"hud.settings.fullscreen_mode.exclusive": "Exclusive",
"hud.settings.fullscreen_mode.borderless": "Borderless",
"hud.settings.particles": "Particles",
"hud.settings.lossy_terrain_compression": "Lossy terrain compression",
"hud.settings.resolution": "Resolution",
"hud.settings.bit_depth": "Bit Depth",
"hud.settings.refresh_rate": "Refresh Rate",

View File

@ -800,7 +800,10 @@ impl Client {
| ClientGeneral::RefundSkill(_)
| ClientGeneral::RequestSiteInfo(_)
| ClientGeneral::UnlockSkillGroup(_)
| ClientGeneral::RequestPlayerPhysics { .. } => &mut self.in_game_stream,
| ClientGeneral::RequestPlayerPhysics { .. }
| ClientGeneral::RequestLossyTerrainCompression { .. } => {
&mut self.in_game_stream
},
//Only in game, terrain
ClientGeneral::TerrainChunkRequest { .. } => &mut self.terrain_stream,
//Always possible
@ -820,6 +823,12 @@ impl Client {
})
}
pub fn request_lossy_terrain_compression(&mut self, lossy_terrain_compression: bool) {
self.send_msg(ClientGeneral::RequestLossyTerrainCompression {
lossy_terrain_compression,
})
}
fn send_msg<S>(&mut self, msg: S)
where
S: Into<ClientMsg>,

View File

@ -84,6 +84,9 @@ pub enum ClientGeneral {
RequestPlayerPhysics {
server_authoritative: bool,
},
RequestLossyTerrainCompression {
lossy_terrain_compression: bool,
},
}
impl ClientMsg {
@ -121,7 +124,8 @@ impl ClientMsg {
| ClientGeneral::RefundSkill(_)
| ClientGeneral::RequestSiteInfo(_)
| ClientGeneral::UnlockSkillGroup(_)
| ClientGeneral::RequestPlayerPhysics { .. } => {
| ClientGeneral::RequestPlayerPhysics { .. }
| ClientGeneral::RequestLossyTerrainCompression { .. } => {
c_type == ClientType::Game && presence.is_some()
},
//Always possible

View File

@ -15,7 +15,7 @@ pub use self::{
server::{
CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate,
RegisterError, SerializedTerrainChunk, ServerGeneral, ServerInfo, ServerInit, ServerMsg,
ServerRegisterAnswer, TERRAIN_LOW_BANDWIDTH,
ServerRegisterAnswer,
},
world_msg::WorldMapMsg,
};

View File

@ -73,13 +73,9 @@ pub enum SerializedTerrainChunk {
TriPng(WireChonk<TriPngEncoding<false>, WidePacking<true>, TerrainChunkMeta, TerrainChunkSize>),
}
/// If someone has less than this number of bytes per second of bandwidth, spend
/// more CPU generating a smaller encoding of terrain data.
pub const TERRAIN_LOW_BANDWIDTH: f32 = 500_000.0;
impl SerializedTerrainChunk {
pub fn via_heuristic(chunk: &TerrainChunk, low_bandwidth: bool) -> Self {
if low_bandwidth && (chunk.get_max_z() - chunk.get_min_z() <= 128) {
pub fn via_heuristic(chunk: &TerrainChunk, lossy_compression: bool) -> Self {
if lossy_compression && (chunk.get_max_z() - chunk.get_min_z() <= 128) {
Self::quadpng(chunk)
} else {
Self::deflate(chunk)

View File

@ -34,8 +34,8 @@ pub struct NetworkRequestMetrics {
pub chunks_request_dropped: IntCounter,
pub chunks_served_from_memory: IntCounter,
pub chunks_generation_triggered: IntCounter,
pub chunks_served_lo_bandwidth: IntCounter,
pub chunks_served_hi_bandwidth: IntCounter,
pub chunks_served_lossy: IntCounter,
pub chunks_served_lossless: IntCounter,
}
pub struct ChunkGenMetrics {
@ -189,29 +189,27 @@ impl NetworkRequestMetrics {
"chunks_generation_triggered",
"number of all chunks that were requested and needs to be generated",
))?;
let chunks_served_lo_bandwidth = IntCounter::with_opts(Opts::new(
"chunks_served_lo_bandwidth",
"number of chunks that were sent with compression setting by the low bandwidth \
heuristic",
let chunks_served_lossy = IntCounter::with_opts(Opts::new(
"chunks_served_lossy",
"number of chunks that were sent with lossy compression requested",
))?;
let chunks_served_hi_bandwidth = IntCounter::with_opts(Opts::new(
"chunks_served_hi_bandwidth",
"number of chunks that were sent with compression setting by the high bandwidth \
heuristic",
let chunks_served_lossless = IntCounter::with_opts(Opts::new(
"chunks_served_lossless",
"number of chunks that were sent with lossless compression requested",
))?;
registry.register(Box::new(chunks_request_dropped.clone()))?;
registry.register(Box::new(chunks_served_from_memory.clone()))?;
registry.register(Box::new(chunks_generation_triggered.clone()))?;
registry.register(Box::new(chunks_served_lo_bandwidth.clone()))?;
registry.register(Box::new(chunks_served_hi_bandwidth.clone()))?;
registry.register(Box::new(chunks_served_lossy.clone()))?;
registry.register(Box::new(chunks_served_lossless.clone()))?;
Ok(Self {
chunks_request_dropped,
chunks_served_from_memory,
chunks_generation_triggered,
chunks_served_lo_bandwidth,
chunks_served_hi_bandwidth,
chunks_served_lossy,
chunks_served_lossless,
})
}
}

View File

@ -9,6 +9,7 @@ use vek::*;
pub struct Presence {
pub view_distance: u32,
pub kind: PresenceKind,
pub lossy_terrain_compression: bool,
}
impl Presence {
@ -16,6 +17,7 @@ impl Presence {
Self {
view_distance,
kind,
lossy_terrain_compression: false,
}
}
}

View File

@ -253,6 +253,11 @@ impl Sys {
setting.client_optin = server_authoritative;
}
},
ClientGeneral::RequestLossyTerrainCompression {
lossy_terrain_compression,
} => {
presence.lossy_terrain_compression = lossy_terrain_compression;
},
_ => tracing::error!("not a client_in_game msg"),
}
Ok(())

View File

@ -6,9 +6,7 @@ use common::{
vol::RectVolSize,
};
use common_ecs::{Job, Origin, ParMode, Phase, System};
use common_net::msg::{
ClientGeneral, SerializedTerrainChunk, ServerGeneral, TERRAIN_LOW_BANDWIDTH,
};
use common_net::msg::{ClientGeneral, SerializedTerrainChunk, ServerGeneral};
use rayon::iter::ParallelIterator;
use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage};
use tracing::{debug, trace};
@ -79,21 +77,17 @@ impl<'a> System<'a> for Sys {
match terrain.get_key_arc(key) {
Some(chunk) => {
network_metrics.chunks_served_from_memory.inc();
if let Some(participant) = &client.participant {
let low_bandwidth =
participant.bandwidth() < TERRAIN_LOW_BANDWIDTH;
client.send(ServerGeneral::TerrainChunkUpdate {
key,
chunk: Ok(SerializedTerrainChunk::via_heuristic(
&chunk,
low_bandwidth,
presence.lossy_terrain_compression,
)),
})?;
if low_bandwidth {
network_metrics.chunks_served_lo_bandwidth.inc();
if presence.lossy_terrain_compression {
network_metrics.chunks_served_lossy.inc();
} else {
network_metrics.chunks_served_hi_bandwidth.inc();
}
network_metrics.chunks_served_lossless.inc();
}
},
None => {

View File

@ -14,7 +14,7 @@ use common::{
LoadoutBuilder, SkillSetBuilder,
};
use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{SerializedTerrainChunk, ServerGeneral, TERRAIN_LOW_BANDWIDTH};
use common_net::msg::{SerializedTerrainChunk, ServerGeneral};
use common_state::TerrainChanges;
use comp::Behavior;
use specs::{Join, Read, ReadExpect, ReadStorage, Write, WriteExpect};
@ -43,12 +43,11 @@ impl LazyTerrainMessage {
&mut self,
network_metrics: &NetworkRequestMetrics,
client: &Client,
presence: &Presence,
chunk_key: &vek::Vec2<i32>,
generate_chunk: F,
) -> Result<(), A> {
if let Some(participant) = &client.participant {
let low_bandwidth = participant.bandwidth() < TERRAIN_LOW_BANDWIDTH;
let lazy_msg = if low_bandwidth {
let lazy_msg = if presence.lossy_terrain_compression {
&mut self.lazy_msg_lo
} else {
&mut self.lazy_msg_hi
@ -57,20 +56,22 @@ impl LazyTerrainMessage {
*lazy_msg = Some(client.prepare(ServerGeneral::TerrainChunkUpdate {
key: *chunk_key,
chunk: Ok(match generate_chunk() {
Ok(chunk) => SerializedTerrainChunk::via_heuristic(&chunk, low_bandwidth),
Ok(chunk) => SerializedTerrainChunk::via_heuristic(
&chunk,
presence.lossy_terrain_compression,
),
Err(e) => return Err(e),
}),
}));
}
lazy_msg.as_ref().map(|ref msg| {
let _ = client.send_prepared(&msg);
if low_bandwidth {
network_metrics.chunks_served_lo_bandwidth.inc();
if presence.lossy_terrain_compression {
network_metrics.chunks_served_lossy.inc();
} else {
network_metrics.chunks_served_hi_bandwidth.inc();
network_metrics.chunks_served_lossless.inc();
}
});
}
Ok(())
}
}
@ -293,9 +294,13 @@ impl<'a> System<'a> for Sys {
if adjusted_dist_sqr <= presence.view_distance.pow(2) {
lazy_msg
.prepare_and_send::<!, _>(&network_metrics, &client, &key, || {
Ok(&*chunk)
})
.prepare_and_send::<!, _>(
&network_metrics,
&client,
&presence,
&key,
|| Ok(&*chunk),
)
.into_ok();
}
});

View File

@ -35,11 +35,13 @@ impl<'a> System<'a> for Sys {
for (presence, pos, client) in (&presences, &positions, &clients).join() {
if super::terrain::chunk_in_vd(pos.0, *chunk_key, &terrain, presence.view_distance)
{
if let Err(()) =
lazy_msg.prepare_and_send(&network_metrics, &client, chunk_key, || {
terrain.get_key(*chunk_key).ok_or(())
})
{
if let Err(()) = lazy_msg.prepare_and_send(
&network_metrics,
&client,
&presence,
chunk_key,
|| terrain.get_key(*chunk_key).ok_or(()),
) {
break 'chunk;
}
}

View File

@ -82,6 +82,8 @@ widget_ids! {
//
particles_button,
particles_label,
lossy_terrain_compression_button,
lossy_terrain_compression_label,
//
fullscreen_button,
fullscreen_label,
@ -874,6 +876,42 @@ impl<'a> Widget for Video<'a> {
events.push(ToggleParticlesEnabled(particles_enabled));
}
// Lossy terrain compression
Text::new(
&self
.localized_strings
.get("hud.settings.lossy_terrain_compression"),
)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.right_from(state.ids.particles_label, 64.0)
.color(TEXT_COLOR)
.set(state.ids.lossy_terrain_compression_label, ui);
let lossy_terrain_compression = ToggleButton::new(
self.global_state
.settings
.graphics
.lossy_terrain_compression,
self.imgs.checkbox,
self.imgs.checkbox_checked,
)
.w_h(18.0, 18.0)
.right_from(state.ids.lossy_terrain_compression_label, 10.0)
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
.set(state.ids.lossy_terrain_compression_button, ui);
if self
.global_state
.settings
.graphics
.lossy_terrain_compression
!= lossy_terrain_compression
{
events.push(ToggleLossyTerrainCompression(lossy_terrain_compression));
}
// Resolution
let resolutions: Vec<[u16; 2]> = state
.video_modes

View File

@ -88,9 +88,13 @@ impl SessionState {
scene
.camera_mut()
.set_fov_deg(global_state.settings.graphics.fov);
client
.borrow_mut()
.request_player_physics(global_state.settings.gameplay.player_physics_behavior);
{
let mut client = client.borrow_mut();
client.request_player_physics(global_state.settings.gameplay.player_physics_behavior);
client.request_lossy_terrain_compression(
global_state.settings.graphics.lossy_terrain_compression,
);
}
let hud = Hud::new(global_state, &client.borrow());
let walk_forward_dir = scene.camera().forward_xy();
let walk_right_dir = scene.camera().right_xy();

View File

@ -71,6 +71,7 @@ pub enum Graphics {
ChangeFullscreenMode(FullScreenSettings),
ToggleParticlesEnabled(bool),
ToggleLossyTerrainCompression(bool),
AdjustWindowSize([u16; 2]),
ResetGraphicsSettings,
@ -329,6 +330,13 @@ impl SettingsChange {
Graphics::ToggleParticlesEnabled(particles_enabled) => {
settings.graphics.particles_enabled = particles_enabled;
},
Graphics::ToggleLossyTerrainCompression(lossy_terrain_compression) => {
settings.graphics.lossy_terrain_compression = lossy_terrain_compression;
session_state
.client
.borrow_mut()
.request_lossy_terrain_compression(lossy_terrain_compression);
},
Graphics::AdjustWindowSize(new_size) => {
global_state.window.set_size(new_size.into());
settings.graphics.window_size = new_size;

View File

@ -32,6 +32,7 @@ pub struct GraphicsSettings {
pub view_distance: u32,
pub sprite_render_distance: u32,
pub particles_enabled: bool,
pub lossy_terrain_compression: bool,
pub figure_lod_render_distance: u32,
pub max_fps: Fps,
pub fov: u16,
@ -50,6 +51,7 @@ impl Default for GraphicsSettings {
view_distance: 10,
sprite_render_distance: 100,
particles_enabled: true,
lossy_terrain_compression: false,
figure_lod_render_distance: 300,
max_fps: Fps::Max(60),
fov: 70,

View File

@ -563,7 +563,7 @@ fn main() {
.unwrap(),
));
const SKIP_DEFLATE_2_5: bool = true;
const SKIP_DEFLATE_2_5: bool = false;
const SKIP_DYNA: bool = true;
const SKIP_IMAGECHONK: bool = true;
const SKIP_MIXED: bool = true;
@ -701,6 +701,42 @@ fn main() {
("deflate4chonk", (deflate4chonk_post - deflate4chonk_pre).subsec_nanos()),
("deflate5chonk", (deflate5chonk_post - deflate5chonk_pre).subsec_nanos()),
]);
{
let bucket = z_buckets
.entry("deflate2")
.or_default()
.entry(chunk.get_max_z() - chunk.get_min_z())
.or_insert((0, 0.0));
bucket.0 += 1;
bucket.1 += (deflate2chonk_post - deflate2chonk_pre).subsec_nanos() as f32;
}
{
let bucket = z_buckets
.entry("deflate3")
.or_default()
.entry(chunk.get_max_z() - chunk.get_min_z())
.or_insert((0, 0.0));
bucket.0 += 1;
bucket.1 += (deflate3chonk_post - deflate3chonk_pre).subsec_nanos() as f32;
}
{
let bucket = z_buckets
.entry("deflate4")
.or_default()
.entry(chunk.get_max_z() - chunk.get_min_z())
.or_insert((0, 0.0));
bucket.0 += 1;
bucket.1 += (deflate4chonk_post - deflate4chonk_pre).subsec_nanos() as f32;
}
{
let bucket = z_buckets
.entry("deflate5")
.or_default()
.entry(chunk.get_max_z() - chunk.get_min_z())
.or_insert((0, 0.0));
bucket.0 += 1;
bucket.1 += (deflate5chonk_post - deflate5chonk_pre).subsec_nanos() as f32;
}
}
if !SKIP_DYNA {
@ -886,7 +922,7 @@ fn main() {
("tripngaverage", (tripngaverage_post - tripngaverage_pre).subsec_nanos()),
("tripngconst", (tripngconst_post - tripngconst_pre).subsec_nanos()),
]);
{
if false {
let bucket = z_buckets
.entry("quadpngquarttall")
.or_default()
@ -906,7 +942,7 @@ fn main() {
bucket.1 +=
(quadpngquartwide_post - quadpngquartwide_pre).subsec_nanos() as f32;
}
if true {
if false {
let bucket = z_buckets
.entry("tripngaverage")
.or_default()