mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-21 12:29:18 +02:00
async mesh loading
This commit is contained in:
@@ -11,12 +11,13 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "scene/eu7/eu7_model_prefetch.h"
|
||||
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/MdlMngr.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "scene/eu7/eu7_pack_bench.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
@@ -28,11 +29,29 @@ namespace scene::eu7 {
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t kMaxWarmedTextures { 2048 };
|
||||
constexpr std::size_t kMaxPackMaterialAssignCache { 4096 };
|
||||
|
||||
std::mutex g_pack_mesh_load_mutex;
|
||||
std::unordered_set<std::string> g_session_warmed_textures;
|
||||
std::list<std::string> g_session_warmed_textures_lru;
|
||||
std::unordered_map<std::string, std::list<std::string>::iterator> g_session_warmed_textures_iters;
|
||||
std::unordered_map<std::string, material_data> g_pack_material_assign_cache;
|
||||
|
||||
[[nodiscard]] std::string
|
||||
pack_material_cache_key(
|
||||
std::string const &model_file,
|
||||
std::string const &texture_file ) {
|
||||
return model_file + '\x1e' + texture_file;
|
||||
}
|
||||
|
||||
void
|
||||
evict_pack_material_assign_cache_if_needed() {
|
||||
while( g_pack_material_assign_cache.size() >= kMaxPackMaterialAssignCache ) {
|
||||
if( g_pack_material_assign_cache.empty() ) {
|
||||
break;
|
||||
}
|
||||
g_pack_material_assign_cache.erase( g_pack_material_assign_cache.begin() );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
touch_warmed_texture_lru( std::string const &texture_file ) {
|
||||
@@ -107,12 +126,119 @@ pack_texture_usable( std::string texture_file ) {
|
||||
|
||||
void
|
||||
preload_pack_model_file( std::string const & ) {
|
||||
// Mesh warm runs on the main thread via ensure_pack_mesh_in_session_cache only.
|
||||
// GetModel mutates Global.asCurrentTexturePath and must not run on PACK workers.
|
||||
// GetModel + Init must run on the main thread (Global.asCurrentTexturePath + OpenGL).
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
warm_one_pack_texture( std::string texture_file, std::unordered_set<std::string> &seen ) {
|
||||
pack_texture_path_is_rooted( std::string const &texture_file ) {
|
||||
return texture_file.starts_with( "dynamic/" ) ||
|
||||
texture_file.starts_with( "textures/" ) ||
|
||||
texture_file.starts_with( "scenery/" ) ||
|
||||
texture_file.starts_with( "models/" );
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string
|
||||
pack_model_texture_dir( std::string model_file ) {
|
||||
if( model_file.empty() || model_file == "notload" ) {
|
||||
return {};
|
||||
}
|
||||
replace_slashes( model_file );
|
||||
erase_extension( model_file );
|
||||
auto const slash_pos { model_file.rfind( '/' ) };
|
||||
if( slash_pos == std::string::npos ) {
|
||||
return {};
|
||||
}
|
||||
return model_file.substr( 0, slash_pos + 1 );
|
||||
}
|
||||
|
||||
void
|
||||
append_pack_texture_candidate(
|
||||
std::vector<std::string> &candidates,
|
||||
std::string candidate ) {
|
||||
if( candidate.empty() ) {
|
||||
return;
|
||||
}
|
||||
replace_slashes( candidate );
|
||||
if(
|
||||
std::find( candidates.begin(), candidates.end(), candidate ) ==
|
||||
candidates.end() ) {
|
||||
candidates.emplace_back( std::move( candidate ) );
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<std::string>
|
||||
pack_texture_resolve_candidates(
|
||||
std::string const &model_file,
|
||||
std::string texture_file ) {
|
||||
std::vector<std::string> candidates;
|
||||
candidates.reserve( 4 );
|
||||
if( false == pack_texture_usable( texture_file ) ) {
|
||||
return candidates;
|
||||
}
|
||||
replace_slashes( texture_file );
|
||||
append_pack_texture_candidate( candidates, texture_file );
|
||||
|
||||
auto const model_dir { pack_model_texture_dir( model_file ) };
|
||||
if( false == model_dir.empty() ) {
|
||||
append_pack_texture_candidate( candidates, model_dir + texture_file );
|
||||
}
|
||||
if( false == pack_texture_path_is_rooted( texture_file ) ) {
|
||||
append_pack_texture_candidate( candidates, paths::textures + texture_file );
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
class PackTexturePathScope {
|
||||
public:
|
||||
explicit PackTexturePathScope( std::string const &model_file ) {
|
||||
m_saved_path = Global.asCurrentTexturePath;
|
||||
auto const model_dir { pack_model_texture_dir( model_file ) };
|
||||
if( false == model_dir.empty() && model_file.find( '/' ) != std::string::npos ) {
|
||||
Global.asCurrentTexturePath = model_dir;
|
||||
m_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
~PackTexturePathScope() {
|
||||
if( m_active ) {
|
||||
Global.asCurrentTexturePath = std::move( m_saved_path );
|
||||
}
|
||||
}
|
||||
|
||||
PackTexturePathScope( PackTexturePathScope const & ) = delete;
|
||||
PackTexturePathScope &operator=( PackTexturePathScope const & ) = delete;
|
||||
|
||||
private:
|
||||
std::string m_saved_path;
|
||||
bool m_active { false };
|
||||
};
|
||||
|
||||
[[nodiscard]] bool
|
||||
fetch_pack_texture_material( std::string const &texture_file ) {
|
||||
auto const resolved { TextureTest( ToLower( texture_file ) ) };
|
||||
if( false == resolved.empty() ) {
|
||||
GfxRenderer->Fetch_Material( resolved );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool warmed { false };
|
||||
for( int skinindex { 1 }; skinindex <= 4; ++skinindex ) {
|
||||
auto const multi {
|
||||
TextureTest( ToLower( texture_file + "," + std::to_string( skinindex ) ) ) };
|
||||
if( multi.empty() ) {
|
||||
break;
|
||||
}
|
||||
GfxRenderer->Fetch_Material( multi );
|
||||
warmed = true;
|
||||
}
|
||||
return warmed;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
warm_one_pack_texture(
|
||||
std::string texture_file,
|
||||
std::unordered_set<std::string> &seen,
|
||||
std::string const &model_file = {} ) {
|
||||
if( false == pack_texture_usable( texture_file ) ) {
|
||||
return false;
|
||||
}
|
||||
@@ -124,77 +250,40 @@ warm_one_pack_texture( std::string texture_file, std::unordered_set<std::string>
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const resolved { TextureTest( ToLower( texture_file ) ) };
|
||||
if( resolved.empty() ) {
|
||||
bool warmed { false };
|
||||
for( int skinindex { 1 }; skinindex <= 4; ++skinindex ) {
|
||||
auto const multi {
|
||||
TextureTest( ToLower( texture_file + "," + std::to_string( skinindex ) ) ) };
|
||||
if( multi.empty() ) {
|
||||
break;
|
||||
}
|
||||
GfxRenderer->Fetch_Material( multi );
|
||||
warmed = true;
|
||||
PackTexturePathScope const scope { model_file };
|
||||
for( auto const &candidate : pack_texture_resolve_candidates( model_file, texture_file ) ) {
|
||||
if( fetch_pack_texture_material( candidate ) ) {
|
||||
remember_warmed_texture( texture_file );
|
||||
return true;
|
||||
}
|
||||
if( false == warmed ) {
|
||||
pack_bench_inc( &Eu7PackBench::main_texture_warm_miss );
|
||||
log_pack_texture_fail( texture_file );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
GfxRenderer->Fetch_Material( resolved );
|
||||
}
|
||||
|
||||
remember_warmed_texture( texture_file );
|
||||
pack_bench_inc( &Eu7PackBench::main_texture_warm_miss );
|
||||
log_pack_texture_fail( texture_file );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( false == resolved.empty() ) {
|
||||
for( int skinindex { 1 }; skinindex <= 4; ++skinindex ) {
|
||||
auto const multi {
|
||||
TextureTest( ToLower( texture_file + "," + std::to_string( skinindex ) ) ) };
|
||||
if( multi.empty() ) {
|
||||
break;
|
||||
}
|
||||
GfxRenderer->Fetch_Material( multi );
|
||||
[[nodiscard]] std::string
|
||||
find_pack_model_file_for_texture(
|
||||
Eu7Model const *const models,
|
||||
std::size_t const model_count,
|
||||
std::string texture_file ) {
|
||||
if( models == nullptr || model_count == 0 ) {
|
||||
return {};
|
||||
}
|
||||
replace_slashes( texture_file );
|
||||
for( std::size_t i { 0 }; i < model_count; ++i ) {
|
||||
auto candidate { models[ i ].texture_file };
|
||||
replace_slashes( candidate );
|
||||
if( candidate == texture_file ) {
|
||||
return models[ i ].model_file;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
[[nodiscard]] bool
|
||||
ensure_pack_mesh_in_session_cache(
|
||||
std::string model_file,
|
||||
std::unordered_map<std::string, TModel3d *> &session_cache ) {
|
||||
if( model_file.empty() || model_file == "notload" ) {
|
||||
return false;
|
||||
}
|
||||
replace_slashes( model_file );
|
||||
if( session_cache.contains( model_file ) ) {
|
||||
pack_bench_inc( &Eu7PackBench::stream_mesh_session_hit );
|
||||
return true;
|
||||
}
|
||||
|
||||
auto const was_global_cached { TModelsManager::IsModelCached( model_file ) };
|
||||
TModel3d *mesh { nullptr };
|
||||
{
|
||||
std::lock_guard<std::mutex> lock { g_pack_mesh_load_mutex };
|
||||
mesh = TModelsManager::GetModel( model_file, false, false );
|
||||
}
|
||||
if( was_global_cached ) {
|
||||
pack_bench_inc( &Eu7PackBench::stream_mesh_global_hit );
|
||||
}
|
||||
else {
|
||||
pack_bench_inc( &Eu7PackBench::stream_mesh_disk_load );
|
||||
}
|
||||
if( mesh != nullptr ) {
|
||||
TAnimModel::warm_instanceable_cache( mesh );
|
||||
}
|
||||
session_cache.emplace( model_file, mesh );
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
reset_pack_texture_warm_cache() {
|
||||
g_session_warmed_textures.clear();
|
||||
@@ -221,7 +310,9 @@ warm_pack_texture_paths_main(
|
||||
std::string const *const paths,
|
||||
std::size_t const count,
|
||||
double const budget_ms,
|
||||
std::size_t *const processed_out ) {
|
||||
std::size_t *const processed_out,
|
||||
Eu7Model const *const models,
|
||||
std::size_t const model_count ) {
|
||||
if( paths == nullptr || count == 0 || GfxRenderer == nullptr ) {
|
||||
if( processed_out != nullptr ) {
|
||||
*processed_out = 0;
|
||||
@@ -246,7 +337,10 @@ warm_pack_texture_paths_main(
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( warm_one_pack_texture( paths[ i ], seen ) ) {
|
||||
if( warm_one_pack_texture(
|
||||
paths[ i ],
|
||||
seen,
|
||||
find_pack_model_file_for_texture( models, model_count, paths[ i ] ) ) ) {
|
||||
++warmed;
|
||||
}
|
||||
++processed;
|
||||
@@ -259,7 +353,10 @@ warm_pack_texture_paths_main(
|
||||
}
|
||||
|
||||
std::size_t
|
||||
warm_pack_textures_main( Eu7Model const *const models, std::size_t const count ) {
|
||||
warm_pack_textures_main(
|
||||
Eu7Model const *const models,
|
||||
std::size_t const count,
|
||||
double const budget_ms ) {
|
||||
if( models == nullptr || count == 0 || GfxRenderer == nullptr ) {
|
||||
return 0;
|
||||
}
|
||||
@@ -267,13 +364,88 @@ warm_pack_textures_main( Eu7Model const *const models, std::size_t const count )
|
||||
std::unordered_set<std::string> seen;
|
||||
seen.reserve( std::min( count, std::size_t { 64 } ) );
|
||||
std::size_t warmed { 0 };
|
||||
auto const started { std::chrono::steady_clock::now() };
|
||||
|
||||
for( std::size_t i { 0 }; i < count; ++i ) {
|
||||
if( warm_one_pack_texture( models[ i ].texture_file, seen ) ) {
|
||||
if(
|
||||
budget_ms > 0.0 &&
|
||||
i > 0 ) {
|
||||
auto const elapsed_ms {
|
||||
std::chrono::duration<double, std::milli>(
|
||||
std::chrono::steady_clock::now() - started ).count() };
|
||||
if( elapsed_ms >= budget_ms ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( warm_one_pack_texture(
|
||||
models[ i ].texture_file,
|
||||
seen,
|
||||
models[ i ].model_file ) ) {
|
||||
++warmed;
|
||||
}
|
||||
}
|
||||
return warmed;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
assign_pack_texture(
|
||||
material_data &material,
|
||||
std::string const &model_file,
|
||||
std::string const &texture_file,
|
||||
std::string const &resolved_texture,
|
||||
std::uint32_t const textures_alpha ) {
|
||||
if( false == pack_texture_usable( texture_file ) ) {
|
||||
if( textures_alpha != 0 ) {
|
||||
material.textures_alpha = static_cast<int>( textures_alpha );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto const cache_key {
|
||||
resolved_texture.empty() ?
|
||||
pack_material_cache_key( model_file, texture_file ) :
|
||||
pack_material_cache_key( model_file, resolved_texture ) };
|
||||
auto const cached { g_pack_material_assign_cache.find( cache_key ) };
|
||||
if(
|
||||
cached != g_pack_material_assign_cache.end() &&
|
||||
cached->second.replacable_skins[ 1 ] != null_handle ) {
|
||||
material = cached->second;
|
||||
if( textures_alpha != 0 ) {
|
||||
material.textures_alpha = static_cast<int>( textures_alpha );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if( false == resolved_texture.empty() ) {
|
||||
material = {};
|
||||
material.assign( resolved_texture );
|
||||
if( material.replacable_skins[ 1 ] != null_handle ) {
|
||||
if( textures_alpha != 0 ) {
|
||||
material.textures_alpha = static_cast<int>( textures_alpha );
|
||||
}
|
||||
evict_pack_material_assign_cache_if_needed();
|
||||
g_pack_material_assign_cache.emplace( cache_key, material );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
PackTexturePathScope const scope { model_file };
|
||||
for( auto const &candidate : pack_texture_resolve_candidates( model_file, texture_file ) ) {
|
||||
material = {};
|
||||
material.assign( candidate );
|
||||
if( material.replacable_skins[ 1 ] != null_handle ) {
|
||||
if( textures_alpha != 0 ) {
|
||||
material.textures_alpha = static_cast<int>( textures_alpha );
|
||||
}
|
||||
evict_pack_material_assign_cache_if_needed();
|
||||
g_pack_material_assign_cache.emplace( cache_key, material );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
pack_bench_inc( &Eu7PackBench::main_texture_assign_fail );
|
||||
log_pack_texture_fail( texture_file );
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace scene::eu7
|
||||
|
||||
Reference in New Issue
Block a user