diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..5c94c4c5 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +# Steam Hardware survey: https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam +[target.'cfg(target_arch="x86_64")'] +rustflags = ["-C", "target-feature=+aes,+avx,+avx2,+cmpxchg16b,+fma,+sse3,+ssse3,+sse4.1,+sse4.2"] diff --git a/Cargo.lock b/Cargo.lock index 4b851089..22e5fd2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,6 +327,16 @@ dependencies = [ "objc2", ] +[[package]] +name = "block_compression" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ba559a2fa71d562c6fb6d03f04334a60520016e60d1b9667aed1f61b4af37f" +dependencies = [ + "bytemuck", + "wgpu", +] + [[package]] name = "bstr" version = "1.11.3" @@ -415,9 +425,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.9" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "jobserver", "libc", @@ -760,6 +770,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + [[package]] name = "equivalent" version = "1.0.1" @@ -1148,15 +1164,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "http" version = "1.2.0" @@ -1455,9 +1462,9 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", @@ -1485,9 +1492,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "itertools" @@ -1573,9 +1580,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kira" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50679def12511d0a12c261ecffad7bf0c39d83250b62a3f905fa52343613ca77" +checksum = "a6c094dc109f5735ce9586c9865c7059fe2e951a2a74ccda7350f756ee98cbc2" dependencies = [ "atomic-arena", "cpal", @@ -1593,6 +1600,7 @@ name = "korangar" version = "0.1.0" dependencies = [ "arrayvec", + "block_compression", "bumpalo", "bytemuck", "cgmath", @@ -1799,9 +1807,9 @@ dependencies = [ [[package]] name = "luajit-src" -version = "210.5.11+97813fb" +version = "210.5.12+a4f56a4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3015551c284515db7c30c559fc1080f9cb9ee990d1f6fca315451a107c7540bb" +checksum = "b3a8e7962a5368d5f264d045a5a255e90f9aa3fc1941ae15a8d2940d42cac671" dependencies = [ "cc", "which", @@ -2779,9 +2787,9 @@ dependencies = [ [[package]] name = "range-alloc" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" [[package]] name = "rangemap" @@ -3027,9 +3035,9 @@ checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustix" -version = "0.38.43" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.8.0", "errno 0.3.10", @@ -3206,9 +3214,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.135" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" dependencies = [ "itoa", "memchr", @@ -4313,12 +4321,12 @@ dependencies = [ [[package]] name = "which" -version = "6.0.3" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +checksum = "fb4a9e33648339dc1642b0e36e21b3385e6148e289226f657c809dee59df5028" dependencies = [ "either", - "home", + "env_home", "rustix", "winsafe", ] diff --git a/Cargo.toml b/Cargo.toml index 2acc28bb..52af1979 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = ["korangar", "ragnarok_*", "korangar_*"] [workspace.dependencies] arrayvec = "0.7" bitflags = "2.6" +block_compression = { version = "0.2", default-features = false } bumpalo = "3.16" bytemuck = "1.21" cgmath = "0.18" @@ -52,6 +53,9 @@ walkdir = "2.5" wgpu = "24" winit = "0.30" +[profile.release] +lto = "thin" + [profile.dev.build-override] opt-level = 3 diff --git a/korangar/Cargo.toml b/korangar/Cargo.toml index 38606214..ba8e785d 100644 --- a/korangar/Cargo.toml +++ b/korangar/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] arrayvec = { workspace = true } +block_compression = { workspace = true, features = ["bc7", "wgpu"] } bumpalo = { workspace = true, features = ["allocator_api"] } bytemuck = { workspace = true, features = ["derive", "extern_crate_std", "min_const_generics"] } cgmath = { workspace = true, features = ["mint", "serde"] } diff --git a/korangar/src/graphics/capabilities.rs b/korangar/src/graphics/capabilities.rs index a45dc38a..afebf1a0 100644 --- a/korangar/src/graphics/capabilities.rs +++ b/korangar/src/graphics/capabilities.rs @@ -17,6 +17,7 @@ pub struct Capabilities { bindless: bool, multidraw_indirect: bool, clamp_to_border: bool, + texture_compression: bool, #[cfg(feature = "debug")] polygon_mode_line: bool, required_features: Features, @@ -37,6 +38,7 @@ impl Capabilities { bindless: false, multidraw_indirect: false, clamp_to_border: false, + texture_compression: false, #[cfg(feature = "debug")] polygon_mode_line: false, required_features: Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, @@ -58,6 +60,7 @@ impl Capabilities { adapter_features, Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, ); + Self::check_feature(adapter_features, Features::TEXTURE_COMPRESSION_BC); Self::check_feature(adapter_features, Features::TEXTURE_BINDING_ARRAY); Self::check_feature(adapter_features, Features::POLYGON_MODE_LINE); } @@ -85,6 +88,11 @@ impl Capabilities { capabilities.required_features |= Features::ADDRESS_MODE_CLAMP_TO_BORDER | Features::ADDRESS_MODE_CLAMP_TO_ZERO; } + if adapter_features.contains(Features::TEXTURE_COMPRESSION_BC) { + capabilities.texture_compression = true; + capabilities.required_features |= Features::TEXTURE_COMPRESSION_BC; + } + #[cfg(feature = "debug")] if adapter_features.contains(Features::POLYGON_MODE_LINE) { capabilities.polygon_mode_line = true; @@ -135,6 +143,11 @@ impl Capabilities { self.clamp_to_border } + /// Returns `true` if the backend supports BC texture compression. + pub fn supports_texture_compression(&self) -> bool { + self.texture_compression + } + /// Returns `true` if the backend allows drawing triangles as lines /// (wireframe) instead of filled. #[cfg(feature = "debug")] diff --git a/korangar/src/graphics/engine.rs b/korangar/src/graphics/engine.rs index 1250d12f..2bcce3bd 100644 --- a/korangar/src/graphics/engine.rs +++ b/korangar/src/graphics/engine.rs @@ -17,7 +17,8 @@ use winit::window::Window; use super::{ AntiAliasingResource, Capabilities, EntityInstruction, FramePacer, FrameStage, GlobalContext, LimitFramerate, ModelInstruction, Msaa, - Prepare, PresentModeInfo, ScreenSpaceAntiAliasing, ShadowDetail, Ssaa, Surface, TextureSamplerType, RENDER_TO_TEXTURE_FORMAT, + Prepare, PresentModeInfo, ScreenSpaceAntiAliasing, ShadowDetail, Ssaa, Surface, TextureCompression, TextureSamplerType, + RENDER_TO_TEXTURE_FORMAT, }; use crate::graphics::instruction::RenderInstruction; use crate::graphics::passes::*; @@ -428,6 +429,14 @@ impl GraphicsEngine { ssaa } + pub fn check_texture_compression_requirements(&self, texture_compression: TextureCompression) -> TextureCompression { + if self.capabilities.supports_texture_compression() { + texture_compression + } else { + TextureCompression::Off + } + } + pub fn on_suspended(&mut self) { // Android devices are expected to drop their surface view. if cfg!(target_os = "android") { diff --git a/korangar/src/graphics/passes/directional_shadow/shader/model.wgsl b/korangar/src/graphics/passes/directional_shadow/shader/model.wgsl index 6d6e8242..6ed31ecd 100644 --- a/korangar/src/graphics/passes/directional_shadow/shader/model.wgsl +++ b/korangar/src/graphics/passes/directional_shadow/shader/model.wgsl @@ -42,7 +42,7 @@ fn vs_main( fn fs_main(input: VertexOutput) -> @location(0) vec4 { var diffuse_color = textureSampleLevel(texture, texture_sampler, input.texture_coordinates, 0.0); - if (diffuse_color.a < 1.0) { + if (diffuse_color.a == 0.0) { discard; } diff --git a/korangar/src/graphics/passes/point_shadow/shader/model.wgsl b/korangar/src/graphics/passes/point_shadow/shader/model.wgsl index 0c4abf0d..f5c15b38 100644 --- a/korangar/src/graphics/passes/point_shadow/shader/model.wgsl +++ b/korangar/src/graphics/passes/point_shadow/shader/model.wgsl @@ -47,7 +47,7 @@ fn fs_main(input: VertexOutput) -> @builtin(frag_depth) f32 { let light_distance = length(input.world_position.xyz - pass_uniforms.light_position.xyz); - if (diffuse_color.a != 1.0) { + if (diffuse_color.a == 0.0) { discard; } diff --git a/korangar/src/graphics/settings.rs b/korangar/src/graphics/settings.rs index 46e18f68..173e75b2 100644 --- a/korangar/src/graphics/settings.rs +++ b/korangar/src/graphics/settings.rs @@ -2,9 +2,11 @@ use std::fmt::{Display, Formatter}; #[cfg(feature = "debug")] use std::num::NonZeroU32; +use block_compression::{BC7Settings, CompressionVariant}; #[cfg(feature = "debug")] use derive_new::new; use serde::{Deserialize, Serialize}; +use wgpu::TextureFormat; use crate::interface::layout::ScreenSize; @@ -160,6 +162,46 @@ impl Display for ScreenSpaceAntiAliasing { } } +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum TextureCompression { + Off, + UltraFast, + VeryFast, + Fast, + Normal, +} + +impl TextureCompression { + pub fn is_uncompressed(&self) -> bool { + *self == TextureCompression::Off + } +} + +impl From for TextureFormat { + fn from(value: TextureCompression) -> Self { + match value { + TextureCompression::Off => TextureFormat::Rgba8UnormSrgb, + TextureCompression::UltraFast | TextureCompression::VeryFast | TextureCompression::Fast | TextureCompression::Normal => { + TextureFormat::Bc7RgbaUnormSrgb + } + } + } +} + +impl TryFrom for CompressionVariant { + type Error = (); + + fn try_from(value: TextureCompression) -> Result { + match value { + TextureCompression::Off => Err(()), + TextureCompression::UltraFast => Ok(CompressionVariant::BC7(BC7Settings::alpha_ultrafast())), + TextureCompression::VeryFast => Ok(CompressionVariant::BC7(BC7Settings::alpha_very_fast())), + TextureCompression::Fast => Ok(CompressionVariant::BC7(BC7Settings::alpha_fast())), + TextureCompression::Normal => Ok(CompressionVariant::BC7(BC7Settings::alpha_basic())), + } + } +} + #[cfg(feature = "debug")] #[derive(Copy, Clone, Default, new)] pub struct RenderSettings { diff --git a/korangar/src/graphics/texture.rs b/korangar/src/graphics/texture.rs index b0ee8fdf..f233c1b7 100644 --- a/korangar/src/graphics/texture.rs +++ b/korangar/src/graphics/texture.rs @@ -7,8 +7,9 @@ use hashbrown::HashMap; use korangar_util::container::Cacheable; use wgpu::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, - BindingType, Device, Extent3d, Queue, ShaderStages, TexelCopyBufferLayout, TextureAspect, TextureDescriptor, TextureDimension, - TextureFormat, TextureSampleType, TextureUsages, TextureView, TextureViewDescriptor, TextureViewDimension, + BindingType, Device, Extent3d, Origin3d, Queue, ShaderStages, TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect, + TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureView, TextureViewDescriptor, + TextureViewDimension, }; use crate::interface::layout::ScreenSize; @@ -80,24 +81,54 @@ impl Texture { } } - /// This function doesn't upload mip-map data. Mip maps should be written - /// using the `MipMapRenderPassContext` & `Lanczos3Drawer`. pub fn new_with_data(device: &Device, queue: &Queue, descriptor: &TextureDescriptor, image_data: &[u8], transparent: bool) -> Self { let id = TEXTURE_ID.fetch_add(1, Ordering::Relaxed); let label = descriptor.label.map(|label| label.to_string()); let texture = device.create_texture(descriptor); - let block_size = texture.format().block_copy_size(None).unwrap(); - - queue.write_texture( - texture.as_image_copy(), - image_data, - TexelCopyBufferLayout { - offset: 0, - bytes_per_row: Some(descriptor.size.width * block_size), - rows_per_image: Some(descriptor.size.height), - }, - descriptor.size, - ); + let format = texture.format(); + + let (block_width, block_height) = format.block_dimensions(); + let block_size = format.block_copy_size(None).unwrap(); + + let mut offset = 0; + let mut mip_width = descriptor.size.width; + let mut mip_height = descriptor.size.height; + + for mip_level in 0..descriptor.mip_level_count { + let width_blocks = mip_width.div_ceil(block_width); + let height_blocks = mip_height.div_ceil(block_height); + + let bytes_per_row = width_blocks * block_size; + let mip_size = bytes_per_row * height_blocks; + + if offset + mip_size as usize <= image_data.len() { + queue.write_texture( + TexelCopyTextureInfo { + texture: &texture, + mip_level, + origin: Origin3d::ZERO, + aspect: TextureAspect::All, + }, + &image_data[offset..offset + mip_size as usize], + TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(bytes_per_row), + rows_per_image: None, + }, + Extent3d { + width: mip_width, + height: mip_height, + depth_or_array_layers: 1, + }, + ); + + offset += mip_size as usize; + mip_width = (mip_width / 2).max(1); + mip_height = (mip_height / 2).max(1); + } else { + break; + } + } let texture_view = texture.create_view(&TextureViewDescriptor { label: descriptor.label, diff --git a/korangar/src/interface/windows/settings/graphics.rs b/korangar/src/interface/windows/settings/graphics.rs index a7505b52..67407f44 100644 --- a/korangar/src/interface/windows/settings/graphics.rs +++ b/korangar/src/interface/windows/settings/graphics.rs @@ -4,7 +4,8 @@ use korangar_interface::windows::{PrototypeWindow, Window, WindowBuilder}; use korangar_interface::{dimension_bound, size_bound}; use crate::graphics::{ - LimitFramerate, Msaa, PresentModeInfo, ScreenSpaceAntiAliasing, ShadowDetail, ShadowQuality, Ssaa, TextureSamplerType, + LimitFramerate, Msaa, PresentModeInfo, ScreenSpaceAntiAliasing, ShadowDetail, ShadowQuality, Ssaa, TextureCompression, + TextureSamplerType, }; use crate::interface::application::InterfaceSettings; use crate::interface::layout::ScreenSize; @@ -23,6 +24,7 @@ pub struct GraphicsSettingsWindow< ShadowResolution, ShadowMode, HighQualityInterface, + Compression, > where LightingRenderMode: TrackedState + 'static, Vsync: TrackedStateBinary, @@ -35,6 +37,7 @@ pub struct GraphicsSettingsWindow< ShadowResolution: TrackedState + 'static, ShadowMode: TrackedState + 'static, HighQualityInterface: TrackedStateBinary, + Compression: TrackedState + 'static, { present_mode_info: PresentModeInfo, supported_msaa: Vec<(String, Msaa)>, @@ -49,6 +52,7 @@ pub struct GraphicsSettingsWindow< shadow_detail: ShadowResolution, shadow_quality: ShadowMode, high_quality_interface: HighQualityInterface, + texture_compression: Compression, } impl< @@ -63,6 +67,7 @@ impl< ShadowResolution, ShadowMode, HighQualityInterface, + Compression, > GraphicsSettingsWindow< LightingRenderMode, @@ -76,6 +81,7 @@ impl< ShadowResolution, ShadowMode, HighQualityInterface, + Compression, > where LightingRenderMode: TrackedState + 'static, @@ -89,6 +95,7 @@ where ShadowResolution: TrackedState + 'static, ShadowMode: TrackedState + 'static, HighQualityInterface: TrackedStateBinary, + Compression: TrackedState + 'static, { pub const WINDOW_CLASS: &'static str = "graphics_settings"; @@ -106,6 +113,7 @@ where shadow_detail: ShadowResolution, shadow_quality: ShadowMode, high_quality_interface: HighQualityInterface, + texture_compression: Compression, ) -> Self { Self { present_mode_info, @@ -121,6 +129,7 @@ where shadow_detail, shadow_quality, high_quality_interface, + texture_compression, } } } @@ -137,6 +146,7 @@ impl< ShadowResolution, ShadowMode, HighQualityInterface, + Compression, > PrototypeWindow for GraphicsSettingsWindow< LightingRenderMode, @@ -150,6 +160,7 @@ impl< ShadowResolution, ShadowMode, HighQualityInterface, + Compression, > where LightingRenderMode: TrackedState + 'static, @@ -163,6 +174,7 @@ where ShadowResolution: TrackedState + 'static, ShadowMode: TrackedState + 'static, HighQualityInterface: TrackedStateBinary, + Compression: TrackedState + 'static, { fn window_class(&self) -> Option<&str> { Self::WINDOW_CLASS.into() @@ -204,6 +216,22 @@ where .with_event(Box::new(Vec::new)) .with_width(dimension_bound!(!)) .wrap(), + Text::default() + .with_text("Texture compression") + .with_width(dimension_bound!(50%)) + .wrap(), + PickList::default() + .with_options(vec![ + ("Off", TextureCompression::Off), + ("Ultra Fast", TextureCompression::UltraFast), + ("Very Fast", TextureCompression::VeryFast), + ("Fast", TextureCompression::Fast), + ("Normal", TextureCompression::Normal), + ]) + .with_selected(self.texture_compression.clone()) + .with_event(Box::new(Vec::new)) + .with_width(dimension_bound!(!)) + .wrap(), Text::default().with_text("Multisampling").with_width(dimension_bound!(50%)).wrap(), PickList::default() .with_options(self.supported_msaa.clone()) diff --git a/korangar/src/loaders/async/mod.rs b/korangar/src/loaders/async/mod.rs index 5d104e92..5f545b12 100644 --- a/korangar/src/loaders/async/mod.rs +++ b/korangar/src/loaders/async/mod.rs @@ -11,7 +11,7 @@ use korangar_util::texture_atlas::AtlasAllocation; use ragnarok_packets::{EntityId, ItemId, TilePosition}; use rayon::{ThreadPool, ThreadPoolBuilder}; -use crate::graphics::Texture; +use crate::graphics::{Texture, TextureCompression}; use crate::loaders::error::LoadError; use crate::loaders::{ActionLoader, AnimationLoader, ImageType, MapLoader, ModelLoader, SpriteLoader, TextureLoader}; #[cfg(feature = "debug")] @@ -33,8 +33,14 @@ pub enum LoaderId { pub enum LoadableResource { AnimationData(Arc), - ItemSprite { texture: Arc, location: ItemLocation }, - Map { map: Box, player_position: TilePosition }, + ItemSprite { + texture: Arc, + location: ItemLocation, + }, + Map { + map: Box, + player_position: Option, + }, } enum LoadStatus { @@ -71,7 +77,7 @@ impl AsyncLoader { ) -> Self { let thread_pool = ThreadPoolBuilder::new() .num_threads(1) - .thread_name(|_| "async loader".to_string()) + .thread_name(|number| format!("light task thread pool {number}")) .build() .unwrap(); @@ -152,8 +158,9 @@ impl AsyncLoader { pub fn request_map_load( &self, + texture_compression: TextureCompression, map_name: String, - player_position: TilePosition, + player_position: Option, #[cfg(feature = "debug")] tile_texture_mapping: Arc>, ) { let map_loader = self.map_loader.clone(); @@ -165,6 +172,7 @@ impl AsyncLoader { let _load_measurement = Profiler::start_measurement("map load"); let map = map_loader.load( + texture_compression, map_name, &model_loader, texture_loader, diff --git a/korangar/src/loaders/map/mod.rs b/korangar/src/loaders/map/mod.rs index da07fa42..50ba26f7 100644 --- a/korangar/src/loaders/map/mod.rs +++ b/korangar/src/loaders/map/mod.rs @@ -21,7 +21,7 @@ use wgpu::{BufferUsages, Device, Queue}; pub use self::vertices::MAP_TILE_SIZE; use self::vertices::{generate_tile_vertices, ground_vertices}; use super::error::LoadError; -use crate::graphics::{Buffer, ModelVertex, NativeModelVertex, Texture}; +use crate::graphics::{Buffer, ModelVertex, NativeModelVertex, Texture, TextureCompression}; use crate::loaders::{GameFileLoader, ImageType, ModelLoader, TextureAtlasFactory, TextureLoader}; use crate::world::{LightSourceKey, Model}; use crate::{EffectSourceExt, LightSourceExt, Map, Object, ObjectKey, SoundSourceExt}; @@ -57,6 +57,7 @@ pub struct MapLoader { impl MapLoader { pub fn load( &self, + texture_compression: TextureCompression, resource_file: String, model_loader: &ModelLoader, texture_loader: Arc, @@ -65,7 +66,7 @@ impl MapLoader { #[cfg(feature = "debug")] let timer = Timer::new_dynamic(format!("load map from {}", &resource_file)); - let mut texture_atlas_factory = TextureAtlasFactory::new(texture_loader.clone(), "map", true, true); + let mut texture_atlas_factory = TextureAtlasFactory::new(texture_loader.clone(), "map", true, true, texture_compression); let mut deferred_vertex_generation: Vec = Vec::new(); let map_file_name = format!("data\\{}.rsw", resource_file); @@ -207,6 +208,7 @@ impl MapLoader { .filter(|_| !(water_bounds.min == Point2::from_value(f32::MAX) && water_bounds.max == Point2::from_value(f32::MIN))); let map = Map::new( + resource_file, gat_data.map_width as usize, gat_data.map_height as usize, water_settings, diff --git a/korangar/src/loaders/sprite/mod.rs b/korangar/src/loaders/sprite/mod.rs index 4f1b74a9..aa9a5508 100644 --- a/korangar/src/loaders/sprite/mod.rs +++ b/korangar/src/loaders/sprite/mod.rs @@ -11,9 +11,8 @@ use korangar_util::FileLoader; use ragnarok_bytes::{ByteReader, FromBytes}; use ragnarok_formats::sprite::{PaletteColor, RgbaImageData, SpriteData}; use ragnarok_formats::version::InternalVersion; -use wgpu::{Device, Extent3d, Queue, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages}; -use super::FALLBACK_SPRITE_FILE; +use super::{TextureLoader, FALLBACK_SPRITE_FILE}; use crate::graphics::Texture; use crate::loaders::error::LoadError; use crate::loaders::GameFileLoader; @@ -37,18 +36,16 @@ impl Cacheable for Sprite { } pub struct SpriteLoader { - device: Arc, - queue: Arc, game_file_loader: Arc, + texture_loader: Arc, cache: Mutex>>, } impl SpriteLoader { - pub fn new(device: Arc, queue: Arc, game_file_loader: Arc) -> Self { + pub fn new(game_file_loader: Arc, texture_loader: Arc) -> Self { Self { - device, - queue, game_file_loader, + texture_loader, cache: Mutex::new(SimpleCache::new( NonZeroU32::new(MAX_CACHE_COUNT).unwrap(), NonZeroUsize::new(MAX_CACHE_SIZE).unwrap(), @@ -151,29 +148,11 @@ impl SpriteLoader { .map(|mut image_data| { premultiply_alpha(&mut image_data.data); - let texture = Texture::new_with_data( - &self.device, - &self.queue, - &TextureDescriptor { - label: Some(path), - size: Extent3d { - width: image_data.width as u32, - height: image_data.height as u32, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: TextureFormat::Rgba8UnormSrgb, - usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }, - RgbaImage::from_raw(image_data.width as u32, image_data.height as u32, image_data.data) - .unwrap() - .as_raw(), + self.texture_loader.create_color( + path, + RgbaImage::from_raw(image_data.width as u32, image_data.height as u32, image_data.data).unwrap(), false, - ); - Arc::new(texture) + ) }) .collect(); diff --git a/korangar/src/loaders/texture/mod.rs b/korangar/src/loaders/texture/mod.rs index e15d5676..755a98a8 100644 --- a/korangar/src/loaders/texture/mod.rs +++ b/korangar/src/loaders/texture/mod.rs @@ -2,6 +2,7 @@ use std::io::Cursor; use std::num::{NonZeroU32, NonZeroUsize}; use std::sync::{Arc, Mutex}; +use block_compression::{CompressionVariant, GpuBlockCompressor}; use hashbrown::HashMap; use image::{GrayImage, ImageBuffer, ImageFormat, ImageReader, Rgba, RgbaImage}; #[cfg(feature = "debug")] @@ -11,13 +12,14 @@ use korangar_util::container::SimpleCache; use korangar_util::texture_atlas::{AllocationId, AtlasAllocation, OfflineTextureAtlas}; use korangar_util::FileLoader; use wgpu::{ - CommandEncoderDescriptor, Device, Extent3d, Queue, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, - TextureViewDescriptor, TextureViewDimension, + BufferDescriptor, BufferUsages, CommandEncoderDescriptor, ComputePassDescriptor, Device, Extent3d, Origin3d, Queue, + TexelCopyBufferInfo, TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, + TextureUsages, TextureViewDescriptor, TextureViewDimension, }; use super::error::LoadError; use super::{FALLBACK_BMP_FILE, FALLBACK_JPEG_FILE, FALLBACK_PNG_FILE, FALLBACK_TGA_FILE, MIP_LEVELS}; -use crate::graphics::{Lanczos3Drawer, MipMapRenderPassContext, Texture}; +use crate::graphics::{Lanczos3Drawer, MipMapRenderPassContext, Texture, TextureCompression}; use crate::loaders::GameFileLoader; const MAX_CACHE_COUNT: u32 = 512; @@ -36,12 +38,14 @@ pub struct TextureLoader { game_file_loader: Arc, mip_map_render_context: MipMapRenderPassContext, lanczos3_drawer: Lanczos3Drawer, + block_compressor: Mutex, cache: Mutex>>, } impl TextureLoader { pub fn new(device: Arc, queue: Arc, game_file_loader: Arc) -> Self { let lanczos3_drawer = Lanczos3Drawer::new(&device); + let block_compressor = Mutex::new(GpuBlockCompressor::new(device.clone(), queue.clone())); Self { device, @@ -49,6 +53,7 @@ impl TextureLoader { game_file_loader, mip_map_render_context: MipMapRenderPassContext::default(), lanczos3_drawer, + block_compressor, cache: Mutex::new(SimpleCache::new( NonZeroU32::new(MAX_CACHE_COUNT).unwrap(), NonZeroUsize::new(MAX_CACHE_SIZE).unwrap(), @@ -56,31 +61,97 @@ impl TextureLoader { } } - fn create(&self, name: &str, image: RgbaImage, transparent: bool) -> Arc { + fn create_raw( + &self, + name: &str, + width: u32, + height: u32, + mip_level_count: u32, + format: TextureFormat, + transparent: bool, + data: &[u8], + ) -> Arc { let texture = Texture::new_with_data( &self.device, &self.queue, &TextureDescriptor { label: Some(name), size: Extent3d { - width: image.width(), - height: image.height(), + width, + height, depth_or_array_layers: 1, }, - mip_level_count: 1, + mip_level_count, sample_count: 1, dimension: TextureDimension::D2, - format: TextureFormat::Rgba8UnormSrgb, + format, usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, view_formats: &[], }, - image.as_raw(), + data, transparent, ); Arc::new(texture) } + pub fn create_color(&self, name: &str, image: RgbaImage, transparent: bool) -> Arc { + self.create_raw( + name, + image.width(), + image.height(), + 1, + TextureFormat::Rgba8UnormSrgb, + transparent, + image.as_raw(), + ) + } + pub fn create_sdf(&self, name: &str, image: GrayImage) -> Arc { + self.create_raw( + name, + image.width(), + image.height(), + 1, + TextureFormat::R8Unorm, + false, + image.as_raw(), + ) + } + + pub(crate) fn create_msdf(&self, name: &str, image: RgbaImage) -> Arc { + self.create_raw( + name, + image.width(), + image.height(), + 1, + TextureFormat::Rgba8Unorm, + false, + image.as_raw(), + ) + } + + pub(crate) fn create_with_mipmaps( + &self, + name: &str, + texture_compression: TextureCompression, + mips_level: u32, + transparent: bool, + image: RgbaImage, + ) -> Arc { + match texture_compression.is_uncompressed() { + true => self.create_uncompressed_with_mipmaps(name, texture_compression, mips_level, transparent, image), + false => self.create_compressed_with_mipmaps(name, texture_compression, mips_level, transparent, image), + } + } + + pub(crate) fn create_uncompressed_with_mipmaps( + &self, + name: &str, + _texture_compression: TextureCompression, + mips_level: u32, + transparent: bool, + image: RgbaImage, + ) -> Arc { let texture = Texture::new_with_data( &self.device, &self.queue, @@ -91,71 +162,140 @@ impl TextureLoader { height: image.height(), depth_or_array_layers: 1, }, - mip_level_count: 1, + mip_level_count: MIP_LEVELS, sample_count: 1, dimension: TextureDimension::D2, - format: TextureFormat::R8Unorm, - usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, + format: TextureFormat::Rgba8UnormSrgb, + usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }, image.as_raw(), - false, + transparent, ); + + if mips_level > 1 { + let mut mip_views = Vec::with_capacity(mips_level as usize); + + for level in 0..mips_level { + let view = texture.get_texture().create_view(&TextureViewDescriptor { + label: Some(&format!("mip map level {level}")), + format: None, + dimension: Some(TextureViewDimension::D2), + usage: None, + aspect: TextureAspect::All, + base_mip_level: level, + mip_level_count: Some(1), + base_array_layer: 0, + array_layer_count: Some(1), + }); + mip_views.push(view); + } + + let mut encoder = self.device.create_command_encoder(&CommandEncoderDescriptor { + label: Some("TextureLoader"), + }); + + for index in 0..(mips_level - 1) as usize { + let mut pass = + self.mip_map_render_context + .create_pass(&self.device, &mut encoder, &mip_views[index], &mip_views[index + 1]); + + self.lanczos3_drawer.draw(&mut pass); + } + + self.queue.submit(Some(encoder.finish())); + } + Arc::new(texture) } - pub fn create_msdf(&self, name: &str, image: RgbaImage) -> Arc { - let texture = Texture::new_with_data( + pub(crate) fn create_compressed_with_mipmaps( + &self, + name: &str, + texture_compression: TextureCompression, + mips_level: u32, + transparent: bool, + image: RgbaImage, + ) -> Arc { + let width = image.width(); + let height = image.height(); + + assert_eq!(width % 4, 0, "Texture width must be aligned to 4 pixels"); + assert_eq!(height % 4, 0, "Texture height must be aligned to 4 pixels"); + + let temp_texture = Texture::new( &self.device, - &self.queue, &TextureDescriptor { - label: Some(name), + label: Some("temporary mip texture"), size: Extent3d { - width: image.width(), - height: image.height(), + width, + height, depth_or_array_layers: 1, }, - mip_level_count: 1, + mip_level_count: mips_level, sample_count: 1, dimension: TextureDimension::D2, - format: TextureFormat::Rgba8Unorm, - usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, - view_formats: &[], + format: TextureFormat::Rgba8UnormSrgb, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT, + view_formats: &[TextureFormat::Rgba8Unorm], }, - image.as_raw(), - false, + transparent, ); - Arc::new(texture) - } - fn create_with_mip_maps(&self, name: &str, image: RgbaImage, transparent: bool) -> Arc { - let texture = Texture::new_with_data( + self.queue.write_texture( + temp_texture.get_texture().as_image_copy(), + &image, + TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(width * 4), + rows_per_image: None, + }, + Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + ); + + let texture = Texture::new( &self.device, - &self.queue, &TextureDescriptor { label: Some(name), size: Extent3d { - width: image.width(), - height: image.height(), + width, + height, depth_or_array_layers: 1, }, - mip_level_count: MIP_LEVELS, + mip_level_count: mips_level, sample_count: 1, dimension: TextureDimension::D2, - format: TextureFormat::Rgba8UnormSrgb, - usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT, + format: texture_compression.into(), + usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, view_formats: &[], }, - image.as_raw(), transparent, ); - let mut mip_views = Vec::with_capacity(MIP_LEVELS as usize); + let mut encoder = self.device.create_command_encoder(&CommandEncoderDescriptor { + label: Some("compression encoder"), + }); + + let mut mip_views = Vec::with_capacity(mips_level as usize); + let variant = CompressionVariant::try_from(texture_compression).unwrap(); + + let mut total_size = 0; + let mut offsets = Vec::with_capacity(mips_level as usize); - for level in 0..MIP_LEVELS { - let view = texture.get_texture().create_view(&TextureViewDescriptor { - label: Some(&format!("mip map level {level}")), - format: None, + for level in 0..mips_level { + let mip_width = width >> level; + let mip_height = height >> level; + + offsets.push(total_size); + total_size += variant.blocks_byte_size(mip_width, mip_height); + + let view = temp_texture.get_texture().create_view(&TextureViewDescriptor { + label: Some(&format!("mip {level} view")), + format: Some(TextureFormat::Rgba8UnormSrgb), dimension: Some(TextureViewDimension::D2), usage: None, aspect: TextureAspect::All, @@ -164,20 +304,87 @@ impl TextureLoader { base_array_layer: 0, array_layer_count: Some(1), }); + mip_views.push(view); } - let mut encoder = self.device.create_command_encoder(&CommandEncoderDescriptor { - label: Some("TextureLoader"), - }); - - for index in 0..(MIP_LEVELS - 1) as usize { + for index in 0..(mips_level - 1) as usize { let mut pass = self .mip_map_render_context .create_pass(&self.device, &mut encoder, &mip_views[index], &mip_views[index + 1]); self.lanczos3_drawer.draw(&mut pass); } + let output_buffer = self.device.create_buffer(&BufferDescriptor { + label: Some("compressed output buffer"), + size: total_size as u64, + usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + + let mut block_compressor = self.block_compressor.lock().unwrap(); + + for level in 0..mips_level { + let mip_width = width >> level; + let mip_height = height >> level; + + block_compressor.add_compression_task( + variant, + &temp_texture.get_texture().create_view(&TextureViewDescriptor { + label: Some(&format!("compression mip {level} view")), + format: Some(TextureFormat::Rgba8Unorm), + base_mip_level: level, + mip_level_count: Some(1), + dimension: Some(TextureViewDimension::D2), + usage: None, + aspect: TextureAspect::All, + base_array_layer: 0, + array_layer_count: Some(1), + }), + mip_width, + mip_height, + &output_buffer, + Some(offsets[level as usize] as u32), + ); + } + + { + let mut pass = encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("texture compression pass"), + timestamp_writes: None, + }); + block_compressor.compress(&mut pass); + } + + drop(block_compressor); + + for level in 0..mips_level { + let mip_width = width >> level; + let mip_height = height >> level; + + encoder.copy_buffer_to_texture( + TexelCopyBufferInfo { + buffer: &output_buffer, + layout: TexelCopyBufferLayout { + offset: offsets[level as usize] as u64, + bytes_per_row: Some(variant.bytes_per_row(mip_width)), + rows_per_image: Some(mip_height), + }, + }, + TexelCopyTextureInfo { + texture: texture.get_texture(), + mip_level: level, + origin: Origin3d::ZERO, + aspect: TextureAspect::All, + }, + Extent3d { + width: mip_width, + height: mip_height, + depth_or_array_layers: 1, + }, + ); + } + self.queue.submit(Some(encoder.finish())); Arc::new(texture) @@ -187,7 +394,7 @@ impl TextureLoader { let texture = match image_type { ImageType::Color => { let (texture_data, transparent) = self.load_texture_data(path, false)?; - self.create(path, texture_data, transparent) + self.create_color(path, texture_data, transparent) } ImageType::Sdf => { let texture_data = self.load_grayscale_texture_data(path)?; @@ -383,6 +590,7 @@ pub struct TextureAtlasFactory { lookup: HashMap, create_mip_map: bool, transparent: bool, + texture_compression: TextureCompression, } #[derive(Copy, Clone)] @@ -398,8 +606,9 @@ impl TextureAtlasFactory { name: impl Into, add_padding: bool, paths: &[&str], + texture_compression: TextureCompression, ) -> (Vec, Arc) { - let mut factory = Self::new(texture_loader, name, add_padding, false); + let mut factory = Self::new(texture_loader, name, add_padding, false, texture_compression); let mut ids: Vec = paths.iter().map(|path| factory.register(path)).collect(); factory.build_atlas(); @@ -413,7 +622,13 @@ impl TextureAtlasFactory { (mapping, texture) } - pub fn new(texture_loader: Arc, name: impl Into, add_padding: bool, create_mip_map: bool) -> Self { + pub fn new( + texture_loader: Arc, + name: impl Into, + add_padding: bool, + create_mip_map: bool, + texture_compression: TextureCompression, + ) -> Self { let mip_level_count = if create_mip_map { NonZeroU32::new(MIP_LEVELS) } else { None }; Self { @@ -423,6 +638,7 @@ impl TextureAtlasFactory { lookup: HashMap::default(), create_mip_map, transparent: false, + texture_compression, } } @@ -456,18 +672,15 @@ impl TextureAtlasFactory { } pub fn upload_texture_atlas_texture(self) -> Arc { - if self.create_mip_map { - self.texture_loader.create_with_mip_maps( - &format!("{} texture atlas", self.name), - self.texture_atlas.get_atlas(), - self.transparent, - ) - } else { - self.texture_loader.create( - &format!("{} texture atlas", self.name), - self.texture_atlas.get_atlas(), - self.transparent, - ) - } + let atlas = self.texture_atlas.get_atlas(); + let name = format!("{} texture atlas", self.name); + + let mips_level = match self.create_mip_map { + true => MIP_LEVELS, + false => 1, + }; + + self.texture_loader + .create_with_mipmaps(&name, self.texture_compression, mips_level, self.transparent, atlas) } } diff --git a/korangar/src/main.rs b/korangar/src/main.rs index 1f82623b..6bf889cc 100644 --- a/korangar/src/main.rs +++ b/korangar/src/main.rs @@ -207,9 +207,9 @@ struct Client { ssaa: MappedRemote, screen_space_anti_aliasing: MappedRemote, high_quality_interface: MappedRemote, + texture_compression: MappedRemote, #[cfg(feature = "debug")] render_settings: PlainTrackedState, - mute_on_focus_loss: MappedRemote, application: InterfaceSettings, @@ -295,6 +295,7 @@ impl Client { .mapped(|settings| &settings.screen_space_anti_aliasing) .new_remote(); let high_quality_interface = graphics_settings.mapped(|settings| &settings.high_quality_interface).new_remote(); + let texture_compression = graphics_settings.mapped(|settings| &settings.texture_compression).new_remote(); #[cfg(feature = "debug")] let render_settings = PlainTrackedState::new(RenderSettings::new()); @@ -380,7 +381,7 @@ impl Client { game_file_loader.clone(), audio_engine.clone(), )); - let sprite_loader = Arc::new(SpriteLoader::new(device.clone(), queue.clone(), game_file_loader.clone())); + let sprite_loader = Arc::new(SpriteLoader::new(game_file_loader.clone(), texture_loader.clone())); let action_loader = Arc::new(ActionLoader::new(game_file_loader.clone(), audio_engine.clone())); let effect_loader = Arc::new(EffectLoader::new(game_file_loader.clone())); let animation_loader = Arc::new(AnimationLoader::new()); @@ -531,23 +532,30 @@ impl Client { let bounding_box_object_set_buffer = ResourceSetBuffer::default(); #[cfg(feature = "debug")] - let (pathing_texture_mapping, pathing_texture) = - TextureAtlasFactory::create_from_group(texture_loader.clone(), "pathing", false, &[ - "pathing_goal.png", - "pathing_straight.png", - "pathing_diagonal.png", - ]); + let (pathing_texture_mapping, pathing_texture) = TextureAtlasFactory::create_from_group( + texture_loader.clone(), + "pathing", + false, + &["pathing_goal.png", "pathing_straight.png", "pathing_diagonal.png"], + graphics_engine.check_texture_compression_requirements(*texture_compression.get()), + ); #[cfg(feature = "debug")] - let (tile_texture_mapping, tile_texture) = TextureAtlasFactory::create_from_group(texture_loader.clone(), "tile", false, &[ - "tile_0.png", - "tile_1.png", - "tile_2.png", - "tile_3.png", - "tile_4.png", - "tile_5.png", - "tile_6.png", - ]); + let (tile_texture_mapping, tile_texture) = TextureAtlasFactory::create_from_group( + texture_loader.clone(), + "tile", + false, + &[ + "tile_0.png", + "tile_1.png", + "tile_2.png", + "tile_3.png", + "tile_4.png", + "tile_5.png", + "tile_6.png", + ], + graphics_engine.check_texture_compression_requirements(*texture_compression.get()), + ); #[cfg(feature = "debug")] let tile_texture_mapping = Arc::new(tile_texture_mapping); @@ -564,8 +572,11 @@ impl Client { }); time_phase!("load default map", { + let compression = graphics_engine.check_texture_compression_requirements(*texture_compression.get()); + let map = map_loader .load( + compression, DEFAULT_MAP.to_string(), &model_loader, texture_loader.clone(), @@ -632,6 +643,7 @@ impl Client { ssaa, screen_space_anti_aliasing, high_quality_interface, + texture_compression, #[cfg(feature = "debug")] render_settings, mute_on_focus_loss, @@ -856,17 +868,22 @@ impl Client { self.networking_system.connect_to_character_server(login_data, server); self.map = None; - self.entities.clear(); self.particle_holder.clear(); self.effect_holder.clear(); self.point_light_manager.clear(); + self.audio_engine.clear_ambient_sound(); + + self.entities.clear(); + self.audio_engine.play_background_music_track(None); self.interface.close_all_windows_except(&mut self.focus_state); self.async_loader.request_map_load( + self.graphics_engine + .check_texture_compression_requirements(*self.texture_compression.get()), DEFAULT_MAP.to_string(), - TilePosition::new(0, 0), + Some(TilePosition::new(0, 0)), #[cfg(feature = "debug")] self.tile_texture_mapping.clone(), ); @@ -969,6 +986,10 @@ impl Client { self.dialog_system.close_dialog(); self.map = None; + self.particle_holder.clear(); + self.effect_holder.clear(); + self.point_light_manager.clear(); + self.audio_engine.clear_ambient_sound(); } NetworkEvent::CharacterCreated { character_information } => { self.saved_characters.push(character_information); @@ -1059,13 +1080,19 @@ impl Client { } NetworkEvent::ChangeMap(map_name, player_position) => { self.map = None; + self.particle_holder.clear(); + self.effect_holder.clear(); + self.point_light_manager.clear(); + self.audio_engine.clear_ambient_sound(); // Only the player must stay alive between map changes. self.entities.truncate(1); self.async_loader.request_map_load( + self.graphics_engine + .check_texture_compression_requirements(*self.texture_compression.get()), map_name, - player_position, + Some(player_position), #[cfg(feature = "debug")] self.tile_texture_mapping.clone(), ); @@ -1506,6 +1533,7 @@ impl Client { self.shadow_detail.clone_state(), self.shadow_quality.clone_state(), self.high_quality_interface.clone_state(), + self.texture_compression.clone_state(), ), ), UserEvent::OpenAudioSettingsWindow => self.interface.open_window( @@ -1859,13 +1887,12 @@ impl Client { map.set_ambient_sound_sources(&self.audio_engine); self.audio_engine.play_background_music_track(map.background_music_track_name()); - let player_position = Vector2::new(player_position.x as usize, player_position.y as usize); - self.entities[0].set_position(map, player_position, client_tick); - self.player_camera.set_focus_point(self.entities[0].get_position()); + if let Some(player_position) = player_position { + let player_position = Vector2::new(player_position.x as usize, player_position.y as usize); + self.entities[0].set_position(map, player_position, client_tick); + self.player_camera.set_focus_point(self.entities[0].get_position()); + } - self.particle_holder.clear(); - self.effect_holder.clear(); - self.point_light_manager.clear(); self.interface.schedule_render(); let _ = self.networking_system.map_loaded(); } @@ -2426,6 +2453,19 @@ impl Client { update_interface = true; } + if self.texture_compression.consume_changed() { + if let Some(map) = self.map.as_ref() { + self.async_loader.request_map_load( + self.graphics_engine + .check_texture_compression_requirements(*self.texture_compression.get()), + map.get_resource_file().to_string(), + None, + #[cfg(feature = "debug")] + self.tile_texture_mapping.clone(), + ); + } + } + if update_interface { self.interface.schedule_render(); } diff --git a/korangar/src/settings/graphic.rs b/korangar/src/settings/graphic.rs index 02ee2610..a94dce0a 100644 --- a/korangar/src/settings/graphic.rs +++ b/korangar/src/settings/graphic.rs @@ -3,7 +3,9 @@ use korangar_debug::logging::{print_debug, Colorize}; use ron::ser::PrettyConfig; use serde::{Deserialize, Serialize}; -use crate::graphics::{LimitFramerate, Msaa, ScreenSpaceAntiAliasing, ShadowDetail, ShadowQuality, Ssaa, TextureSamplerType}; +use crate::graphics::{ + LimitFramerate, Msaa, ScreenSpaceAntiAliasing, ShadowDetail, ShadowQuality, Ssaa, TextureCompression, TextureSamplerType, +}; #[derive(Serialize, Deserialize)] pub struct GraphicsSettings { @@ -18,6 +20,7 @@ pub struct GraphicsSettings { pub shadow_detail: ShadowDetail, pub shadow_quality: ShadowQuality, pub high_quality_interface: bool, + pub texture_compression: TextureCompression, } impl Default for GraphicsSettings { @@ -34,6 +37,7 @@ impl Default for GraphicsSettings { shadow_detail: ShadowDetail::Medium, shadow_quality: ShadowQuality::Soft, high_quality_interface: true, + texture_compression: TextureCompression::Off, } } } diff --git a/korangar/src/world/map/mod.rs b/korangar/src/world/map/mod.rs index 91801bfb..cd7f0492 100644 --- a/korangar/src/world/map/mod.rs +++ b/korangar/src/world/map/mod.rs @@ -116,6 +116,7 @@ impl MarkerIdentifier { #[derive(new)] pub struct Map { + resource_file: String, width: usize, height: usize, water_settings: Option, @@ -147,6 +148,10 @@ impl Map { self.water_bounds } + pub fn get_resource_file(&self) -> &str { + &self.resource_file + } + pub fn get_world_position(&self, position: Vector2) -> Point3 { let height = average_tile_height(self.get_tile(position)); Point3::new(position.x as f32 * 5.0 + 2.5, height, position.y as f32 * 5.0 + 2.5) diff --git a/korangar_util/src/texture_atlas/offline.rs b/korangar_util/src/texture_atlas/offline.rs index e8772a2f..1206a181 100644 --- a/korangar_util/src/texture_atlas/offline.rs +++ b/korangar_util/src/texture_atlas/offline.rs @@ -9,10 +9,6 @@ use super::AtlasAllocation; use crate::container::{SecondarySimpleSlab, SimpleSlab}; use crate::{create_simple_key, Rectangle}; -/// Factor we used to increase the texture size for inefficiency in -/// the packing algorithm. -const EFFICIENCY_FACTOR: f32 = 1.05; - create_simple_key!(AllocationId, "A key for an allocation"); /// A texture atlas implementation using the MAXRECTS-BSSF (Best Short Side Fit) @@ -161,18 +157,10 @@ impl OfflineTextureAtlas { } else { success = false; - if self.mip_level_count > 1 { - if width <= height { - width *= 2 - } else { - height *= 2 - } + if width <= height { + width *= 2; } else { - let current_area = width * height; - let adjusted_area = (current_area as f32 * EFFICIENCY_FACTOR) as u32; - let side = (adjusted_area as f32).sqrt() as u32; - width = side; - height = side; + height *= 2; } break; @@ -205,27 +193,18 @@ impl OfflineTextureAtlas { fn estimate_initial_size(&self, deferred_allocations: &[(AllocationId, DeferredAllocation)]) -> (u32, u32) { let total_area: u32 = deferred_allocations.iter().map(|r| r.1.padded_size.x * r.1.padded_size.y).sum(); - if self.mip_level_count > 1 { - let mut width = 128; - let mut height = 128; - let mut expand_width = true; - - while (width * height) < total_area { - if expand_width { - width *= 2; - expand_width = false; - } else { - height *= 2; - expand_width = true; - } - } + let mut width = 128; + let mut height = 128; - (width, height) - } else { - let adjusted_area = (total_area as f32 * EFFICIENCY_FACTOR) as u32; - let side = (adjusted_area as f32).sqrt() as u32; - (side, side) + while (width * height) < total_area { + if width <= height { + width *= 2; + } else { + height *= 2; + } } + + (width, height) } fn allocate(&mut self, padded_size: Vector2, original_size: Vector2) -> Option {