Use a threadpool to speed up minimap chunk rendering. Also fix ceiling height calculations and color water blue.

This commit is contained in:
Avi Weinstock 2021-05-13 18:24:11 -04:00
parent 59e93d23c1
commit 1214715c21
11 changed files with 192 additions and 69 deletions

1
Cargo.lock generated
View File

@ -5825,6 +5825,7 @@ dependencies = [
"cpal",
"criterion",
"crossbeam",
"crossbeam-channel",
"directories-next",
"dispatch 0.1.4",
"dot_vox",

View File

@ -106,6 +106,7 @@ num_cpus = "1.0"
# vec_map = { version = "0.8.2" }
inline_tweak = "1.0.2"
itertools = "0.10.0"
crossbeam-channel = "0.5"
# Tracy
tracing = "0.1"

View File

@ -11,6 +11,7 @@ pub fn init(world: &mut World) {
{
let pool = world.read_resource::<SlowJobPool>();
pool.configure("IMAGE_PROCESSING", |_| 1);
pool.configure("FIGURE_MESHING", |n| n / 2);
pool.configure("TERRAIN_MESHING", |n| n / 2);
}

View File

@ -6,7 +6,7 @@ use super::{
use crate::{
hud::{Graphic, Ui},
session::settings_change::{Interface as InterfaceChange, Interface::*},
ui::{fonts::Fonts, img_ids},
ui::{fonts::Fonts, img_ids, KeyedJobs},
GlobalState,
};
use client::{self, Client};
@ -14,7 +14,8 @@ use common::{
comp,
comp::group::Role,
grid::Grid,
terrain::{Block, TerrainChunk, TerrainChunkSize},
slowjob::SlowJobPool,
terrain::{Block, BlockKind, TerrainChunk, TerrainChunkSize},
vol::{ReadVol, RectVolSize},
};
use common_net::msg::world_msg::SiteKind;
@ -46,6 +47,9 @@ pub struct VoxelMinimap {
image_id: img_ids::Rotations,
last_pos: Vec3<i32>,
last_ceiling: i32,
/// Maximum z of the top of the tallest loaded chunk (for ceiling pruning)
max_chunk_z: i32,
keyed_jobs: KeyedJobs<Vec2<i32>, MinimapColumn>,
}
const VOXEL_MINIMAP_SIDELENGTH: u32 = 512;
@ -66,13 +70,22 @@ impl VoxelMinimap {
composited,
last_pos: Vec3::zero(),
last_ceiling: 0,
max_chunk_z: 0,
keyed_jobs: KeyedJobs::new(),
}
}
fn block_color(block: &Block) -> Option<Vec4<u8>> {
block
.get_color()
.map(|rgb| Vec4::new(rgb.r, rgb.g, rgb.b, 192))
.map(|rgb| Vec4::new(rgb.r, rgb.g, rgb.b, 255))
.or_else(|| {
if matches!(block.kind(), BlockKind::Water) {
Some(Vec4::new(107, 165, 220, 255))
} else {
None
}
})
}
/// Each layer is a slice of the terrain near that z-level
@ -86,7 +99,7 @@ impl VoxelMinimap {
.get(Vec3::new(v.x, v.y, dz as i32 + z + zoff))
.ok()
.and_then(Self::block_color)
.unwrap_or(Vec4::zero());
.unwrap_or_else(Vec4::zero);
rgba += color.as_() * weights[dz as usize] as f32;
}
let rgba: Vec4<u8> = (rgba / weights.iter().map(|x| *x as f32).sum::<f32>()).as_();
@ -111,9 +124,10 @@ impl VoxelMinimap {
.and_then(Self::block_color)
{
if seen_air > 0 {
rgba = Some(color.map(|j| {
/*rgba = Some(color.map(|j| {
(j as u32).saturating_sub(if seen_air > 2 { 4 } else { 0 }) as u8
}));
}));*/
rgba = Some(color);
break;
}
seen_solids += 1;
@ -137,40 +151,47 @@ impl VoxelMinimap {
}
pub fn maintain(&mut self, client: &Client, ui: &mut Ui) {
let pool = client.state().ecs().read_resource::<SlowJobPool>();
let terrain = client.state().terrain();
let mut new_chunk = false;
for (key, chunk) in terrain.iter() {
if !self.chunk_minimaps.contains_key(&key) {
new_chunk = true;
let mut layers = Vec::new();
const MODE_OVERHEAD: bool = true;
if MODE_OVERHEAD {
Self::composite_layer_overhead(chunk, &mut layers);
} else {
Self::composite_layer_slice(chunk, &mut layers);
let arc_chunk = Arc::clone(chunk);
if let Some((_, column)) = self.keyed_jobs.spawn(Some(&pool), key, move |_| {
let mut layers = Vec::new();
const MODE_OVERHEAD: bool = true;
if MODE_OVERHEAD {
Self::composite_layer_overhead(&arc_chunk, &mut layers);
} else {
Self::composite_layer_slice(&arc_chunk, &mut layers);
}
let above = arc_chunk
.get(Vec3::new(0, 0, arc_chunk.get_max_z() + 1))
.ok()
.cloned()
.unwrap_or_else(Block::empty);
let below = arc_chunk
.get(Vec3::new(0, 0, arc_chunk.get_min_z() - 1))
.ok()
.cloned()
.unwrap_or_else(Block::empty);
MinimapColumn {
zlo: arc_chunk.get_min_z(),
layers,
above: (
Self::block_color(&above).unwrap_or_else(Vec4::zero),
above.is_filled(),
),
below: (
Self::block_color(&below).unwrap_or_else(Vec4::zero),
below.is_filled(),
),
}
}) {
self.chunk_minimaps.insert(key, column);
new_chunk = true;
self.max_chunk_z = self.max_chunk_z.max(chunk.get_max_z());
}
let above = chunk
.get(Vec3::new(0, 0, chunk.get_max_z() + 1))
.ok()
.cloned()
.unwrap_or_else(Block::empty);
let below = chunk
.get(Vec3::new(0, 0, chunk.get_min_z() - 1))
.ok()
.cloned()
.unwrap_or_else(Block::empty);
self.chunk_minimaps.insert(key, MinimapColumn {
zlo: chunk.get_min_z(),
layers,
above: (
Self::block_color(&above).unwrap_or_else(Vec4::zero),
above.is_filled(),
),
below: (
Self::block_color(&below).unwrap_or_else(Vec4::zero),
below.is_filled(),
),
});
}
}
let player = client.entity();
@ -208,7 +229,7 @@ impl VoxelMinimap {
if above.1 {
1
} else {
layers.len() as i32 - pos.z as i32 + zlo
self.max_chunk_z - pos.z as i32
}
})
},
@ -220,7 +241,6 @@ impl VoxelMinimap {
|| self.last_ceiling != ceiling_offset
|| new_chunk
{
tracing::info!("{:?} {:?}", pos, ceiling_offset);
self.last_pos = cpos.with_z(pos.z as i32);
self.last_ceiling = ceiling_offset;
for y in 0..VOXEL_MINIMAP_SIDELENGTH {
@ -241,7 +261,7 @@ impl VoxelMinimap {
layers
.get(
((pos.z as i32 - zlo + ceiling_offset) as usize)
.min(layers.len() - 1),
.min(layers.len().saturating_sub(1)),
)
.and_then(|grid| grid.get(cmod).map(|c| c.0.as_()))
.or_else(|| {
@ -253,7 +273,7 @@ impl VoxelMinimap {
})
},
)
.unwrap_or(Vec4::zero());
.unwrap_or_else(Vec4::zero);
self.composited.put_pixel(
x,
VOXEL_MINIMAP_SIDELENGTH - y - 1,

View File

@ -81,6 +81,7 @@ use common::{
},
consts::MAX_PICKUP_RANGE,
outcome::Outcome,
slowjob::SlowJobPool,
terrain::{SpriteKind, TerrainChunk},
trade::{ReducedInventory, TradeAction},
uid::Uid,
@ -3603,9 +3604,14 @@ impl Hud {
// Check if item images need to be reloaded
self.item_imgs.reload_if_changed(&mut self.ui);
// TODO: using a thread pool in the obvious way for speeding up map zoom results
// in flickering artifacts, figure out a better way to make use of the
// thread pool
let _pool = client.state().ecs().read_resource::<SlowJobPool>();
self.ui.maintain(
&mut global_state.window.renderer_mut(),
None,
//Some(&pool),
Some(proj_mat * view_mat * Mat4::translation_3d(-focus_off)),
);

View File

@ -1563,6 +1563,7 @@ impl CharSelectionUi {
self.controls
.view(&global_state.settings, &client, &self.error, &i18n),
global_state.window.renderer_mut(),
None,
global_state.clipboard.as_ref(),
);

View File

@ -571,6 +571,7 @@ impl<'a> MainMenuUi {
let (messages, _) = self.ui.maintain(
self.controls.view(&global_state.settings, dt.as_secs_f32()),
global_state.window.renderer_mut(),
None,
global_state.clipboard.as_ref(),
);

View File

@ -3,13 +3,16 @@ mod renderer;
pub use renderer::{SampleStrat, Transform};
use crate::render::{RenderError, Renderer, Texture};
use common::figure::Segment;
use crate::{
render::{RenderError, Renderer, Texture},
ui::KeyedJobs,
};
use common::{figure::Segment, slowjob::SlowJobPool};
use guillotiere::{size2, SimpleAtlasAllocator};
use hashbrown::{hash_map::Entry, HashMap};
use image::{DynamicImage, RgbaImage};
use pixel_art::resize_pixel_art;
use std::sync::Arc;
use std::{hash::Hash, sync::Arc};
use tracing::warn;
use vek::*;
@ -142,6 +145,9 @@ pub struct GraphicCache {
textures: Vec<Texture>,
// Stores the location of graphics rendered at a particular resolution and cached on the cpu
cache_map: HashMap<Parameters, CachedDetails>,
#[allow(clippy::type_complexity)]
keyed_jobs: KeyedJobs<(Id, Vec2<u16>), Option<(RgbaImage, Option<Rgba<f32>>)>>,
}
impl GraphicCache {
pub fn new(renderer: &mut Renderer) -> Self {
@ -153,6 +159,7 @@ impl GraphicCache {
atlases: vec![(atlas, 0)],
textures: vec![texture],
cache_map: HashMap::default(),
keyed_jobs: KeyedJobs::new(),
}
}
@ -222,6 +229,7 @@ impl GraphicCache {
pub fn cache_res(
&mut self,
renderer: &mut Renderer,
pool: Option<&SlowJobPool>,
graphic_id: Id,
dims: Vec2<u16>,
source: Aabr<f64>,
@ -277,7 +285,8 @@ impl GraphicCache {
// graphic
if !valid {
// Create image
let (image, border) = draw_graphic(graphic_map, graphic_id, dims)?;
let (image, border) =
draw_graphic(graphic_map, graphic_id, dims, &mut self.keyed_jobs, pool)?;
// If the cache location is invalid, we know the underlying texture is mutable,
// so we should be able to replace the graphic. However, we still want to make
// sure that we are not reusing textures for images that specify a border
@ -292,8 +301,10 @@ impl GraphicCache {
Entry::Vacant(details) => details,
};
// Construct image
let (image, border_color) = draw_graphic(graphic_map, graphic_id, dims)?;
// Construct image in a threadpool
let (image, border_color) =
draw_graphic(graphic_map, graphic_id, dims, &mut self.keyed_jobs, pool)?;
// Upload
let atlas_size = atlas_size(renderer);
@ -382,23 +393,40 @@ impl GraphicCache {
}
// Draw a graphic at the specified dimensions
#[allow(clippy::type_complexity)]
fn draw_graphic(
graphic_map: &GraphicMap,
graphic_id: Id,
dims: Vec2<u16>,
keyed_jobs: &mut KeyedJobs<(Id, Vec2<u16>), Option<(RgbaImage, Option<Rgba<f32>>)>>,
pool: Option<&SlowJobPool>,
) -> Option<(RgbaImage, Option<Rgba<f32>>)> {
match graphic_map.get(&graphic_id) {
Some(Graphic::Blank) => None,
// Render image at requested resolution
// TODO: Use source aabr.
Some(&Graphic::Image(ref image, border_color)) => Some((
resize_pixel_art(&image.to_rgba8(), u32::from(dims.x), u32::from(dims.y)),
border_color,
)),
Some(Graphic::Voxel(ref segment, trans, sample_strat)) => Some((
renderer::draw_vox(&segment, dims, trans.clone(), *sample_strat),
None,
)),
Some(inner) => {
let inner = inner.clone();
keyed_jobs
.spawn(pool, (graphic_id, dims), move |_| {
match inner {
// Render image at requested resolution
// TODO: Use source aabr.
Graphic::Image(ref image, border_color) => Some((
resize_pixel_art(
&image.to_rgba8(),
u32::from(dims.x),
u32::from(dims.y),
),
border_color,
)),
Graphic::Voxel(ref segment, trans, sample_strat) => Some((
renderer::draw_vox(&segment, dims, trans, sample_strat),
None,
)),
_ => None,
}
})
.and_then(|(_, v)| v)
},
None => {
warn!(
?graphic_id,

View File

@ -15,6 +15,7 @@ use super::{
scale::{Scale, ScaleMode},
};
use crate::{render::Renderer, window::Window, Error};
use common::slowjob::SlowJobPool;
use common_base::span;
use iced::{mouse, Cache, Size, UserInterface};
use iced_winit::Clipboard;
@ -142,6 +143,7 @@ impl IcedUi {
&mut self,
root: E,
renderer: &mut Renderer,
pool: Option<&SlowJobPool>,
clipboard: Option<&Clipboard>,
) -> (Vec<M>, mouse::Interaction) {
span!(_guard, "maintain", "IcedUi::maintain");
@ -207,7 +209,7 @@ impl IcedUi {
self.cache = Some(user_interface.into_cache());
self.renderer.draw(primitive, renderer);
self.renderer.draw(primitive, renderer, pool);
(messages, mouse_interaction)
}

View File

@ -20,7 +20,7 @@ use crate::{
},
Error,
};
use common::util::srgba_to_linear;
use common::{slowjob::SlowJobPool, util::srgba_to_linear};
use common_base::span;
use std::{convert::TryInto, ops::Range};
use vek::*;
@ -184,7 +184,12 @@ impl IcedRenderer {
self.cache.resize_glyph_cache(renderer).unwrap();
}
pub fn draw(&mut self, primitive: Primitive, renderer: &mut Renderer) {
pub fn draw(
&mut self,
primitive: Primitive,
renderer: &mut Renderer,
pool: Option<&SlowJobPool>,
) {
span!(_guard, "draw", "IcedRenderer::draw");
// Re-use memory
self.draw_commands.clear();
@ -194,7 +199,7 @@ impl IcedRenderer {
self.current_state = State::Plain;
self.start = 0;
self.draw_primitive(primitive, Vec2::zero(), 1.0, renderer);
self.draw_primitive(primitive, Vec2::zero(), 1.0, renderer, pool);
// Enter the final command.
self.draw_commands.push(match self.current_state {
@ -426,12 +431,13 @@ impl IcedRenderer {
offset: Vec2<u32>,
alpha: f32,
renderer: &mut Renderer,
pool: Option<&SlowJobPool>,
) {
match primitive {
Primitive::Group { primitives } => {
primitives
.into_iter()
.for_each(|p| self.draw_primitive(p, offset, alpha, renderer));
.for_each(|p| self.draw_primitive(p, offset, alpha, renderer, pool));
},
Primitive::Image {
handle,
@ -536,6 +542,7 @@ impl IcedRenderer {
// Cache graphic at particular resolution.
let (uv_aabr, tex_id) = match graphic_cache.cache_res(
renderer,
pool,
graphic_id,
resolution,
// TODO: take f32 here
@ -730,7 +737,7 @@ impl IcedRenderer {
// TODO: cull primitives outside the current scissor
// Renderer child
self.draw_primitive(*content, offset + clip_offset, alpha, renderer);
self.draw_primitive(*content, offset + clip_offset, alpha, renderer, pool);
// Reset scissor
self.draw_commands.push(match self.current_state {
@ -745,7 +752,7 @@ impl IcedRenderer {
.push(DrawCommand::Scissor(self.window_scissor));
},
Primitive::Opacity { alpha: a, content } => {
self.draw_primitive(*content, offset, alpha * a, renderer);
self.draw_primitive(*content, offset, alpha * a, renderer, pool);
},
Primitive::Nothing => {},
}

View File

@ -34,7 +34,7 @@ use crate::{
#[rustfmt::skip]
use ::image::GenericImageView;
use cache::Cache;
use common::util::srgba_to_linear;
use common::{slowjob::SlowJobPool, util::srgba_to_linear};
use common_base::span;
use conrod_core::{
event::Input,
@ -48,8 +48,8 @@ use conrod_core::{
};
use core::{convert::TryInto, f32, f64, ops::Range};
use graphic::TexId;
use hashbrown::hash_map::Entry;
use std::time::Duration;
use hashbrown::{hash_map::Entry, HashMap};
use std::{hash::Hash, time::Duration};
use tracing::{error, warn};
use vek::*;
@ -306,7 +306,12 @@ impl Ui {
pub fn widget_input(&self, id: widget::Id) -> Widget { self.ui.widget_input(id) }
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
pub fn maintain(&mut self, renderer: &mut Renderer, view_projection_mat: Option<Mat4<f32>>) {
pub fn maintain(
&mut self,
renderer: &mut Renderer,
pool: Option<&SlowJobPool>,
view_projection_mat: Option<Mat4<f32>>,
) {
span!(_guard, "maintain", "Ui::maintain");
// Maintain tooltip manager
self.tooltip_manager
@ -353,16 +358,17 @@ impl Ui {
}
let mut retry = false;
self.maintain_internal(renderer, view_projection_mat, &mut retry);
self.maintain_internal(renderer, pool, view_projection_mat, &mut retry);
if retry {
// Update the glyph cache and try again.
self.maintain_internal(renderer, view_projection_mat, &mut retry);
self.maintain_internal(renderer, pool, view_projection_mat, &mut retry);
}
}
fn maintain_internal(
&mut self,
renderer: &mut Renderer,
pool: Option<&SlowJobPool>,
view_projection_mat: Option<Mat4<f32>>,
retry: &mut bool,
) {
@ -806,6 +812,7 @@ impl Ui {
// Cache graphic at particular resolution.
let (uv_aabr, tex_id) = match graphic_cache.cache_res(
renderer,
pool,
*graphic_id,
resolution,
source_aabr,
@ -1047,3 +1054,51 @@ fn default_scissor(renderer: &Renderer) -> Aabr<u16> {
},
}
}
pub struct KeyedJobs<K, V> {
tx: crossbeam_channel::Sender<(K, V)>,
rx: crossbeam_channel::Receiver<(K, V)>,
buf: HashMap<K, V>,
}
impl<K: Hash + Eq + Send + Sync + 'static, V: Send + Sync + 'static> KeyedJobs<K, V> {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let (tx, rx) = crossbeam_channel::unbounded();
Self {
tx,
rx,
buf: HashMap::new(),
}
}
pub fn spawn(
&mut self,
pool: Option<&SlowJobPool>,
k: K,
f: impl FnOnce(&K) -> V + Send + Sync + 'static,
) -> Option<(K, V)> {
if let Some(pool) = pool {
if let Some(v) = self.buf.remove(&k) {
Some((k, v))
} else {
while let Ok((k2, v)) = self.rx.try_recv() {
if k == k2 {
return Some((k, v));
} else {
self.buf.insert(k2, v);
}
}
let tx = self.tx.clone();
pool.spawn("IMAGE_PROCESSING", move || {
let v = f(&k);
let _ = tx.send((k, v));
});
None
}
} else {
let v = f(&k);
Some((k, v))
}
}
}