use std::{
collections::{HashMap, HashSet},
io::Cursor,
sync::Arc,
};
use image::{DynamicImage, RgbImage};
use parking_lot::Mutex;
use perovskite_core::constants::textures::FALLBACK_UNKNOWN_TEXTURE;
use texture_packer::{importer::ImageImporter, Rect, TexturePacker};
use anyhow::{Error, Result};
use crate::{
block_renderer::BlockRenderer,
cache::CacheManager,
game_state::items::ClientItemManager,
vulkan::{mini_renderer::MiniBlockRenderer, Texture2DHolder, VulkanContext},
};
use self::{egui_ui::EguiUi, hud::GameHud};
pub(crate) mod egui_ui;
pub(crate) mod hud;
pub(crate) async fn make_uis(
item_defs: Arc<ClientItemManager>,
cache_manager: &Arc<Mutex<CacheManager>>,
ctx: Arc<VulkanContext>,
block_renderer: &BlockRenderer,
) -> Result<(hud::GameHud, egui_ui::EguiUi)> {
let (texture_atlas, texture_coords) =
build_texture_atlas(&item_defs, cache_manager, ctx, block_renderer).await?;
let hud = GameHud {
texture_coords: texture_coords.clone(),
texture_atlas: texture_atlas.clone(),
item_defs: item_defs.clone(),
last_size: (0, 0),
hotbar_slot: 0,
crosshair_draw_call: None,
hotbar_draw_call: None,
hotbar_view_id: None,
fps_counter: fps_counter::FPSCounter::new(),
};
let egui_ui = EguiUi::new(texture_atlas, texture_coords, item_defs.clone());
Ok((hud, egui_ui))
}
fn pack_tex(
texture_packer: &mut TexturePacker<'_, DynamicImage, String>,
name: &str,
img: DynamicImage,
) -> Result<()> {
texture_packer
.pack_own(name.into(), img)
.map_err(|x| Error::msg(format!("Texture pack for {} failed: {:?}", name, x)))
}
async fn build_texture_atlas(
item_defs: &ClientItemManager,
cache_manager: &Arc<Mutex<CacheManager>>,
ctx: Arc<VulkanContext>,
block_renderer: &BlockRenderer,
) -> Result<(Arc<Texture2DHolder>, HashMap<String, Rect>)> {
let mut all_texture_names = HashSet::new();
let mut all_rendered_blocks = HashSet::new();
for def in item_defs.all_item_defs() {
if !def.block_apperance.is_empty() {
all_rendered_blocks.insert(def.block_apperance.clone());
} else if let Some(tex) = &def.inventory_texture {
all_texture_names.insert(tex.texture_name.clone());
}
}
let all_rendered_blocks = all_rendered_blocks.into_iter().collect::<Vec<_>>();
let all_texture_names = item_defs
.all_item_defs()
.flat_map(|x| &x.inventory_texture)
.map(|x| x.texture_name.clone())
.collect::<HashSet<_>>();
let mut simple_textures = HashMap::new();
let rendered_block_textures = Arc::new(Mutex::new(HashMap::new()));
let mut cache_manager_lock = cache_manager.lock();
for name in all_texture_names {
let texture = cache_manager_lock.load_media_by_name(&name).await?;
simple_textures.insert(name, texture);
}
drop(cache_manager_lock);
tokio::task::block_in_place(|| {
std::thread::scope(|s| {
const NUM_MINI_RENDER_THREADS: usize = 4;
let mut handles = Vec::with_capacity(NUM_MINI_RENDER_THREADS);
for i in 0..NUM_MINI_RENDER_THREADS {
let ctx = ctx.clone();
let mut tasks = vec![];
for (j, name) in all_rendered_blocks.iter().enumerate() {
if (j % NUM_MINI_RENDER_THREADS) == i {
tasks.push(name);
}
}
handles.push(s.spawn(|| {
let mut renderer = MiniBlockRenderer::new(
ctx,
[64, 64],
block_renderer.atlas(),
block_renderer.block_types().air_block(),
)
.unwrap();
for name in tasks {
let block_id = match block_renderer.block_types().get_block_by_name(name) {
Some(id) => id,
None => continue,
};
let block_def = match block_renderer.block_types().get_blockdef(block_id) {
Some(block_type) => block_type,
None => continue,
};
let cached_content = cache_manager
.lock()
.try_get_block_appearance(block_def)
.unwrap();
if let Some(content) = cached_content {
rendered_block_textures
.lock()
.insert(name, ImageImporter::import_from_memory(&content).unwrap());
continue;
}
let block_tex = renderer
.render(block_renderer, block_id, block_def)
.unwrap();
let mut bytes: Vec<u8> = Vec::new();
block_tex
.write_to(&mut Cursor::new(&mut bytes), image::ImageOutputFormat::Png)
.unwrap();
cache_manager
.lock()
.insert_block_appearance(block_def, bytes)
.unwrap();
rendered_block_textures.lock().insert(name, block_tex);
}
}));
}
});
});
let config = texture_packer::TexturePackerConfig {
allow_rotation: false,
max_width: 4096,
max_height: 4096,
border_padding: 1,
texture_padding: 1,
texture_extrusion: 1,
trim: false,
texture_outlines: false,
};
let mut texture_packer = texture_packer::TexturePacker::new_skyline(config);
pack_tex(
&mut texture_packer,
CROSSHAIR,
ImageImporter::import_from_memory(include_bytes!("crosshair.png")).unwrap(),
)?;
pack_tex(
&mut texture_packer,
UNKNOWN_TEXTURE,
ImageImporter::import_from_memory(include_bytes!("../block_unknown.png")).unwrap(),
)?;
pack_tex(
&mut texture_packer,
FRAME_SELECTED,
ImageImporter::import_from_memory(include_bytes!("frame_selected.png")).unwrap(),
)?;
pack_tex(
&mut texture_packer,
FRAME_UNSELECTED,
ImageImporter::import_from_memory(include_bytes!("frame_unselected.png")).unwrap(),
)?;
pack_tex(
&mut texture_packer,
DIGIT_ATLAS,
ImageImporter::import_from_memory(include_bytes!("digit_atlas.png")).unwrap(),
)?;
pack_tex(
&mut texture_packer,
TEST_ITEM,
ImageImporter::import_from_memory(include_bytes!("testonly_pickaxe.png")).unwrap(),
)?;
const COLOR_SCALE_R: [u8; 8] = [255, 255, 255, 255, 191, 128, 64, 0];
const COLOR_SCALE_G: [u8; 8] = [0, 64, 128, 191, 255, 255, 255, 255];
for i in 0..8 {
let mut image = RgbImage::new(1, 1);
image.put_pixel(0, 0, image::Rgb([COLOR_SCALE_R[i], COLOR_SCALE_G[i], 0]));
texture_packer
.pack_own(
format!("builtin:wear_{}", i),
DynamicImage::ImageRgb8(image),
)
.map_err(|x| Error::msg(format!("Texture pack failed: {:?}", x)))?;
}
for (name, texture) in simple_textures {
pack_tex(
&mut texture_packer,
&("simple:".to_string() + &name),
ImageImporter::import_from_memory(&texture)
.map_err(|x| Error::msg(format!("Texture import failed: {:?}", x)))?,
)?;
}
for (name, texture) in rendered_block_textures.lock().drain() {
pack_tex(
&mut texture_packer,
&("rendered_block:".to_string() + &name),
texture,
)?;
}
let fallback_rect = texture_packer
.get_frame(&String::from(FALLBACK_UNKNOWN_TEXTURE))
.unwrap()
.frame;
let texture_atlas = texture_packer::exporter::ImageExporter::export(&texture_packer)
.map_err(|x| Error::msg(format!("Texture atlas export failed: {:?}", x)))?;
let mut texture_coords = HashMap::new();
for item in item_defs.all_item_defs() {
if !item.block_apperance.is_empty() {
texture_coords.insert(
item.short_name.clone(),
texture_packer
.get_frame(&("rendered_block:".to_string() + &item.block_apperance))
.map(|x| x.frame)
.unwrap_or(fallback_rect),
);
} else if let Some(tex) = &item.inventory_texture {
texture_coords.insert(
item.short_name.clone(),
texture_packer
.get_frame(&("simple:".to_string() + &tex.texture_name))
.map(|x| x.frame)
.unwrap_or(fallback_rect),
);
} else {
texture_coords.insert(item.short_name.clone(), fallback_rect);
};
}
for (key, value) in texture_packer.get_frames().iter() {
if key.starts_with("builtin:") {
texture_coords.insert(key.clone(), value.frame);
}
}
let texture_atlas = Arc::new(Texture2DHolder::create(&ctx, &texture_atlas)?);
Ok((texture_atlas, texture_coords))
}
fn get_texture(
item: &perovskite_core::protocol::items::ItemStack,
atlas_coords: &HashMap<String, Rect>,
item_defs: &ClientItemManager,
) -> Rect {
item_defs
.get(&item.item_name)
.and_then(|x| atlas_coords.get(&x.short_name).copied())
.unwrap_or(*atlas_coords.get(UNKNOWN_TEXTURE).unwrap())
}
const CROSSHAIR: &str = "builtin:crosshair";
const DIGIT_ATLAS: &str = "builtin:digit_atlas";
const FRAME_SELECTED: &str = "builtin:frame_selected";
const FRAME_UNSELECTED: &str = "builtin:frame_unselected";
const TEST_ITEM: &str = "builtin:test_item";
const UNKNOWN_TEXTURE: &str = "builtin:unknown";