diff --git a/src/Include/xrRender/RenderDeviceRender.h b/src/Include/xrRender/RenderDeviceRender.h index 65d37e87f5..00c2fb2cc1 100644 --- a/src/Include/xrRender/RenderDeviceRender.h +++ b/src/Include/xrRender/RenderDeviceRender.h @@ -38,13 +38,17 @@ class IRenderDeviceRender // Resources control virtual void DeferredLoad(BOOL E) = 0; + virtual void ResourcesPrepareLoad() = 0; virtual void ResourcesDeferredUpload() = 0; virtual void ResourcesDeferredUnload() = 0; virtual void ResourcesGetMemoryUsage(u32& m_base, u32& c_base, u32& m_lmaps, u32& c_lmaps) = 0; virtual void ResourcesDestroyNecessaryTextures() = 0; virtual void ResourcesStoreNecessaryTextures() = 0; virtual void ResourcesDumpMemoryUsage() = 0; - virtual void ResourcesPrefetchCreateTexture(LPCSTR name) = 0; + virtual void ResourcesPrefetchCreateTexture(LPCSTR name, LPCSTR canonical_level_path = nullptr) = 0; + virtual u64 ResourcesBeginLoadGeneration() = 0; + virtual void ResourcesAbortLoadGeneration(u64 generation) = 0; + virtual void ResourcesFinalizeLoadGeneration(u64 generation) = 0; // HWSupport virtual bool HWSupportsShaderYUV2RGB() = 0; diff --git a/src/Include/xrRender/RenderVisual.h b/src/Include/xrRender/RenderVisual.h index 90f764c4d6..1b40859111 100644 --- a/src/Include/xrRender/RenderVisual.h +++ b/src/Include/xrRender/RenderVisual.h @@ -42,6 +42,8 @@ class IRenderVisual virtual void SetShaderTexture(LPCSTR shader, LPCSTR texture) {}; virtual void ResetShaderTexture() {}; + virtual void CommitShaderTexture() {}; + virtual void SuspendShaderTexture() {}; virtual void MarkAsHot(bool is_hot) {}; //--DSR-- HeatVision virtual void MarkAsGlowing(bool is_glowing) {}; //--DSR-- SilencerOverheat virtual void MarkIgnoreOptimization(BOOL value) diff --git a/src/Layers/xrRender/DetailManager.cpp b/src/Layers/xrRender/DetailManager.cpp index 4737e4bec9..728baa086d 100644 --- a/src/Layers/xrRender/DetailManager.cpp +++ b/src/Layers/xrRender/DetailManager.cpp @@ -97,13 +97,23 @@ CDetailManager::CDetailManager() #ifdef DETAIL_RADIUS // KD: variable detail radius - dm_size = dm_current_size; - dm_cache_line = dm_current_cache_line; - dm_cache1_line = dm_current_cache1_line; - dm_cache_size = dm_current_cache_size; - dm_fade = dm_current_fade; - ps_r__Detail_density = ps_current_detail_density; - ps_r__Detail_height = ps_current_detail_height; + // The early level package is prepared while the previous level may still + // render. Do not write shared recipe globals when the requested recipe is + // already active; this keeps the worker-side constructor read-only. + if (dm_size != dm_current_size) + dm_size = dm_current_size; + if (dm_cache_line != dm_current_cache_line) + dm_cache_line = dm_current_cache_line; + if (dm_cache1_line != dm_current_cache1_line) + dm_cache1_line = dm_current_cache1_line; + if (dm_cache_size != dm_current_cache_size) + dm_cache_size = dm_current_cache_size; + if (dm_fade != dm_current_fade) + dm_fade = dm_current_fade; + if (ps_r__Detail_density != ps_current_detail_density) + ps_r__Detail_density = ps_current_detail_density; + if (ps_r__Detail_height != ps_current_detail_height) + ps_r__Detail_height = ps_current_detail_height; cache_level1 = (CacheSlot1**)Memory.mem_alloc(dm_cache1_line * sizeof(CacheSlot1*) #ifdef USE_MEMORY_MONITOR , "CDetailManager::cache_level1" @@ -185,18 +195,46 @@ void dump (CDetailManager::vis_list& lst) } } */ -void CDetailManager::Load() +void CDetailManager::SnapshotSwing(SSwingValue* values) +{ + R_ASSERT(values); + values[0].amp1 = pSettings->r_float("details", "swing_normal_amp1"); + values[0].amp2 = pSettings->r_float("details", "swing_normal_amp2"); + values[0].rot1 = pSettings->r_float("details", "swing_normal_rot1"); + values[0].rot2 = pSettings->r_float("details", "swing_normal_rot2"); + values[0].speed = pSettings->r_float("details", "swing_normal_speed"); + values[1].amp1 = pSettings->r_float("details", "swing_fast_amp1"); + values[1].amp2 = pSettings->r_float("details", "swing_fast_amp2"); + values[1].rot1 = pSettings->r_float("details", "swing_fast_rot1"); + values[1].rot2 = pSettings->r_float("details", "swing_fast_rot2"); + values[1].speed = pSettings->r_float("details", "swing_fast_speed"); +} + +void CDetailManager::Load(bool publish, bool create_shaders, LPCSTR canonical_level_path, + const SSwingValue* swing_values) { // Open file stream - if (!FS.exist("$level$", "level.details")) + xr_string fn; + if (canonical_level_path && canonical_level_path[0]) + { + fn = canonical_level_path; + if (fn.back() != '\\' && fn.back() != '/') + fn += '\\'; + fn += "level.details"; + } + else + { + string_path resolved; + FS.update_path(resolved, "$level$", "level.details"); + fn = resolved; + } + if (!FS.exist(fn.c_str())) { dtFS = NULL; return; } - string_path fn; - FS.update_path(fn, "$level$", "level.details"); - dtFS = FS.r_open(fn); + dtFS = FS.r_open(fn.c_str()); // Header dtFS->r_chunk_safe(0, &dtH, sizeof(dtH)); @@ -210,7 +248,7 @@ void CDetailManager::Load() { CDetail* dt = xr_new(); IReader* S = m_fs->open_chunk(m_id); - dt->Load(S); + dt->Load(S, create_shaders); objects.push_back(dt); S->close(); } @@ -241,36 +279,48 @@ void CDetailManager::Load() bwdithermap(2, dither); // Hardware specific optimizations - if (UseVS()) hw_Load(); + if (UseVS()) hw_Load(create_shaders); else soft_Load(); - // swing desc - // normal - swing_desc[0].amp1 = pSettings->r_float("details", "swing_normal_amp1"); - swing_desc[0].amp2 = pSettings->r_float("details", "swing_normal_amp2"); - swing_desc[0].rot1 = pSettings->r_float("details", "swing_normal_rot1"); - swing_desc[0].rot2 = pSettings->r_float("details", "swing_normal_rot2"); - swing_desc[0].speed = pSettings->r_float("details", "swing_normal_speed"); - // fast - swing_desc[1].amp1 = pSettings->r_float("details", "swing_fast_amp1"); - swing_desc[1].amp2 = pSettings->r_float("details", "swing_fast_amp2"); - swing_desc[1].rot1 = pSettings->r_float("details", "swing_fast_rot1"); - swing_desc[1].rot2 = pSettings->r_float("details", "swing_fast_rot2"); - swing_desc[1].speed = pSettings->r_float("details", "swing_fast_speed"); + if (swing_values) + CopyMemory(swing_desc, swing_values, sizeof(swing_desc)); + else + SnapshotSwing(swing_desc); - if (ps_r2_ls_flags.test(R2FLAG_EXP_MT_CALC)) + if (publish) + Publish(); +} + +void CDetailManager::CommitShaders() +{ + for (CDetail* detail : objects) + detail->CommitShader(); + if (UseVS()) + hw_Load_Shaders(); +} + +void CDetailManager::SuspendShaders() +{ + for (CDetail* detail : objects) + detail->SuspendShader(); +} + +void CDetailManager::Publish() +{ + if (dtFS && ps_r2_ls_flags.test(R2FLAG_EXP_MT_CALC)) { - // MT-details (@front) - Device.seqParallelRender.push_back(xr_make_delegate(this, &CDetailManager::MT_CALC)); + auto callback = xr_make_delegate(this, &CDetailManager::MT_CALC); + if (std::find(Device.seqParallelRender.begin(), Device.seqParallelRender.end(), callback) == + Device.seqParallelRender.end()) + { + Device.seqParallelRender.push_back(callback); + } } } #endif void CDetailManager::Unload() { - auto I = std::find(Device.seqParallelRender.begin(), Device.seqParallelRender.end(), xr_make_delegate(this, &CDetailManager::MT_CALC)); - - if (I != Device.seqParallelRender.end()) - Device.seqParallelRender.erase(I); + Suspend(); if (UseVS()) hw_Unload(); else soft_Unload(); @@ -289,6 +339,61 @@ void CDetailManager::Unload() xr_free(dtSlots); // heap-owned wide slot array (was a VFS alias pre-v4) } +void CDetailManager::Suspend() +{ + auto I = std::find(Device.seqParallelRender.begin(), Device.seqParallelRender.end(), + xr_make_delegate(this, &CDetailManager::MT_CALC)); + if (I != Device.seqParallelRender.end()) + Device.seqParallelRender.erase(I); + xrCriticalSectionGuard guard(m_mt_calc_guard); +} + +void CDetailManager::Resume() +{ + if (!dtFS) + return; + xrCriticalSectionGuard guard(m_mt_calc_guard); + cache_task.clear(); + for (u32 visible = 0; visible < 3; ++visible) + for (auto& model : m_visibles[visible]) + model.clear(); + for (u32 i = 0; i < dm_cache_size; ++i) + { + Slot& slot = cache_pool[i]; + slot.type = stReady; + slot.frame = 0; + slot.vis.hom_frame = 0; + slot.vis.hom_tested = 0; + for (SlotPart& part : slot.G) + for (SlotItemVec& items : part.r_items) + items.clear(); + } + cache_Initialize(); + for (u32 z = 0; z < dm_cache1_line; ++z) + for (u32 x = 0; x < dm_cache1_line; ++x) + { + CacheSlot1& cache_slot = cache_level1[z][x]; + cache_slot.empty = TRUE; + cache_slot.vis.clear(); + for (Slot** slot : cache_slot.slots) + { + cache_slot.vis.box.merge((*slot)->vis.box); + if (!(*slot)->empty) + cache_slot.empty = FALSE; + } + cache_slot.vis.box.getsphere(cache_slot.vis.sphere.P, cache_slot.vis.sphere.R); + } + m_frame_calc = 0; + m_frame_rendered.store(Device.dwFrame, std::memory_order_release); + if (ps_r2_ls_flags.test(R2FLAG_EXP_MT_CALC)) + { + auto I = std::find(Device.seqParallelRender.begin(), Device.seqParallelRender.end(), + xr_make_delegate(this, &CDetailManager::MT_CALC)); + if (I == Device.seqParallelRender.end()) + Device.seqParallelRender.push_back(xr_make_delegate(this, &CDetailManager::MT_CALC)); + } +} + extern ECORE_API float r_ssaDISCARD; extern float ps_r__ssaDISCARD_exp; extern float ps_r__ssaDISCARD_fade_k; diff --git a/src/Layers/xrRender/DetailManager.h b/src/Layers/xrRender/DetailManager.h index cf14153eb0..ab44e6c2fb 100644 --- a/src/Layers/xrRender/DetailManager.h +++ b/src/Layers/xrRender/DetailManager.h @@ -235,7 +235,7 @@ class ECORE_API CDetailManager ref_constant hwc_s_consts; ref_constant hwc_s_xform; ref_constant hwc_s_array; - void hw_Load(); + void hw_Load(bool create_shaders = true); void hw_Load_Geom(); void hw_Load_Shaders(); void hw_Unload(); @@ -263,8 +263,15 @@ class ECORE_API CDetailManager int w2cg_X(int x) { return x - cache_cx + dm_size; } int w2cg_Z(int z) { return cache_cz - dm_size + (dm_cache_line - 1 - z); } - void Load(); + static void SnapshotSwing(SSwingValue* values); + void Load(bool publish = true, bool create_shaders = true, LPCSTR canonical_level_path = nullptr, + const SSwingValue* swing_values = nullptr); + void CommitShaders(); + void SuspendShaders(); + void Publish(); void Unload(); + void Suspend(); + void Resume(); void Render(); /// MT stuff diff --git a/src/Layers/xrRender/DetailManager_VS.cpp b/src/Layers/xrRender/DetailManager_VS.cpp index cae9626547..f99488f68c 100644 --- a/src/Layers/xrRender/DetailManager_VS.cpp +++ b/src/Layers/xrRender/DetailManager_VS.cpp @@ -39,10 +39,11 @@ short QC(float v) return short(t & 0xffff); } -void CDetailManager::hw_Load() +void CDetailManager::hw_Load(bool create_shaders) { hw_Load_Geom(); - hw_Load_Shaders(); + if (create_shaders) + hw_Load_Shaders(); } void CDetailManager::hw_Load_Geom() diff --git a/src/Layers/xrRender/DetailModel.cpp b/src/Layers/xrRender/DetailModel.cpp index 3518bd8b6e..695b424a45 100644 --- a/src/Layers/xrRender/DetailModel.cpp +++ b/src/Layers/xrRender/DetailModel.cpp @@ -79,13 +79,16 @@ void CDetail::transfer(Fmatrix& mXform, fvfVertexOut* vDest, u32 C, u16* iDest, } } -void CDetail::Load(IReader* S) +void CDetail::Load(IReader* S, bool create_shader) { // Shader string256 fnT, fnS; S->r_stringZ(fnS, sizeof(fnS)); S->r_stringZ(fnT, sizeof(fnT)); - shader.create(fnS, fnT); + m_shader_name = fnS; + m_texture_name = fnT; + if (create_shader) + CommitShader(); // Params m_Flags.assign(S->r_u32()); @@ -122,6 +125,17 @@ void CDetail::Load(IReader* S) #endif } +void CDetail::CommitShader() +{ + if (!shader && m_shader_name.size()) + shader.create(m_shader_name.c_str(), m_texture_name.c_str()); +} + +void CDetail::SuspendShader() +{ + shader = nullptr; +} + #ifndef _EDITOR #include "xrstripify.h" diff --git a/src/Layers/xrRender/DetailModel.h b/src/Layers/xrRender/DetailModel.h index d03a3c513d..b783a7c6bb 100644 --- a/src/Layers/xrRender/DetailModel.h +++ b/src/Layers/xrRender/DetailModel.h @@ -6,8 +6,12 @@ class ECORE_API CDetail : public IRender_DetailModel { + shared_str m_shader_name; + shared_str m_texture_name; public: - void Load(IReader* S); + void Load(IReader* S, bool create_shader = true); + void CommitShader(); + void SuspendShader(); void Optimize(); virtual void Unload(); diff --git a/src/Layers/xrRender/FBasicVisual.cpp b/src/Layers/xrRender/FBasicVisual.cpp index 59161c1ed6..934c10cb81 100644 --- a/src/Layers/xrRender/FBasicVisual.cpp +++ b/src/Layers/xrRender/FBasicVisual.cpp @@ -13,6 +13,8 @@ #include "../../xrEngine/fmesh.h" #include "dxRenderDeviceRender.h" +ECORE_API thread_local bool g_defer_visual_shader_creation = false; + ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// @@ -23,10 +25,33 @@ IRender_Mesh::~IRender_Mesh() _RELEASE(p_rm_Indices); } +void IRender_Mesh::DeferGeometry(D3DVERTEXELEMENT9* decl) +{ + R_ASSERT(decl); + u32 index = 0; + do + { + R_ASSERT(index < MAX_FVF_DECL_SIZE); + pending_decl[index] = decl[index]; + } while (pending_decl[index++].Stream != 0xff); + geom_commit_pending = true; +} + +void IRender_Mesh::CommitGeometry() +{ + if (!geom_commit_pending) + return; + rm_geom.create(pending_decl, p_rm_Vertices, p_rm_Indices); + geom_commit_pending = false; +} + dxRender_Visual::dxRender_Visual() { Type = 0; shader = 0; + shader_commit_pending = false; + shader_id_pending = 0; + shader_id_source = 0; vis.clear(); } @@ -45,7 +70,9 @@ void dxRender_Visual::Load(const char* N, IReader* data, u32) dbg_name = N; dbg_id = 1; skinning = Engine.External.GetSkinningMode(); - hud = ::Render->hud_loading; + // Static level visuals are prepared on workers and are always world geometry. + // Avoid reading the owner-only HUD mode concurrently with HUD/model creation. + hud = g_defer_visual_shader_creation ? false : ::Render->hud_loading; // header VERIFY(data); @@ -55,7 +82,14 @@ void dxRender_Visual::Load(const char* N, IReader* data, u32) R_ASSERT2(hdr.format_version==xrOGF_FormatVersion, "Invalid visual version"); Type = hdr.type; //if (hdr.shader_id) shader = ::Render->getShader (hdr.shader_id); - if (hdr.shader_id) shader = ::RImplementation.getShader(hdr.shader_id); + if (hdr.shader_id) + { + shader_id_source = hdr.shader_id; + if (g_defer_visual_shader_creation) + shader_id_pending = hdr.shader_id; + else + shader = ::RImplementation.getShader(hdr.shader_id); + } vis.box.set(hdr.bb.min, hdr.bb.max); vis.sphere.set(hdr.bs.c, hdr.bs.r); } @@ -145,11 +179,41 @@ void dxRender_Visual::SetShaderTexture(LPCSTR s_shader, LPCSTR s_texture) dbg_texture = s_texture; } + if (g_defer_visual_shader_creation) + { + shader_commit_pending = true; + return; + } + shader_commit_pending = true; + CommitShaderTexture(); +} + +void dxRender_Visual::CommitShaderTexture() +{ + if (!shader_commit_pending && !shader_id_pending) + return; + if (shader_id_pending) + { + shader = ::RImplementation.getShader(shader_id_pending); + shader_id_pending = 0; + } + if (!shader_commit_pending) + return; Engine.External.SetSkinningMode(skinning); bool prev_hud = ::Render->hud_loading; ::Render->hud_loading = hud; shader.create(*dbg_shader, *dbg_texture); ::Render->hud_loading = prev_hud; + shader_commit_pending = false; +} + +void dxRender_Visual::SuspendShaderTexture() +{ + shader = nullptr; + if (shader_id_source) + shader_id_pending = shader_id_source; + else if (dbg_shader.size()) + shader_commit_pending = true; } void dxRender_Visual::ResetShaderTexture() @@ -177,4 +241,7 @@ void dxRender_Visual::Copy(dxRender_Visual* pFrom) PCOPY(dbg_texture_def); PCOPY(skinning); PCOPY(hud); + PCOPY(shader_commit_pending); + PCOPY(shader_id_pending); + PCOPY(shader_id_source); } diff --git a/src/Layers/xrRender/FBasicVisual.h b/src/Layers/xrRender/FBasicVisual.h index c6037b2eee..cba32f00d5 100644 --- a/src/Layers/xrRender/FBasicVisual.h +++ b/src/Layers/xrRender/FBasicVisual.h @@ -8,6 +8,8 @@ #define VLOAD_NOVERTICES (1<<0) +extern ECORE_API thread_local bool g_defer_visual_shader_creation; + // The class itself class CKinematicsAnimated; class CKinematics; @@ -28,14 +30,19 @@ struct IRender_Mesh u32 iBase; u32 iCount; u32 dwPrimitives; + D3DVERTEXELEMENT9 pending_decl[MAX_FVF_DECL_SIZE]; + bool geom_commit_pending; IRender_Mesh() { p_rm_Vertices = 0; p_rm_Indices = 0; + geom_commit_pending = false; } virtual ~IRender_Mesh(); + void DeferGeometry(D3DVERTEXELEMENT9* decl); + void CommitGeometry(); private: IRender_Mesh(const IRender_Mesh& other); void operator=(const IRender_Mesh& other); @@ -68,6 +75,9 @@ class ECORE_API dxRender_Visual : public IRenderVisual ref_shader shader; // pipe state, shared s32 skinning; bool hud; + bool shader_commit_pending; + u16 shader_id_pending; + u16 shader_id_source; virtual void Render(float LOD) { @@ -90,6 +100,8 @@ class ECORE_API dxRender_Visual : public IRenderVisual virtual void SetShaderTexture(LPCSTR shader, LPCSTR texture); virtual void ResetShaderTexture(); + virtual void CommitShaderTexture(); + virtual void SuspendShaderTexture(); virtual vis_data& _BCL getVisData() { return vis; } virtual u32 getType() { return Type; } diff --git a/src/Layers/xrRender/FTreeVisual.cpp b/src/Layers/xrRender/FTreeVisual.cpp index 75e53f7a49..96a8e76ae7 100644 --- a/src/Layers/xrRender/FTreeVisual.cpp +++ b/src/Layers/xrRender/FTreeVisual.cpp @@ -87,24 +87,35 @@ void FTreeVisual::Load(const char* N, IReader* data, u32 dwFlags) } // Geom - rm_geom.create(vFormat, p_rm_Vertices, p_rm_Indices); + if (g_defer_visual_shader_creation) + DeferGeometry(vFormat); + else + rm_geom.create(vFormat, p_rm_Vertices, p_rm_Indices); // Get constants - m_xform = "m_xform"; - m_xform_v = "m_xform_v"; - c_consts = "consts"; - c_wave = "wave"; - c_wind = "wind"; - c_c_bias = "c_bias"; - c_c_scale = "c_scale"; - c_c_sun = "c_sun"; - - c_prev_wave = "prev_wave"; - c_prev_wind = "prev_wind"; - - c_c_PrevBendersPos = "benders_prevpos"; - c_c_BendersPos = "benders_pos"; - c_c_BendersSetup = "benders_setup"; + static std::once_flag constants_once; + std::call_once(constants_once, [] + { + m_xform = "m_xform"; + m_xform_v = "m_xform_v"; + c_consts = "consts"; + c_wave = "wave"; + c_wind = "wind"; + c_c_bias = "c_bias"; + c_c_scale = "c_scale"; + c_c_sun = "c_sun"; + c_prev_wave = "prev_wave"; + c_prev_wind = "prev_wind"; + c_c_PrevBendersPos = "benders_prevpos"; + c_c_BendersPos = "benders_pos"; + c_c_BendersSetup = "benders_setup"; + }); +} + +void FTreeVisual::CommitShaderTexture() +{ + CommitGeometry(); + dxRender_Visual::CommitShaderTexture(); } struct FTreeVisual_setup diff --git a/src/Layers/xrRender/FTreeVisual.h b/src/Layers/xrRender/FTreeVisual.h index 07fd1a95dc..6da9eed5ec 100644 --- a/src/Layers/xrRender/FTreeVisual.h +++ b/src/Layers/xrRender/FTreeVisual.h @@ -24,6 +24,7 @@ class FTreeVisual : public dxRender_Visual, public IRender_Mesh virtual void Load(LPCSTR N, IReader* data, u32 dwFlags); virtual void Copy(dxRender_Visual* pFrom); virtual void Release(); + virtual void CommitShaderTexture(); FTreeVisual(void); virtual ~FTreeVisual(void); diff --git a/src/Layers/xrRender/FVisual.cpp b/src/Layers/xrRender/FVisual.cpp index 4cf7491d7f..dd4c0fea1c 100644 --- a/src/Layers/xrRender/FVisual.cpp +++ b/src/Layers/xrRender/FVisual.cpp @@ -101,7 +101,10 @@ void Fvisual::Load(const char* N, IReader* data, u32 dwFlags) m_fast->p_rm_Indices->AddRef(); // geom - m_fast->rm_geom.create(fmt, m_fast->p_rm_Vertices, m_fast->p_rm_Indices); + if (g_defer_visual_shader_creation) + m_fast->DeferGeometry(fmt); + else + m_fast->rm_geom.create(fmt, m_fast->p_rm_Vertices, m_fast->p_rm_Indices); } #endif // (RENDER==R_R2) || (RENDER==R_R3) || (RENDER==R_R4) } @@ -206,7 +209,20 @@ void Fvisual::Load(const char* N, IReader* data, u32 dwFlags) if (dwFlags & VLOAD_NOVERTICES) return; else - rm_geom.create(vFormat, p_rm_Vertices, p_rm_Indices); + { + if (g_defer_visual_shader_creation) + DeferGeometry(vFormat); + else + rm_geom.create(vFormat, p_rm_Vertices, p_rm_Indices); + } +} + +void Fvisual::CommitShaderTexture() +{ + CommitGeometry(); + if (m_fast) + m_fast->CommitGeometry(); + dxRender_Visual::CommitShaderTexture(); } void Fvisual::Render(float) diff --git a/src/Layers/xrRender/FVisual.h b/src/Layers/xrRender/FVisual.h index 3ed8991f4c..513417a33c 100644 --- a/src/Layers/xrRender/FVisual.h +++ b/src/Layers/xrRender/FVisual.h @@ -20,6 +20,7 @@ class Fvisual : public dxRender_Visual, public IRender_Mesh virtual void Load(LPCSTR N, IReader* data, u32 dwFlags); virtual void Copy(dxRender_Visual* pFrom); virtual void Release(); + virtual void CommitShaderTexture(); Fvisual(); virtual ~Fvisual(); diff --git a/src/Layers/xrRender/HOM.cpp b/src/Layers/xrRender/HOM.cpp index ea71da4eb6..0fbc878a5c 100644 --- a/src/Layers/xrRender/HOM.cpp +++ b/src/Layers/xrRender/HOM.cpp @@ -58,6 +58,12 @@ CHOM::~CHOM() #endif } +CHOM::StaticData::~StaticData() +{ + xr_delete(model); + xr_free(tris); +} + #pragma pack(push,4) struct HOM_poly { @@ -77,18 +83,36 @@ IC float Area(Fvector& v0, Fvector& v1, Fvector& v2) } void CHOM::Load() +{ + StaticData data; + Prepare(data); + Resume(data); +} + +void CHOM::Prepare(StaticData& data) { // Find and open file string_path fName; FS.update_path(fName, "$level$", "level.hom"); - if (!FS.exist(fName)) + Prepare(fName, data); +} + +void CHOM::Prepare(LPCSTR canonical_level_path, StaticData& data) +{ + R_ASSERT(!data.model && !data.tris); + xr_string fName = canonical_level_path ? canonical_level_path : ""; + if (!fName.empty() && fName.back() != '\\' && fName.back() != '/') + fName += '\\'; + if (fName.size() < 9 || xr_strcmp(fName.c_str() + fName.size() - 9, "level.hom")) + fName += "level.hom"; + if (!FS.exist(fName.c_str())) { - Msg(" WARNING: Occlusion map '%s' not found.", fName); + Msg(" WARNING: Occlusion map '%s' not found.", fName.c_str()); return; } - Msg("* Loading HOM: %s", fName); + Msg("* Loading HOM: %s", fName.c_str()); - IReader* fs = FS.r_open(fName); + IReader* fs = FS.r_open(fName.c_str()); IReader* S = fs->open_chunk(1); // Load tris and merge them @@ -105,19 +129,19 @@ void CHOM::Load() CL.calc_adjacency(adjacency); // Create RASTER-triangles - m_pTris = xr_alloc(u32(CL.getTS())); + data.tris = xr_alloc(u32(CL.getTS())); for (u32 it = 0; it < CL.getTS(); it++) { CDB::TRI& clT = CL.getT()[it]; - occTri& rT = m_pTris[it]; + occTri& rT = data.tris[it]; Fvector& v0 = CL.getV()[clT.verts[0]]; Fvector& v1 = CL.getV()[clT.verts[1]]; Fvector& v2 = CL.getV()[clT.verts[2]]; - rT.adjacent[0] = (0xffffffff == adjacency[3 * it + 0]) ? ((occTri*)(-1)) : (m_pTris + adjacency[3 * it + 0]); - rT.adjacent[1] = (0xffffffff == adjacency[3 * it + 1]) ? ((occTri*)(-1)) : (m_pTris + adjacency[3 * it + 1]); - rT.adjacent[2] = (0xffffffff == adjacency[3 * it + 2]) ? ((occTri*)(-1)) : (m_pTris + adjacency[3 * it + 2]); + rT.adjacent[0] = (0xffffffff == adjacency[3 * it + 0]) ? ((occTri*)(-1)) : (data.tris + adjacency[3 * it + 0]); + rT.adjacent[1] = (0xffffffff == adjacency[3 * it + 1]) ? ((occTri*)(-1)) : (data.tris + adjacency[3 * it + 1]); + rT.adjacent[2] = (0xffffffff == adjacency[3 * it + 2]) ? ((occTri*)(-1)) : (data.tris + adjacency[3 * it + 2]); rT.flags = clT.dummy; rT.area = Area(v0, v1, v2); @@ -132,21 +156,16 @@ void CHOM::Load() } // Create AABB-tree - m_pModel = xr_new(); - m_pModel->build(CL.getV(), int(CL.getVS()), CL.getT(), int(CL.getTS())); - bEnabled = TRUE; + data.model = xr_new(); + data.model->build(CL.getV(), int(CL.getVS()), CL.getT(), int(CL.getTS())); + data.enabled = TRUE; S->close(); FS.r_close(fs); - - if (ps_r2_ls_flags.test(R2FLAG_EXP_MT_CALC)) - { - // MT-HOM (@front) - Device.seqParallelRender.push_back(xr_make_delegate(this, &CHOM::MT_RENDER)); - } } void CHOM::Unload() { + xrCriticalSectionGuard guard(m_mt_render_guard); xr_delete(m_pModel); xr_free(m_pTris); bEnabled = FALSE; @@ -156,6 +175,38 @@ void CHOM::Unload() Device.seqParallelRender.erase(I); } +void CHOM::Suspend(StaticData& data) +{ + xrCriticalSectionGuard guard(m_mt_render_guard); + auto callback = xr_make_delegate(this, &CHOM::MT_RENDER); + auto iterator = std::find(Device.seqParallelRender.begin(), Device.seqParallelRender.end(), callback); + if (iterator != Device.seqParallelRender.end()) + Device.seqParallelRender.erase(iterator); + + R_ASSERT(!data.model && !data.tris); + data.model = m_pModel; + data.tris = m_pTris; + data.enabled = bEnabled; + m_pModel = nullptr; + m_pTris = nullptr; + bEnabled = FALSE; +} + +void CHOM::Resume(StaticData& data) +{ + xrCriticalSectionGuard guard(m_mt_render_guard); + R_ASSERT(!m_pModel && !m_pTris); + m_pModel = data.model; + m_pTris = data.tris; + bEnabled = data.enabled; + data.model = nullptr; + data.tris = nullptr; + data.enabled = FALSE; + MT_frame_rendered.store(0, std::memory_order_release); + if (m_pModel && ps_r2_ls_flags.test(R2FLAG_EXP_MT_CALC)) + Device.seqParallelRender.push_back(xr_make_delegate(this, &CHOM::MT_RENDER)); +} + class pred_fb { public: diff --git a/src/Layers/xrRender/HOM.h b/src/Layers/xrRender/HOM.h index 863a806035..79968c8f7c 100644 --- a/src/Layers/xrRender/HOM.h +++ b/src/Layers/xrRender/HOM.h @@ -29,8 +29,20 @@ class CHOM void Render_DB(CFrustum& base); public: + struct StaticData + { + CDB::MODEL* model = nullptr; + occTri* tris = nullptr; + BOOL enabled = FALSE; + ~StaticData(); + }; + void Load(); + void Prepare(StaticData& data); + void Prepare(LPCSTR canonical_level_path, StaticData& data); void Unload(); + void Suspend(StaticData& data); + void Resume(StaticData& data); void Render(CFrustum& base); void Render_ZB(); // void Debug (); diff --git a/src/Layers/xrRender/Light_DB.cpp b/src/Layers/xrRender/Light_DB.cpp index e15f39fdb6..0ec749d0d4 100644 --- a/src/Layers/xrRender/Light_DB.cpp +++ b/src/Layers/xrRender/Light_DB.cpp @@ -7,26 +7,33 @@ #include "light_db.h" CLight_DB::CLight_DB() + : rain_light(nullptr), m_prepared(false) { } CLight_DB::~CLight_DB() { + if (m_prepared || rain_light || sun_original || sun_adapted || !v_static.empty() || !v_hemi.empty()) + Unload(); } void CLight_DB::Load(IReader* fs) { - IReader* F = 0; + IReader* reader = fs->open_chunk(fsL_LIGHT_DYNAMIC); + R_ASSERT(reader); + LoadDynamic(*reader, true); + reader->close(); +} +void CLight_DB::LoadDynamic(IReader& reader, bool publish) +{ // Lights itself sun_original = NULL; sun_adapted = NULL; - rain_light = xr_new(); + rain_light = xr_new(publish); rain_light->set_type(IRender_Light::DIRECT); { - F = fs->open_chunk(fsL_LIGHT_DYNAMIC); - - u32 size = F->length(); + u32 size = reader.length(); u32 element = sizeof(Flight) + 4; u32 count = size / element; VERIFY(count*element == size); @@ -34,7 +41,7 @@ void CLight_DB::Load(IReader* fs) for (u32 i = 0; i < count; i++) { Flight Ldata; - light* L = Create(); + light* L = Create(publish); L->flags.bStatic = true; L->set_type(IRender_Light::POINT); @@ -44,8 +51,8 @@ void CLight_DB::Load(IReader* fs) L->set_shadow(true); #endif u32 controller = 0; - F->r(&controller, 4); - F->r(&Ldata, sizeof(Flight)); + reader.r(&controller, 4); + reader.r(&Ldata, sizeof(Flight)); if (Ldata.type == D3DLIGHT_DIRECTIONAL) { Fvector tmp_R; @@ -58,7 +65,7 @@ void CLight_DB::Load(IReader* fs) L->set_rotation(Ldata.direction, tmp_R); // copy to env-sun - sun_adapted = L = Create(); + sun_adapted = L = Create(publish); L->flags.bStatic = true; L->set_type(IRender_Light::DIRECT); L->set_shadow(true); @@ -76,12 +83,11 @@ void CLight_DB::Load(IReader* fs) L->set_rotation(tmp_D, tmp_R); L->set_range(Ldata.range); L->set_color(Ldata.diffuse); - L->set_active(true); + if (publish) + L->set_active(true); // R_ASSERT (L->spatial.sector ); } } - - F->close(); } R_ASSERT2(sun_original && sun_adapted, "Where is sun?"); @@ -110,70 +116,144 @@ void CLight_DB::LoadHemi() if (FS.exist(fn_game, "$level$", "build.lights")) { IReader* F = FS.r_open(fn_game); - + IReader* chunk = F->open_chunk(1); //Hemispheric light chunk + if (chunk) { - IReader* chunk = F->open_chunk(1); //Hemispheric light chunk - - if (chunk) - { - u32 size = chunk->length(); - u32 element = sizeof(R_Light); - u32 count = size / element; - VERIFY(count*element == size); - v_hemi.reserve(count); - for (u32 i = 0; i < count; i++) - { - R_Light Ldata; - - chunk->r(&Ldata, sizeof(R_Light)); - - if (Ldata.type == D3DLIGHT_POINT) - //if (Ldata.type!=0) - { - light* L = Create(); - L->flags.bStatic = true; - L->set_type(IRender_Light::POINT); + LoadHemi(*chunk, true); + chunk->close(); + } + FS.r_close(F); + } +} - Fvector tmp_D, tmp_R; - tmp_D.set(0, 0, -1); // forward - tmp_R.set(1, 0, 0); // right +void CLight_DB::LoadHemi(IReader& reader, bool publish) +{ + u32 size = reader.length(); + u32 element = sizeof(R_Light); + u32 count = size / element; + VERIFY(count*element == size); + v_hemi.reserve(count); + for (u32 i = 0; i < count; i++) + { + R_Light Ldata; + reader.r(&Ldata, sizeof(R_Light)); + if (Ldata.type != D3DLIGHT_POINT) + continue; - // point - v_hemi.push_back(L); - L->set_position(Ldata.position); - L->set_rotation(tmp_D, tmp_R); - L->set_range(Ldata.range); - L->set_color(Ldata.diffuse.x, Ldata.diffuse.y, Ldata.diffuse.z); - L->set_active(true); - L->set_attenuation_params(Ldata.attenuation0, Ldata.attenuation1, Ldata.attenuation2, - Ldata.falloff); - L->SpatialComponent->spatial.type = STYPE_LIGHTSOURCEHEMI; - // R_ASSERT (L->spatial.sector ); - } - } + light* L = Create(publish); + L->flags.bStatic = true; + L->set_type(IRender_Light::POINT); + Fvector tmp_D, tmp_R; + tmp_D.set(0, 0, -1); + tmp_R.set(1, 0, 0); + v_hemi.push_back(L); + L->set_position(Ldata.position); + L->set_rotation(tmp_D, tmp_R); + L->set_range(Ldata.range); + L->set_color(Ldata.diffuse.x, Ldata.diffuse.y, Ldata.diffuse.z); + if (publish) + L->set_active(true); + L->set_attenuation_params(Ldata.attenuation0, Ldata.attenuation1, Ldata.attenuation2, Ldata.falloff); + L->SpatialComponent->spatial.type = STYPE_LIGHTSOURCEHEMI; + } +} +#endif - chunk->close(); - } - } +void CLight_DB::LoadPrepared(const xr_vector& dynamic, const xr_vector& hemi) +{ + Prepare(dynamic, hemi); + CommitPrepared(); +} - FS.r_close(F); +void CLight_DB::Prepare(const xr_vector& dynamic, const xr_vector& hemi) +{ + R_ASSERT(!dynamic.empty()); + R_ASSERT(!m_prepared && v_static.empty() && v_hemi.empty() && !sun_original && !sun_adapted && !rain_light); + m_prepared = true; + IReader dynamic_reader(const_cast(dynamic.data()), static_cast(dynamic.size())); + LoadDynamic(dynamic_reader, false); +#if RENDER != R_R1 + if (!hemi.empty()) + { + IReader hemi_reader(const_cast(hemi.data()), static_cast(hemi.size())); + LoadHemi(hemi_reader, false); } +#endif } + +void CLight_DB::CommitPrepared() +{ + R_ASSERT(m_prepared && rain_light && sun_original && sun_adapted); + rain_light->publish_for_render(); + ((light*)sun_original._get())->publish_for_render(); + ((light*)sun_adapted._get())->publish_for_render(); + for (ref_light& item : v_static) + ((light*)item._get())->publish_for_render(); + for (ref_light& item : v_hemi) + ((light*)item._get())->publish_for_render(); + Resume(); + m_prepared = false; +} + +void CLight_DB::PrepareForCache() +{ + R_ASSERT(!m_prepared && rain_light && sun_original && sun_adapted); + m_prepared = true; +#if (RENDER==R_R2) || (RENDER==R_R3) || (RENDER==R_R4) + RImplementation.LP_normal.clear(); + RImplementation.LP_pending.clear(); #endif + rain_light->reset_for_cache(); + ((light*)sun_original._get())->reset_for_cache(); + ((light*)sun_adapted._get())->reset_for_cache(); + for (ref_light& item : v_static) + ((light*)item._get())->reset_for_cache(); + for (ref_light& item : v_hemi) + ((light*)item._get())->reset_for_cache(); +} void CLight_DB::Unload() { + Suspend(); v_static.clear(); v_hemi.clear(); sun_original.destroy(); sun_adapted.destroy(); - rain_light->destroy(false); + if (rain_light) + rain_light->destroy(false); rain_light = nullptr; + m_prepared = false; +} + +void CLight_DB::Suspend() +{ + for (ref_light& item : v_static) + ((light*)item._get())->set_active(false); + for (ref_light& item : v_hemi) + ((light*)item._get())->set_active(false); +} + +void CLight_DB::Resume() +{ + for (ref_light& item : v_static) + ((light*)item._get())->set_active(true); + for (ref_light& item : v_hemi) + ((light*)item._get())->set_active(true); +} + +void CLight_DB::Swap(CLight_DB& other) +{ + v_static.swap(other.v_static); + v_hemi.swap(other.v_hemi); + std::swap(sun_original, other.sun_original); + std::swap(sun_adapted, other.sun_adapted); + std::swap(rain_light, other.rain_light); + std::swap(m_prepared, other.m_prepared); } -light* CLight_DB::Create() +light* CLight_DB::Create(bool publish) { - light* L = xr_new(); + light* L = xr_new(publish); L->flags.bStatic = false; L->flags.bActive = false; L->flags.bShadow = true; diff --git a/src/Layers/xrRender/Light_DB.h b/src/Layers/xrRender/Light_DB.h index 3981ebd25d..cdeb3ae389 100644 --- a/src/Layers/xrRender/Light_DB.h +++ b/src/Layers/xrRender/Light_DB.h @@ -8,6 +8,11 @@ class CLight_DB private: xr_vector v_static; xr_vector v_hemi; + bool m_prepared; + void LoadDynamic(IReader& reader, bool publish); +#if RENDER != R_R1 + void LoadHemi(IReader& reader, bool publish); +#endif public: ref_light sun_original; ref_light sun_adapted; @@ -16,12 +21,19 @@ class CLight_DB void add_light(light* L); void Load(IReader* fs); + void LoadPrepared(const xr_vector& dynamic, const xr_vector& hemi); + void Prepare(const xr_vector& dynamic, const xr_vector& hemi); + void CommitPrepared(); + void PrepareForCache(); #if RENDER != R_R1 void LoadHemi (); #endif void Unload(); + void Suspend(); + void Resume(); + void Swap(CLight_DB& other); - light* Create(); + light* Create(bool publish = true); void Update(); CLight_DB(); diff --git a/src/Layers/xrRender/ModelPool.cpp b/src/Layers/xrRender/ModelPool.cpp index 4d7c10d4b5..79313791ba 100644 --- a/src/Layers/xrRender/ModelPool.cpp +++ b/src/Layers/xrRender/ModelPool.cpp @@ -27,6 +27,132 @@ #include "IGame_Persistent.h" #endif +namespace +{ +xr_string NormalizeModelName(LPCSTR source) +{ + if (!source || !source[0]) + return {}; + string_path name; + xr_strcpy(name, source); + xr_strlwr(name); + for (LPSTR cursor = name; *cursor; ++cursor) + if (*cursor == '/') + *cursor = '\\'; + LPSTR extension = strext(name); + if (extension && !xr_strcmp(extension, ".ogf")) + *extension = 0; + return name; +} + +void AppendTextureList(LPCSTR source, xr_vector& textures) +{ + for (int index = 0, count = _GetItemCount(source, ','); index < count; ++index) + { + string_path texture; + _GetItem(source, index, texture, ','); + xr_strlwr(texture); + for (LPSTR cursor = texture; *cursor; ++cursor) + if (*cursor == '/') + *cursor = '\\'; + if (texture[0] && xr_strcmp(texture, "null") && xr_strcmp(texture, "$null")) + textures.emplace_back(texture); + } +} + +bool ResolveModelFile(LPCSTR source, LPCSTR canonical_level_path, xr_string& resolved) +{ + const xr_string model = NormalizeModelName(source); + if (model.empty()) + return false; + string_path file_name; + xr_strcpy(file_name, model.c_str()); + if (!strext(file_name)) + xr_strcat(file_name, ".ogf"); + if (canonical_level_path && canonical_level_path[0]) + { + resolved = canonical_level_path; + if (resolved.back() != '\\' && resolved.back() != '/') + resolved += '\\'; + resolved += file_name; + if (FS.exist(resolved.c_str())) + return true; + } + string_path mesh_path; + if (!FS.exist(mesh_path, "$game_meshes$", file_name)) + return false; + resolved = mesh_path; + return true; +} + +void CollectModelTextures(LPCSTR source, LPCSTR canonical_level_path, xr_vector& textures, + xr_set& visited); + +void CollectModelReaderTextures(IReader& data, LPCSTR canonical_level_path, xr_vector& textures, + xr_set& visited) +{ + if (data.find_chunk(OGF_TEXTURE)) + { + string256 texture_list; + string256 shader; + data.r_stringZ(texture_list, sizeof(texture_list)); + data.r_stringZ(shader, sizeof(shader)); + AppendTextureList(texture_list, textures); + } + if (IReader* lod = data.open_chunk(OGF_S_LODS)) + { + string_path lod_name; + lod->r_string(lod_name, sizeof(lod_name)); + lod->close(); + CollectModelTextures(lod_name, canonical_level_path, textures, visited); + } + IReader* children = data.open_chunk(OGF_CHILDREN); + if (!children) + return; + for (u32 index = 0;; ++index) + { + IReader* child = children->open_chunk(index); + if (!child) + break; + CollectModelReaderTextures(*child, canonical_level_path, textures, visited); + child->close(); + } + children->close(); +} + +void CollectModelTextures(LPCSTR source, LPCSTR canonical_level_path, xr_vector& textures, + xr_set& visited) +{ + const xr_string model = NormalizeModelName(source); + if (model.empty() || !visited.insert(model).second) + return; + xr_string path; + if (!ResolveModelFile(model.c_str(), canonical_level_path, path)) + return; + IReader* reader = FS.r_open(path.c_str()); + if (!reader) + return; + CollectModelReaderTextures(*reader, canonical_level_path, textures, visited); + FS.r_close(reader); +} +} + +CModelPool::ModelBlueprint::ModelBlueprint() + : completed(CreateEvent(nullptr, TRUE, FALSE, nullptr)), preparedVisual(nullptr), found(false) +{ + R_ASSERT(completed); +} + +CModelPool::ModelBlueprint::~ModelBlueprint() +{ + if (preparedVisual) + { + preparedVisual->Release(); + xr_delete(preparedVisual); + } + CloseHandle(completed); +} + dxRender_Visual* CModelPool::Instance_Create(u32 type) { dxRender_Visual* V = NULL; @@ -202,6 +328,8 @@ dxRender_Visual* CModelPool::Instance_Register(LPCSTR N, dxRender_Visual* V) void CModelPool::Destroy() { + InvalidateBlueprints(); + // Pool Pool.clear(); @@ -250,6 +378,12 @@ CModelPool::~CModelPool() xr_delete(g_pMotionsContainer); } +void CModelPool::InvalidateBlueprints() +{ + xrCriticalSectionGuard guard(modelBlueprintLock); + modelBlueprints.clear(); +} + dxRender_Visual* CModelPool::Instance_Find(LPCSTR N) { shared_str S(N); @@ -493,6 +627,197 @@ void CModelPool::Prefetch_One(LPCSTR N, bool assert) Delete(V,FALSE); } +xr_shared_ptr CModelPool::PrepareBlueprint(LPCSTR name, LPCSTR canonical_level_path) +{ + const xr_string normalized = NormalizeModelName(name); + xr_string resolved_path; + const bool resolved = ResolveModelFile(name, canonical_level_path, resolved_path); + xr_string key = normalized; + key += '\n'; + key += resolved ? resolved_path : (canonical_level_path ? canonical_level_path : ""); + std::transform(key.begin(), key.end(), key.begin(), [](char value) + { + return value == '/' ? '\\' : char(tolower(u8(value))); + }); + if (resolved) + if (const CLocatorAPI::file* file = FS.exist(resolved_path.c_str())) + { + string128 identity; + xr_sprintf(identity, "\n%08x:%08x:%08x:%08x", file->crc, file->size_real, + file->size_compressed, file->modif); + key += identity; + } + + xr_shared_ptr blueprint; + bool producer = false; + { + xrCriticalSectionGuard guard(modelBlueprintLock); + auto found = modelBlueprints.find(key); + if (found != modelBlueprints.end()) + blueprint = found->second; + else + { + blueprint = xr_make_shared(); + modelBlueprints.emplace(std::move(key), blueprint); + producer = true; + } + } + + if (producer) + { + try + { + if (resolved) + { + IReader* source = FS.r_open(resolved_path.c_str()); + if (source) + { + int size = 0; + try + { + size = source->length(); + R_ASSERT(size > 0); + blueprint->data.resize(size); + CopyMemory(blueprint->data.data(), source->pointer(), size); + } + catch (...) + { + FS.r_close(source); + throw; + } + FS.r_close(source); + blueprint->found = true; + + xr_set visited; + visited.insert(normalized); + IReader texture_reader(blueprint->data.data(), size); + CollectModelReaderTextures(texture_reader, canonical_level_path, blueprint->textures, visited); + +#if RENDER == R_R4 + IReader header_reader(blueprint->data.data(), size); + ogf_header header; + R_ASSERT(header_reader.r_chunk_safe(OGF_HEADER, &header, sizeof(header))); + IReader container_reader(blueprint->data.data(), size); + const bool references_level_geometry = container_reader.find_chunk(OGF_GCONTAINER) || + container_reader.find_chunk(OGF_VCONTAINER) || container_reader.find_chunk(OGF_ICONTAINER) || + container_reader.find_chunk(OGF_FASTPATH); + IReader local_geometry_reader(blueprint->data.data(), size); + const bool has_local_geometry = local_geometry_reader.find_chunk(OGF_VERTICES) && + local_geometry_reader.find_chunk(OGF_INDICES); + if (!references_level_geometry && has_local_geometry && + (header.type == MT_NORMAL || header.type == MT_PROGRESSIVE)) + { + blueprint->preparedVisual = Instance_Create(header.type); + const bool previous_defer = g_defer_visual_shader_creation; + g_defer_visual_shader_creation = true; + try + { + IReader visual_reader(blueprint->data.data(), size); + blueprint->preparedVisual->Load(normalized.c_str(), &visual_reader, 0); + } + catch (...) + { + g_defer_visual_shader_creation = previous_defer; + throw; + } + g_defer_visual_shader_creation = previous_defer; + } +#endif + } + } + std::sort(blueprint->textures.begin(), blueprint->textures.end()); + blueprint->textures.erase(std::unique(blueprint->textures.begin(), blueprint->textures.end()), + blueprint->textures.end()); + } + catch (...) + { + blueprint->failure = std::current_exception(); + SetEvent(blueprint->completed); + throw; + } + SetEvent(blueprint->completed); + } + else + { + WaitForSingleObject(blueprint->completed, INFINITE); + if (blueprint->failure) + std::rethrow_exception(blueprint->failure); + } + return blueprint; +} + +void CModelPool::CollectTextures(LPCSTR name, LPCSTR canonical_level_path, xr_vector& textures) +{ + xr_shared_ptr blueprint = PrepareBlueprint(name, canonical_level_path); + textures.insert(textures.end(), blueprint->textures.begin(), blueprint->textures.end()); + std::sort(textures.begin(), textures.end()); + textures.erase(std::unique(textures.begin(), textures.end()), textures.end()); +} + +bool CModelPool::PrefetchPrepared(LPCSTR name, LPCSTR canonical_level_path, bool assert) +{ + xr_shared_ptr blueprint = PrepareBlueprint(name, canonical_level_path); + if (!blueprint->found) + { + if (assert) + Prefetch_One(name, true); + return false; + } + + xrCriticalSectionGuard guard(blueprint->commitLock); + const xr_string normalized = NormalizeModelName(name); + dxRender_Visual* prepared = blueprint->preparedVisual; + blueprint->preparedVisual = nullptr; + if (Instance_Find(normalized.c_str())) + { + if (prepared) + { + prepared->Release(); + xr_delete(prepared); + } + } + else + { + dxRender_Visual* base = prepared; + try + { + if (base) + base->CommitShaderTexture(); + else + { + IReader data(blueprint->data.data(), static_cast(blueprint->data.size())); + const BOOL previous_allow_children_duplicate = bAllowChildrenDuplicate; + bAllowChildrenDuplicate = FALSE; + try + { + base = Instance_Load(normalized.c_str(), &data, FALSE); + } + catch (...) + { + bAllowChildrenDuplicate = previous_allow_children_duplicate; + throw; + } + bAllowChildrenDuplicate = previous_allow_children_duplicate; + } + g_pGamePersistent->RegisterModel(base); + Instance_Register(normalized.c_str(), base); + base = nullptr; + } + catch (...) + { + if (base) + { + base->Release(); + xr_delete(base); + } + throw; + } + } + + Prefetch_One(normalized.c_str(), assert); + return true; +} + bool CModelPool::Exists(LPCSTR N) { string_path low_name; diff --git a/src/Layers/xrRender/ModelPool.h b/src/Layers/xrRender/ModelPool.h index b8de6ae108..dd29aecf3d 100644 --- a/src/Layers/xrRender/ModelPool.h +++ b/src/Layers/xrRender/ModelPool.h @@ -57,6 +57,22 @@ class ECORE_API CModelPool BOOL bAllowChildrenDuplicate; xrCriticalSection deffered_del_lock; xrSRWLock ModelsLock; + struct ModelBlueprint + { + HANDLE completed; + xrCriticalSection commitLock; + xr_vector textures; + xr_vector data; + dxRender_Visual* preparedVisual; + bool found; + std::exception_ptr failure; + + ModelBlueprint(); + ~ModelBlueprint(); + }; + xrCriticalSection modelBlueprintLock; + xr_map> modelBlueprints; + xr_shared_ptr PrepareBlueprint(LPCSTR name, LPCSTR canonical_level_path); void Destroy(); public: @@ -84,6 +100,9 @@ class ECORE_API CModelPool void Prefetch(); void Prefetch_One(LPCSTR N, bool assert = true); + void CollectTextures(LPCSTR name, LPCSTR canonical_level_path, xr_vector& textures); + bool PrefetchPrepared(LPCSTR name, LPCSTR canonical_level_path, bool assert = true); + void InvalidateBlueprints(); bool Exists(LPCSTR N); void ClearPool(BOOL b_complete); diff --git a/src/Layers/xrRender/PSLibrary.cpp b/src/Layers/xrRender/PSLibrary.cpp index 566d8a377b..eb59f80549 100644 --- a/src/Layers/xrRender/PSLibrary.cpp +++ b/src/Layers/xrRender/PSLibrary.cpp @@ -7,6 +7,7 @@ #include "PSLibrary.h" #include "ParticleEffect.h" #include "ParticleGroup.h" +#include "../../xrCore/_thread_types.h" #ifdef _EDITOR # include "ParticleEffectActions.h" @@ -198,55 +199,78 @@ bool CPSLibrary::Load2() } -bool CPSLibrary::Load(const char* nm) +bool CPSLibrary::LoadDefinitions(const char* nm) { + CTimer startupTimer; + startupTimer.Start(); FS_FileSet files; string_path _path; FS.update_path(_path, "$game_particles$", ""); FS.file_list(files, _path, FS_ListFiles, "*.pe,*.pg"); - FS_FileSet::iterator it = files.begin(); - FS_FileSet::iterator it_e = files.end(); + struct PreparedParticle + { + xr_string file; + shared_str name; + bool effect = false; + xr_unique_ptr effectDefinition; + xr_unique_ptr groupDefinition; + }; + + xr_vector prepared(files.size()); + u32 sourceIndex = 0; + for (const FS_File& file : files) + { + string_path path; + string_path name; + string_path extension; + _splitpath(file.name.c_str(), nullptr, path, name, extension); + + PreparedParticle& result = prepared[sourceIndex++]; + result.file = file.name.c_str(); + result.name.printf("%s%s", path, name); + result.effect = 0 == stricmp(extension, ".pe"); + R_ASSERT(result.effect || 0 == stricmp(extension, ".pg")); + } - string_path p_path, p_name, p_ext; - for (; it != it_e; ++it) + xr_parallel_for(0u, static_cast(prepared.size()), [&](u32 index) { - const FS_File& f = (*it); - _splitpath(f.name.c_str(), 0, p_path, p_name, p_ext); - FS.update_path(_path, "$game_particles$", f.name.c_str()); - CInifile ini(_path, TRUE, TRUE, FALSE); + PreparedParticle& result = prepared[index]; + string_path fullPath; + FS.update_path(fullPath, "$game_particles$", result.file.c_str()); + CInifile ini(fullPath, TRUE, TRUE, FALSE); - xr_sprintf(_path, sizeof(_path), "%s%s", p_path, p_name); - if (0 == stricmp(p_ext, ".pe")) + if (result.effect) { - PS::CPEDef* def = xr_new(); - def->m_Name = _path; - if (def->Load2(ini)) - { - m_all_ps.push_back(def->m_Name); - m_PEDs.push_back(def); - } - else - xr_delete(def); + xr_unique_ptr definition = xr_make_unique(); + definition->m_Name = result.name; + if (definition->Load2(ini)) + result.effectDefinition = std::move(definition); } - else if (0 == stricmp(p_ext, ".pg")) + else { - PS::CPGDef* def = xr_new(); - def->m_Name = _path; - if (def->Load2(ini)) - { - m_all_ps.push_back(def->m_Name); - m_PGDs.push_back(def); - } - else - xr_delete(def); + xr_unique_ptr definition = xr_make_unique(); + definition->m_Name = result.name; + if (definition->Load2(ini)) + result.groupDefinition = std::move(definition); } - else + }); + + for (PreparedParticle& result : prepared) + { + if (result.effectDefinition) { - R_ASSERT(0); + m_all_ps.push_back(result.effectDefinition->m_Name); + m_PEDs.push_back(result.effectDefinition.release()); + } + else if (result.groupDefinition) + { + m_all_ps.push_back(result.groupDefinition->m_Name); + m_PGDs.push_back(result.groupDefinition.release()); } } + m_prepare_loose_ms = startupTimer.GetElapsed_ms(); bool bRes = true; if (FS.exist(nm)) @@ -329,14 +353,32 @@ bool CPSLibrary::Load(const char* nm) FS.r_close(F); } + m_prepare_library_ms = startupTimer.GetElapsed_ms() - m_prepare_loose_ms; std::sort(m_PEDs.begin(), m_PEDs.end(), ped_sort_pred); std::sort(m_PGDs.begin(), m_PGDs.end(), pgd_sort_pred); + m_prepare_sort_ms = startupTimer.GetElapsed_ms() - m_prepare_loose_ms - m_prepare_library_ms; + + return bRes; +} +void CPSLibrary::FinalizeLoad() +{ + CTimer shaderTimer; + shaderTimer.Start(); for (PS::PEDIt e_it = m_PEDs.begin(); e_it != m_PEDs.end(); ++e_it) (*e_it)->CreateShader(); + Msg("* [STARTUP/RENDER PARTICLES] loose=%u library=%u sort=%u shaders=%u effects=%u groups=%u total=%u ms", + m_prepare_loose_ms, m_prepare_library_ms, m_prepare_sort_ms, shaderTimer.GetElapsed_ms(), + static_cast(m_PEDs.size()), static_cast(m_PGDs.size()), + m_prepare_loose_ms + m_prepare_library_ms + m_prepare_sort_ms + shaderTimer.GetElapsed_ms()); +} - return bRes; +bool CPSLibrary::Load(const char* nm) +{ + const bool result = LoadDefinitions(nm); + FinalizeLoad(); + return result; } //---------------------------------------------------- diff --git a/src/Layers/xrRender/PSLibrary.h b/src/Layers/xrRender/PSLibrary.h index 728105e3a0..325af0d3ee 100644 --- a/src/Layers/xrRender/PSLibrary.h +++ b/src/Layers/xrRender/PSLibrary.h @@ -17,9 +17,17 @@ namespace PS class ECORE_API CPSLibrary : public particles_systems::library_interface { + friend class CRender; + PS::PEDVec m_PEDs; PS::PGDVec m_PGDs; xr_vector m_all_ps; + u32 m_prepare_loose_ms = 0; + u32 m_prepare_library_ms = 0; + u32 m_prepare_sort_ms = 0; + + bool LoadDefinitions(LPCSTR nm); + void FinalizeLoad(); #ifdef _EDITOR AnsiString m_CurrentParticles; diff --git a/src/Layers/xrRender/R_Backend.h b/src/Layers/xrRender/R_Backend.h index 24b4575bc3..885dbc7345 100644 --- a/src/Layers/xrRender/R_Backend.h +++ b/src/Layers/xrRender/R_Backend.h @@ -237,10 +237,8 @@ class ECORE_API CBackend VERIFY(!"Invalid texture stage"); } #endif - while (T&&!T->flags.bLoaded) - { - SwitchToThread(); - } + if (T && !T->is_loaded()) + T->Load(); return T; } diff --git a/src/Layers/xrRender/R_Backend_Runtime.cpp b/src/Layers/xrRender/R_Backend_Runtime.cpp index e04c895ca0..6b3ddd033c 100644 --- a/src/Layers/xrRender/R_Backend_Runtime.cpp +++ b/src/Layers/xrRender/R_Backend_Runtime.cpp @@ -246,7 +246,7 @@ void CBackend::set_Textures(STextureList* _T) if (load_surf) { PGO(Msg("PGO:tex%d:%s",load_id,load_surf->cName.c_str())); - load_surf->bind(load_id); + load_surf->Bind(load_id); // load_surf->Apply (load_id); } } @@ -271,7 +271,7 @@ void CBackend::set_Textures(STextureList* _T) if (load_surf) { PGO(Msg("PGO:tex%d:%s",load_id,load_surf->cName.c_str())); - load_surf->bind(load_id); + load_surf->Bind(load_id); // load_surf->Apply (load_id); } } @@ -294,7 +294,7 @@ void CBackend::set_Textures(STextureList* _T) if (load_surf) { PGO(Msg("PGO:tex%d:%s",load_id,load_surf->cName.c_str())); - load_surf->bind(load_id); + load_surf->Bind(load_id); // load_surf->Apply (load_id); } } @@ -317,7 +317,7 @@ void CBackend::set_Textures(STextureList* _T) if (load_surf) { PGO(Msg("PGO:tex%d:%s",load_id,load_surf->cName.c_str())); - load_surf->bind(load_id); + load_surf->Bind(load_id); // load_surf->Apply (load_id); } } @@ -339,7 +339,7 @@ void CBackend::set_Textures(STextureList* _T) if (load_surf) { PGO(Msg("PGO:tex%d:%s",load_id,load_surf->cName.c_str())); - load_surf->bind(load_id); + load_surf->Bind(load_id); // load_surf->Apply (load_id); } } @@ -361,7 +361,7 @@ void CBackend::set_Textures(STextureList* _T) if (load_surf) { PGO(Msg("PGO:tex%d:%s",load_id,load_surf->cName.c_str())); - load_surf->bind(load_id); + load_surf->Bind(load_id); // load_surf->Apply (load_id); } } diff --git a/src/Layers/xrRender/ResourceManager.cpp b/src/Layers/xrRender/ResourceManager.cpp index 6b9e46ac8a..95a7725e22 100644 --- a/src/Layers/xrRender/ResourceManager.cpp +++ b/src/Layers/xrRender/ResourceManager.cpp @@ -46,6 +46,49 @@ BOOL reclaim(xr_vector& vec, const T* ptr) return FALSE; } +template +static void remove_shader_indexed(xr_unordered_flat_map>& index, u64 hash, const T* value) +{ + auto bucket = index.find(hash); + if (bucket == index.end()) + return; + auto& values = bucket->second; + values.erase(std::remove(values.begin(), values.end(), value), values.end()); + if (values.empty()) + index.erase(bucket); +} + +static u64 shader_hash_pointer(u64 hash, const void* pointer) +{ + const uintptr_t value = reinterpret_cast(pointer); + for (u32 i = 0; i < sizeof(value); ++i) + { + hash ^= static_cast(value >> (i * 8)); + hash *= 1099511628211ull; + } + return hash; +} + +static u64 element_hash(const ShaderElement& element) +{ + u64 hash = 1469598103934665603ull; + hash ^= element.flags.iPriority | (element.flags.bStrictB2F << 2) | (element.flags.bEmissive << 3) | + (element.flags.bDistort << 4) | (element.flags.bWmark << 5) | (element.flags.bLandscape << 6) | + (element.flags.iScopeLense << 7); + hash *= 1099511628211ull; + for (const ref_pass& pass : element.passes) + hash = shader_hash_pointer(hash, pass._get()); + return hash; +} + +static u64 shader_hash(const Shader& shader) +{ + u64 hash = 1469598103934665603ull; + for (u32 i = 0; i < 5; ++i) + hash = shader_hash_pointer(hash, shader.E[i]._get()); + return hash; +} + //-------------------------------------------------------------------------------------------------------------- IBlender* CResourceManager::_GetBlender(LPCSTR Name) { @@ -142,21 +185,30 @@ void CResourceManager::_ParseList(sh_list& dest, LPCSTR names) } } -ShaderElement* CResourceManager::_CreateElement(ShaderElement& S) +ShaderElement* CResourceManager::_CreateElement(ShaderElement& S, ref_selement* keep_alive) { if (S.passes.empty()) return 0; xrCriticalSectionGuard guard(creationGuard); - // Search equal in shaders array - for (u32 it = 0; it < v_elements.size(); it++) - if (S.equal(*(v_elements[it]))) return v_elements[it]; + const u64 hash = element_hash(S); + auto& candidates = m_element_index[hash]; + for (ShaderElement* candidate : candidates) + if (S.equal(*candidate)) + { + if (keep_alive) + *keep_alive = candidate; + return candidate; + } // Create _new_ entry ShaderElement* N = xr_new(S); //N->_copy(S); N->dwFlags |= xr_resource_flagged::RF_REGISTERED; v_elements.push_back(N); + candidates.push_back(N); + if (keep_alive) + *keep_alive = N; return N; } @@ -164,15 +216,14 @@ void CResourceManager::_DeleteElement(const ShaderElement* S) { xrCriticalSectionGuard guard(creationGuard); if (0 == (S->dwFlags & xr_resource_flagged::RF_REGISTERED)) return; + remove_shader_indexed(m_element_index, element_hash(*S), S); if (reclaim(v_elements, S)) return; Msg("! ERROR: Failed to find compiled 'shader-element'"); } Shader* CResourceManager::_cpp_Create(IBlender* B, LPCSTR s_shader, LPCSTR s_textures, LPCSTR s_constants, - LPCSTR s_matrices) + LPCSTR s_matrices, bool hud_loading, ref_shader* keep_alive) { - xrCriticalSectionGuard guard(creationGuard); - CBlender_Compile C; Shader S; @@ -199,7 +250,7 @@ Shader* CResourceManager::_cpp_Create(IBlender* B, LPCSTR s_shader, LPCSTR s_tex _ParseList(C.L_matrices, s_matrices); #if defined(USE_DX11) - if (::Render->hud_loading && RImplementation.o.ssfx_core) + if (hud_loading && RImplementation.o.ssfx_core) { C.HudElement = true; } @@ -212,7 +263,7 @@ Shader* CResourceManager::_cpp_Create(IBlender* B, LPCSTR s_shader, LPCSTR s_tex //. C.bDetail = _GetDetailTexture(*C.L_textures[0],C.detail_texture,C.detail_scaler); ShaderElement E; C._cpp_Compile(&E); - S.E[0] = _CreateElement(E); + _CreateElement(E, &S.E[0]); } // Compile element (LOD1) @@ -222,7 +273,7 @@ Shader* CResourceManager::_cpp_Create(IBlender* B, LPCSTR s_shader, LPCSTR s_tex C.bDetail = m_textures_description.GetDetailTexture(C.L_textures[0], C.detail_texture, C.detail_scaler); ShaderElement E; C._cpp_Compile(&E); - S.E[1] = _CreateElement(E); + _CreateElement(E, &S.E[1]); } // Compile element @@ -231,7 +282,7 @@ Shader* CResourceManager::_cpp_Create(IBlender* B, LPCSTR s_shader, LPCSTR s_tex C.bDetail = FALSE; ShaderElement E; C._cpp_Compile(&E); - S.E[2] = _CreateElement(E); + _CreateElement(E, &S.E[2]); } // Compile element @@ -240,7 +291,7 @@ Shader* CResourceManager::_cpp_Create(IBlender* B, LPCSTR s_shader, LPCSTR s_tex C.bDetail = FALSE; ShaderElement E; C._cpp_Compile(&E); - S.E[3] = _CreateElement(E); + _CreateElement(E, &S.E[3]); } // Compile element @@ -249,7 +300,7 @@ Shader* CResourceManager::_cpp_Create(IBlender* B, LPCSTR s_shader, LPCSTR s_tex C.bDetail = TRUE; //.$$$ HACK :) ShaderElement E; C._cpp_Compile(&E); - S.E[4] = _CreateElement(E); + _CreateElement(E, &S.E[4]); } // Compile element @@ -258,22 +309,19 @@ Shader* CResourceManager::_cpp_Create(IBlender* B, LPCSTR s_shader, LPCSTR s_tex C.bDetail = FALSE; ShaderElement E; C._cpp_Compile(&E); - S.E[5] = _CreateElement(E); + _CreateElement(E, &S.E[5]); } // Hacky way to remove from the HUD mask transparent stuff. ( Let's try something better later... ) - if (::Render->hud_loading) + if (hud_loading) { + xrCriticalSectionGuard guard(creationGuard); if (strstr(s_shader, "lens")) S.E[0]->passes[0]->ps->hud_disabled = TRUE; } - // Search equal in shaders array - for (u32 it = 0; it < v_shaders.size(); it++) - if (S.equal(v_shaders[it])) return v_shaders[it]; - // Create _new_ entry - Shader* ResultShader = _CreateShader(&S); + Shader* ResultShader = _CreateShader(&S, keep_alive); return ResultShader; } @@ -288,9 +336,10 @@ Shader* CResourceManager::_cpp_Create(LPCSTR s_shader, LPCSTR s_textures, LPCSTR #if defined(USE_DX10) || defined(USE_DX11) IBlender* pBlender = _GetBlender(s_shader ? s_shader : "null"); if (!pBlender) return NULL; - return _cpp_Create(pBlender, s_shader, s_textures, s_constants, s_matrices); + return _cpp_Create(pBlender, s_shader, s_textures, s_constants, s_matrices, ::Render->hud_loading); #else // USE_DX10 - return _cpp_Create(_GetBlender(s_shader ? s_shader : "null"), s_shader, s_textures, s_constants, s_matrices); + return _cpp_Create(_GetBlender(s_shader ? s_shader : "null"), s_shader, s_textures, s_constants, s_matrices, + ::Render->hud_loading); #endif // USE_DX10 //#else } @@ -310,7 +359,7 @@ Shader* CResourceManager::Create(IBlender* B, LPCSTR s_shader, LPCSTR s_textures if (!g_dedicated_server) #endif { - return _cpp_Create(B, s_shader, s_textures, s_constants, s_matrices); + return _cpp_Create(B, s_shader, s_textures, s_constants, s_matrices, ::Render->hud_loading); //#else } #ifndef _EDITOR @@ -368,12 +417,157 @@ Shader* CResourceManager::Create(LPCSTR s_shader, LPCSTR s_textures, LPCSTR s_co //#endif } +Shader* CResourceManager::CreateLevelShader(LPCSTR s_shader, LPCSTR s_textures, u64 recipe_identity) +{ + xr_string key = s_shader; + key += '\n'; + key += s_textures; + string_path level_path; + FS.update_path(level_path, "$level$", ""); + key += '\n'; + key += level_path; + string32 identity; + xr_sprintf(identity, "%016llx", recipe_identity); + key += '\n'; + key += identity; + { + xrCriticalSectionGuard guard(creationGuard); + auto cached = m_level_shader_cache.find(key); + if (cached != m_level_shader_cache.end()) + return cached->second._get(); + } + + Shader* shader = Create(s_shader, s_textures); + { + xrCriticalSectionGuard guard(creationGuard); + m_level_shader_cache.emplace(std::move(key), ref_shader(shader)); + } + return shader; +} + +ref_shader CResourceManager::CreateLevelCppShader(LPCSTR s_shader, LPCSTR s_textures, LPCSTR s_constants, + LPCSTR s_matrices, u64 recipe_identity, LPCSTR canonical_level_path) +{ + xr_string key = s_shader ? s_shader : "null"; + key += '\n'; + key += s_textures ? s_textures : ""; + key += '\n'; + key += s_constants ? s_constants : ""; + key += '\n'; + key += s_matrices ? s_matrices : ""; + key += "\nworld"; + key += '\n'; + if (canonical_level_path && canonical_level_path[0]) + key += canonical_level_path; + else + { + string_path level_path; + FS.update_path(level_path, "$level$", ""); + key += level_path; + } + string32 identity; + xr_sprintf(identity, "%016llx", recipe_identity); + key += '\n'; + key += identity; + + xr_shared_ptr job; + bool producer = false; + { + xrCriticalSectionGuard guard(creationGuard); + auto cached = m_level_shader_cache.find(key); + if (cached != m_level_shader_cache.end()) + return cached->second; + + auto pending = m_level_shader_jobs.find(key); + if (pending != m_level_shader_jobs.end()) + job = pending->second; + else + { + job = xr_make_shared(); + m_level_shader_jobs.emplace(key, job); + producer = true; + } + } + + if (!producer) + { + WaitForSingleObject(job->completed, INFINITE); + if (job->failure) + std::rethrow_exception(job->failure); + return job->result; + } + + ref_shader shader; + try + { + // A worker never compiles through the shared shader.xr blender instance. + // Serialize it to an owned snapshot, then restore a private clone. + CMemoryWriter snapshot; + CBlender_DESC description; + bool have_blender = false; + { + xrCriticalSectionGuard guard(creationGuard); + IBlender* source = _FindBlender(s_shader ? s_shader : "null"); + if (source) + { + description = source->getDescription(); + source->Save(snapshot); + have_blender = true; + } + } + + struct level_path_scope + { + xr_string previous; + level_path_scope(LPCSTR path) + { + previous.swap(g_resource_level_path_override); + if (path) + g_resource_level_path_override = path; + } + ~level_path_scope() { g_resource_level_path_override.swap(previous); } + } level_path(canonical_level_path); + if (have_blender) + { + IBlender* blender = IBlender::Create(description.CLS); + if (blender) + { + IReader reader(snapshot.pointer(), snapshot.size()); + blender->Load(reader, description.version); + _cpp_Create(blender, s_shader, s_textures, s_constants, s_matrices, false, &shader); + IBlender::Destroy(blender); + } + } + { + xrCriticalSectionGuard guard(creationGuard); + if (shader) + m_level_shader_cache.emplace(key, shader); + job->result = shader; + m_level_shader_jobs.erase(key); + } + } + catch (...) + { + { + xrCriticalSectionGuard guard(creationGuard); + job->failure = std::current_exception(); + m_level_shader_jobs.erase(key); + } + SetEvent(job->completed); + throw; + } + + SetEvent(job->completed); + return shader; +} + void CResourceManager::Delete(const Shader* S) { if (0 == (S->dwFlags & xr_resource_flagged::RF_REGISTERED)) return; xrCriticalSectionGuard guard(creationGuard); + remove_shader_indexed(m_shader_index, shader_hash(*S), S); if (reclaim(v_shaders, S)) return; @@ -381,34 +575,519 @@ void CResourceManager::Delete(const Shader* S) Msg("! ERROR: Failed to find complete shader"); } +void CResourceManager::CompleteTextureLoad(const ResourceLoadGenerationPtr& generation) +{ + xrCriticalSectionGuard guard(textureLoadGuard); + R_ASSERT(generation->pending); + if (--generation->pending == 0) + SetEvent(generation->completed); +} + +void CResourceManager::RecordTextureLoadFailure(const ResourceLoadGenerationPtr& generation, + std::exception_ptr failure) +{ + xrCriticalSectionGuard guard(textureLoadGuard); + if (!generation->failure) + generation->failure = failure; + generation->aborted = true; +} + +void CResourceManager::DrainOwnerTextureLoads(const ResourceLoadGenerationPtr& generation) +{ + xr_vector ownerTextures; + { + xrCriticalSectionGuard guard(textureLoadGuard); + ownerTextures.swap(generation->ownerTextureLoads); + ResetEvent(generation->ownerWorkAvailable); + } + + for (const ref_texture& texture : ownerTextures) + { + bool aborted; + { + xrCriticalSectionGuard guard(textureLoadGuard); + aborted = generation->aborted; + } + + try + { + if (aborted) + texture->CancelQueuedLoad(); + else + texture->LoadQueued(); + } + catch (...) + { + RecordTextureLoadFailure(generation, std::current_exception()); + } + CompleteTextureLoad(generation); + } +} + +std::exception_ptr CResourceManager::WaitForTextureLoadGeneration(const ResourceLoadGenerationPtr& generation) +{ + for (;;) + { + u32 serial; + { + xrCriticalSectionGuard guard(textureLoadGuard); + serial = generation->serial; + } + + DrainOwnerTextureLoads(generation); + if (generation->native_generation && + NativeLoadExecutor::Instance().HelpGeneration(generation->native_generation)) + { + continue; + } + const HANDLE events[] = {generation->completed, generation->ownerWorkAvailable}; + WaitForMultipleObjects(static_cast(std::size(events)), events, FALSE, INFINITE); + + xrCriticalSectionGuard guard(textureLoadGuard); + if (serial == generation->serial && generation->pending == 0 && generation->ownerTextureLoads.empty()) + return generation->failure; + } +} + +void CResourceManager::QueueTextureLoad(const ref_texture& texture) +{ + if (!texture) + return; + + ResourceLoadGenerationPtr generation; + { + xrCriticalSectionGuard guard(textureLoadGuard); + if (resourceLoadGenerationStarting) + { + m_generationStartingTextureLoads.push_back(texture); + return; + } + generation = activeResourceLoadGeneration; + if (generation && (generation->closed || generation->aborted)) + return; + } + + const bool async = texture->CanLoadAsync(); + const DWORD originThread = GetCurrentThreadId(); + xrCriticalSectionGuard guard(textureLoadGuard); + if (resourceLoadGenerationStarting) + { + m_generationStartingTextureLoads.push_back(texture); + return; + } + if ( + (generation && (activeResourceLoadGeneration != generation || generation->closed || generation->aborted)) || + (!generation && activeResourceLoadGeneration)) + return; + if (!texture->TryQueueLoad()) + return; + + if (!generation) + { + if (!async) + { + try + { + m_ownerTextureLoads.push_back(texture); + } + catch (...) + { + texture->CancelQueuedLoad(); + throw; + } + } + else + { + try + { + textureLoadTasks.run([this, texture, originThread]() + { + try + { + if (originThread != GetCurrentThreadId()) + { + PROF_THREAD("X-Ray PPL Thread") + } + texture->LoadQueued(); + } + catch (...) + { + xrCriticalSectionGuard failureGuard(textureLoadGuard); + if (!textureLoadFailure) + textureLoadFailure = std::current_exception(); + } + }); + } + catch (...) + { + texture->CancelQueuedLoad(); + throw; + } + } + ++textureLoadSerial; + return; + } + + if (generation->pending++ == 0) + ResetEvent(generation->completed); + ++generation->serial; + if (!async) + { + try + { + generation->ownerTextureLoads.push_back(texture); + } + catch (...) + { + generation->aborted = true; + texture->CancelQueuedLoad(); + if (--generation->pending == 0) + SetEvent(generation->completed); + throw; + } + SetEvent(generation->ownerWorkAvailable); + return; + } + + NativeLoadExecutor& executor = NativeLoadExecutor::Instance(); + NativeLoadExecutor::Batch batch; + try + { + batch = executor.BeginBatch(generation->native_generation); + } + catch (...) + { + generation->aborted = true; + texture->CancelQueuedLoad(); + if (--generation->pending == 0) + SetEvent(generation->completed); + throw; + } + if (!batch.Valid()) + { + generation->aborted = true; + texture->CancelQueuedLoad(); + if (--generation->pending == 0) + SetEvent(generation->completed); + return; + } + + try + { + const bool submitted = executor.Submit(batch, NativeLoadPriority::ShaderTexture, + [this, texture, generation, originThread]() + { + if (originThread != GetCurrentThreadId()) + { + PROF_THREAD("X-Ray PPL Thread") + } + + std::exception_ptr failure; + bool aborted; + { + xrCriticalSectionGuard guard(textureLoadGuard); + aborted = generation->aborted; + } + try + { + if (aborted) + texture->CancelQueuedLoad(); + else + texture->LoadQueued(); + } + catch (...) + { + failure = std::current_exception(); + RecordTextureLoadFailure(generation, failure); + } + CompleteTextureLoad(generation); + if (failure) + std::rethrow_exception(failure); + }, + [this, texture, generation]() + { + texture->CancelQueuedLoad(); + CompleteTextureLoad(generation); + }); + if (!submitted) + { + generation->aborted = true; + texture->CancelQueuedLoad(); + if (--generation->pending == 0) + SetEvent(generation->completed); + } + } + catch (...) + { + if (!generation->failure) + generation->failure = std::current_exception(); + generation->aborted = true; + texture->CancelQueuedLoad(); + if (--generation->pending == 0) + SetEvent(generation->completed); + throw; + } +} + +void CResourceManager::WaitForTextureLoads() +{ + ResourceLoadGenerationPtr generation; + { + xrCriticalSectionGuard guard(textureLoadGuard); + generation = activeResourceLoadGeneration; + } + if (generation) + { + if (const std::exception_ptr failure = WaitForTextureLoadGeneration(generation)) + std::rethrow_exception(failure); + return; + } + + for (;;) + { + u32 serial; + xr_vector ownerTextures; + { + xrCriticalSectionGuard guard(textureLoadGuard); + serial = textureLoadSerial; + ownerTextures.swap(m_ownerTextureLoads); + } + + // Startup fallback: video, sequence and GIF decoders stay on the render/owner thread. + for (u32 index = 0; index < ownerTextures.size(); ++index) + { + try + { + ownerTextures[index]->LoadQueued(); + } + catch (...) + { + for (++index; index < ownerTextures.size(); ++index) + ownerTextures[index]->CancelQueuedLoad(); + throw; + } + } + textureLoadTasks.wait(); + + std::exception_ptr failure; + { + xrCriticalSectionGuard guard(textureLoadGuard); + if (serial != textureLoadSerial || !m_ownerTextureLoads.empty()) + continue; + failure = textureLoadFailure; + textureLoadFailure = nullptr; + } + if (failure) + std::rethrow_exception(failure); + return; + } +} + +u64 CResourceManager::BeginLoadGeneration() +{ + { + xrCriticalSectionGuard guard(textureLoadGuard); + R_ASSERT2(!activeResourceLoadGeneration && !resourceLoadGenerationStarting, + "Previous texture load generation is still active"); + resourceLoadGenerationStarting = true; + } + + try + { + WaitForTextureLoads(); + } + catch (...) + { + xrCriticalSectionGuard guard(textureLoadGuard); + resourceLoadGenerationStarting = false; + throw; + } + + xr_vector crossedRequests; + ResourceLoadGenerationPtr startedGeneration; + u64 result; + { + xrCriticalSectionGuard guard(textureLoadGuard); + if (++nextResourceLoadGeneration == 0) + ++nextResourceLoadGeneration; + try + { + const u64 nativeGeneration = NativeLoadExecutor::Instance().CurrentGeneration(); + R_ASSERT2(nativeGeneration, "Native load generation must begin before texture load generation"); + startedGeneration = xr_make_shared(nextResourceLoadGeneration, nativeGeneration); + activeResourceLoadGeneration = startedGeneration; + } + catch (...) + { + resourceLoadGenerationStarting = false; + throw; + } + resourceLoadGenerationStarting = false; + crossedRequests.swap(m_generationStartingTextureLoads); + result = nextResourceLoadGeneration; + } + try + { + for (const ref_texture& texture : crossedRequests) + QueueTextureLoad(texture); + } + catch (...) + { + const std::exception_ptr failure = std::current_exception(); + NativeLoadExecutor::Instance().CancelGeneration(startedGeneration->native_generation); + AbortLoadGeneration(result); + std::rethrow_exception(failure); + } + return result; +} + +void CResourceManager::AbortLoadGeneration(u64 generationId) +{ + ResourceLoadGenerationPtr generation; + { + xrCriticalSectionGuard guard(textureLoadGuard); + generation = activeResourceLoadGeneration; + if (!generation || generation->id != generationId) + return; + generation->closed = true; + generation->aborted = true; + } + + WaitForTextureLoadGeneration(generation); + { + xrCriticalSectionGuard guard(textureLoadGuard); + if (activeResourceLoadGeneration == generation) + activeResourceLoadGeneration.reset(); + } +} + +void CResourceManager::FinalizeLoadGeneration(u64 generationId) +{ + ResourceLoadGenerationPtr generation; + { + xrCriticalSectionGuard guard(textureLoadGuard); + generation = activeResourceLoadGeneration; + if (!generation || generation->id != generationId) + return; + generation->closed = true; + } + + const std::exception_ptr failure = WaitForTextureLoadGeneration(generation); + { + xrCriticalSectionGuard guard(textureLoadGuard); + if (activeResourceLoadGeneration == generation) + activeResourceLoadGeneration.reset(); + } + if (failure) + std::rethrow_exception(failure); +} + +void CResourceManager::PrefetchTexture(LPCSTR name, LPCSTR canonical_level_path) +{ + _CreateTexture(name, true, canonical_level_path); +} + +int CResourceManager::GetTextureLoadLod(LPCSTR name) const +{ + ENGINE_API bool is_enough_address_space_available(); + static const bool enough_address_space_available = is_enough_address_space_available(); + for (const shared_str& reduced : m_reduceLodTextureList) + { + if (!strstr(name, reduced.c_str())) + continue; + if (psTextureLOD < 1) + return enough_address_space_available ? 0 : 1; + return psTextureLOD < 3 ? 1 : 2; + } + if (psTextureLOD < 2) + return 0; + return psTextureLOD < 4 ? 1 : 2; +} + void CResourceManager::DeferredUpload() { if (!RDEVICE.b_is_Ready) return; - Msg("CResourceManager::DeferredUpload -> START, size = %d", m_textures.size()); - CTimer timer; timer.Start(); - static DWORD this_thread_id = 0; - this_thread_id = GetCurrentThreadId(); - xr_parallel_foreach(m_textures.begin(), m_textures.end(), [](auto& pair) + xr_vector deferredTextures; { - if (this_thread_id != GetCurrentThreadId()) { PROF_THREAD("X-Ray PPL Thread") } - pair.second->Load(); - }); + xrCriticalSectionGuard guard(creationGuard); + deferredTextures.swap(m_deferredTextureLoads); + } + + for (const ref_texture& texture : deferredTextures) + QueueTextureLoad(texture); + + WaitForTextureLoads(); Msg("texture loading time: %d", timer.GetElapsed_ms()); } +void CResourceManager::PrepareLoad() +{ + xr_vector deferredTextures; + { + xrCriticalSectionGuard guard(creationGuard); + deferredTextures.swap(m_deferredTextureLoads); + } + for (const ref_texture& texture : deferredTextures) + QueueTextureLoad(texture); + + ResourceLoadGenerationPtr generation; + { + xrCriticalSectionGuard guard(textureLoadGuard); + generation = activeResourceLoadGeneration; + } + if (generation) + { + DrainOwnerTextureLoads(generation); + std::exception_ptr failure; + { + xrCriticalSectionGuard guard(textureLoadGuard); + failure = generation->failure; + } + if (failure) + std::rethrow_exception(failure); + return; + } + + xr_vector ownerTextures; + { + xrCriticalSectionGuard guard(textureLoadGuard); + ownerTextures.swap(m_ownerTextureLoads); + } + for (u32 index = 0; index < ownerTextures.size(); ++index) + { + try + { + ownerTextures[index]->LoadQueued(); + } + catch (...) + { + for (++index; index < ownerTextures.size(); ++index) + ownerTextures[index]->CancelQueuedLoad(); + throw; + } + } +} + void CResourceManager::DeferredUnload() { if (!RDEVICE.b_is_Ready) return; - - xr_parallel_foreach(m_textures.begin(), m_textures.end(), [](auto& pair) + WaitForTextureLoads(); + xr_vector textures; + { + xrCriticalSectionGuard guard(creationGuard); + textures.reserve(m_textures.size()); + for (const auto& pair : m_textures) + textures.emplace_back(pair.second); + } + xr_parallel_foreach(textures.begin(), textures.end(), [](ref_texture& texture) { - pair.second->Unload(); + texture->Unload(); }); } @@ -416,35 +1095,37 @@ void CResourceManager::UnloadAllTexturesOnLevelUnload() { if (!RDEVICE.b_is_Ready) return; + WaitForTextureLoads(); - xrCriticalSectionGuard guard(creationGuard); + xr_vector textures_to_unload; + { + xrCriticalSectionGuard guard(creationGuard); + textures_to_unload.reserve(m_textures.size()); - xr_vector textures_to_unload; - textures_to_unload.reserve(m_textures.size()); + for (const auto& pair : m_textures) + { + CTexture* texture = pair.second; + if (!texture) + continue; - for (const auto& pair : m_textures) - { - CTexture* texture = pair.second; - if (!texture) - continue; + if (texture->flags.bUser) + continue; - if (texture->flags.bUser) - continue; + // Keep $ textures alive since they are bound to runtime render targets or other important parts + if (strstr(*texture->cName, "$")) + continue; - // Keep $ textures alive since they are bound to runtime render targets or other important parts - if (strstr(*texture->cName, "$")) - continue; + // Keep UI textures + LPCSTR name = pair.first; + if (strncmp(name, "ui\\", 3) == 0 || strncmp(name, "ui/", 3) == 0) + continue; - // Keep UI textures - LPCSTR name = pair.first; - if (strncmp(name, "ui\\", 3) == 0 || strncmp(name, "ui/", 3) == 0) - continue; + textures_to_unload.emplace_back(texture); + } + } - textures_to_unload.push_back(texture); - } - - for (CTexture* texture : textures_to_unload) - texture->Unload(); + for (ref_texture& texture : textures_to_unload) + texture->Unload(); } #ifdef _EDITOR @@ -467,28 +1148,34 @@ void CResourceManager::ED_UpdateTextures(AStringVec* names) } #endif -Shader* CResourceManager::_CreateShader(Shader* InShader) +Shader* CResourceManager::_CreateShader(Shader* InShader, ref_shader* keep_alive) { xrCriticalSectionGuard guard(creationGuard); - - // Search equal in shaders array - for (Shader* it : v_shaders) - { - if (InShader->equal(it)) - return it; - } + const u64 hash = shader_hash(*InShader); + auto& candidates = m_shader_index[hash]; + for (Shader* candidate : candidates) + if (InShader->equal(candidate)) + { + if (keep_alive) + *keep_alive = candidate; + return candidate; + } // Create _new_ entry Shader* N = xr_new(*InShader); //N->_copy(*InShader); N->dwFlags |= xr_resource_flagged::RF_REGISTERED; v_shaders.push_back(N); + candidates.push_back(N); + if (keep_alive) + *keep_alive = N; return N; } void CResourceManager::_GetMemoryUsage(u32& m_base, u32& c_base, u32& m_lmaps, u32& c_lmaps) { + xrCriticalSectionGuard guard(creationGuard); m_base = c_base = m_lmaps = c_lmaps = 0; map_Texture::iterator I = m_textures.begin(); @@ -511,6 +1198,7 @@ void CResourceManager::_GetMemoryUsage(u32& m_base, u32& c_base, u32& m_lmaps, u void CResourceManager::_DumpMemoryUsage() { + xrCriticalSectionGuard guard(creationGuard); xr_multimap> mtex; // sort @@ -599,7 +1287,7 @@ void CResourceManager::EvictStalledTextures() { #endif // DEBUG continue; } - if (!tex->flags.bLoaded) { + if (!tex->is_loaded()) { #ifdef DEBUG skip_unloaded++; #endif // DEBUG @@ -666,3 +1354,4 @@ BOOL CResourceManager::_GetDetailTexture(LPCSTR Name,LPCSTR& T, R_constant_setup return FALSE; } }*/ +thread_local xr_string g_resource_level_path_override; diff --git a/src/Layers/xrRender/ResourceManager.h b/src/Layers/xrRender/ResourceManager.h index 39883b59cd..f099b35931 100644 --- a/src/Layers/xrRender/ResourceManager.h +++ b/src/Layers/xrRender/ResourceManager.h @@ -14,6 +14,8 @@ struct lua_State; class dx10ConstantBuffer; +ECORE_API xrCriticalSection& shader_creation_guard(LPCSTR name); + // defs class ECORE_API CResourceManager { @@ -85,10 +87,94 @@ class ECORE_API CResourceManager xr_vector v_passes; xr_vector v_elements; xr_vector v_shaders; + xr_unordered_flat_map> m_state_index; + xr_unordered_flat_map> m_pass_index; + xr_unordered_flat_map> m_constant_table_index; + xr_unordered_flat_map> m_texture_list_index; + xr_unordered_flat_map> m_element_index; + xr_unordered_flat_map> m_shader_index; + xr_map m_level_shader_cache; + struct level_shader_job + { + HANDLE completed; + ref_shader result; + std::exception_ptr failure; + level_shader_job() : completed(CreateEvent(nullptr, TRUE, FALSE, nullptr)) { R_ASSERT(completed); } + ~level_shader_job() { CloseHandle(completed); } + }; + xr_map> m_level_shader_jobs; xr_vector m_necessary; + xr_vector m_deferredTextureLoads; + xr_vector m_ownerTextureLoads; + xr_vector m_generationStartingTextureLoads; + xr_map m_prefetchedTextures; + xr_vector m_reduceLodTextureList; + struct TextureSourceInfo + { + xr_string resolvedPath; + u32 loadKind = 0; + bool levelLocal = false; + u32 crc = 0; + u32 sizeReal = 0; + u32 sizeCompressed = 0; + u32 modified = 0; + }; + struct TextureSourceJob + { + HANDLE completed; + TextureSourceInfo source; + std::exception_ptr failure; + + TextureSourceJob() : completed(CreateEvent(nullptr, TRUE, FALSE, nullptr)) { R_ASSERT(completed); } + ~TextureSourceJob() { CloseHandle(completed); } + }; + xrCriticalSection textureSourceGuard; + xr_map> m_textureSourceCache; + struct ResourceLoadGeneration + { + const u64 id; + const u64 native_generation; + HANDLE completed; + HANDLE ownerWorkAvailable; + u32 pending = 0; + u32 serial = 0; + bool closed = false; + bool aborted = false; + std::exception_ptr failure; + xr_vector ownerTextureLoads; + + ResourceLoadGeneration(u64 value, u64 native_value) : id(value), native_generation(native_value), + completed(CreateEvent(nullptr, TRUE, TRUE, nullptr)), + ownerWorkAvailable(CreateEvent(nullptr, TRUE, FALSE, nullptr)) + { + R_ASSERT(completed && ownerWorkAvailable); + } + + ~ResourceLoadGeneration() + { + CloseHandle(ownerWorkAvailable); + CloseHandle(completed); + } + }; + using ResourceLoadGenerationPtr = xr_shared_ptr; // misc xrCriticalSection creationGuard; + xrCriticalSection textureLoadGuard; + xr_task_group textureLoadTasks; + u32 textureLoadSerial = 0; + std::exception_ptr textureLoadFailure; + u64 nextResourceLoadGeneration = 0; + DWORD textureOwnerThread = 0; + bool resourceLoadGenerationStarting = false; + ResourceLoadGenerationPtr activeResourceLoadGeneration; + + void QueueTextureLoad(const ref_texture& texture); + void ResolveTextureSource(LPCSTR name, LPCSTR canonical_level_path, TextureSourceInfo& result); + void CompleteTextureLoad(const ResourceLoadGenerationPtr& generation); + void RecordTextureLoadFailure(const ResourceLoadGenerationPtr& generation, std::exception_ptr failure); + void DrainOwnerTextureLoads(const ResourceLoadGenerationPtr& generation); + std::exception_ptr WaitForTextureLoadGeneration(const ResourceLoadGenerationPtr& generation); public: CTextureDescrMngr m_textures_description; @@ -120,8 +206,13 @@ class ECORE_API CResourceManager #endif // Low level resource creation - CTexture* _CreateTexture(LPCSTR Name); + ref_texture _CreateTexture(LPCSTR Name, bool prefetch = false, LPCSTR canonical_level_path = nullptr); void _DeleteTexture(const CTexture* T); + void PrefetchTexture(LPCSTR Name, LPCSTR canonical_level_path = nullptr); + void InvalidateTextureSourceCache(); + void InvalidateLevelShaderCache(); + void ReleaseLevelShaderCache(LPCSTR canonical_level_path, u64 recipe_identity); + int GetTextureLoadLod(LPCSTR name) const; CMatrix* _CreateMatrix(LPCSTR Name); void _DeleteMatrix(const CMatrix* M); @@ -129,14 +220,14 @@ class ECORE_API CResourceManager CConstant* _CreateConstant(LPCSTR Name); void _DeleteConstant(const CConstant* C); - R_constant_table* _CreateConstantTable(R_constant_table& C); + R_constant_table* _CreateConstantTable(R_constant_table& C, ref_ctable* keep_alive = nullptr); void _DeleteConstantTable(const R_constant_table* C); #if defined(USE_DX10) || defined(USE_DX11) - dx10ConstantBuffer* _CreateConstantBuffer(ID3DShaderReflectionConstantBuffer* pTable); + dx10ConstantBuffer* _CreateConstantBuffer(ID3DShaderReflectionConstantBuffer* pTable, ref_cbuffer* keep_alive = nullptr); void _DeleteConstantBuffer(const dx10ConstantBuffer* pBuffer); - SInputSignature* _CreateInputSignature(ID3DBlob* pBlob); + SInputSignature* _CreateInputSignature(ID3DBlob* pBlob, ref_input_sign* keep_alive = nullptr); void _DeleteInputSignature(const SInputSignature* pSignature); #endif // USE_DX10 @@ -150,59 +241,60 @@ class ECORE_API CResourceManager // DX10 cut CRTC* _CreateRTC (LPCSTR Name, u32 size, D3DFORMAT f); // DX10 cut void _DeleteRTC (const CRTC* RT ); #if defined(USE_DX10) || defined(USE_DX11) - SGS* _CreateGS (LPCSTR Name); + SGS* _CreateGS (LPCSTR Name, ref_gs* keep_alive = nullptr); void _DeleteGS (const SGS* GS ); #endif // USE_DX10 #ifdef USE_DX11 - SHS* _CreateHS (LPCSTR Name); + SHS* _CreateHS (LPCSTR Name, ref_hs* keep_alive = nullptr); void _DeleteHS (const SHS* HS ); - SDS* _CreateDS (LPCSTR Name); + SDS* _CreateDS (LPCSTR Name, ref_ds* keep_alive = nullptr); void _DeleteDS (const SDS* DS ); - SCS* _CreateCS (LPCSTR Name); + SCS* _CreateCS (LPCSTR Name, ref_cs* keep_alive = nullptr); void _DeleteCS (const SCS* CS ); #endif // USE_DX10 - SPS* _CreatePS(LPCSTR Name); + SPS* _CreatePS(LPCSTR Name, ref_ps* keep_alive = nullptr); void _DeletePS(const SPS* PS); - SVS* _CreateVS(LPCSTR Name); + SVS* _CreateVS(LPCSTR Name, ref_vs* keep_alive = nullptr); void _DeleteVS(const SVS* VS); - SPass* _CreatePass(const SPass& proto); + SPass* _CreatePass(const SPass& proto, ref_pass* keep_alive = nullptr); void _DeletePass(const SPass* P); // Shader compiling / optimizing - SState* _CreateState(SimulatorStates& Code); + SState* _CreateState(SimulatorStates& Code, ref_state* keep_alive = nullptr); void _DeleteState(const SState* SB); SDeclaration* _CreateDecl(D3DVERTEXELEMENT9* dcl); void _DeleteDecl(const SDeclaration* dcl); - STextureList* _CreateTextureList(STextureList& L); + STextureList* _CreateTextureList(STextureList& L, ref_texture_list* keep_alive = nullptr); void _DeleteTextureList(const STextureList* L); SMatrixList* _CreateMatrixList(SMatrixList& L); void _DeleteMatrixList(const SMatrixList* L); - Shader* _CreateShader(Shader* InShader); + Shader* _CreateShader(Shader* InShader, ref_shader* keep_alive = nullptr); SConstantList* _CreateConstantList(SConstantList& L); void _DeleteConstantList(const SConstantList* L); - ShaderElement* _CreateElement(ShaderElement& L); + ShaderElement* _CreateElement(ShaderElement& L, ref_selement* keep_alive = nullptr); void _DeleteElement(const ShaderElement* L); Shader* _cpp_Create(LPCSTR s_shader, LPCSTR s_textures = 0, LPCSTR s_constants = 0, LPCSTR s_matrices = 0); Shader* _cpp_Create(IBlender* B, LPCSTR s_shader = 0, LPCSTR s_textures = 0, LPCSTR s_constants = 0, - LPCSTR s_matrices = 0); + LPCSTR s_matrices = 0, bool hud_loading = false, ref_shader* keep_alive = nullptr); Shader* _lua_Create(LPCSTR s_shader, LPCSTR s_textures); BOOL _lua_HasShader(LPCSTR s_shader); CResourceManager() : bDeferredLoad(TRUE) { + textureOwnerThread = GetCurrentThreadId(); } ~CResourceManager(); @@ -216,6 +308,9 @@ class ECORE_API CResourceManager // Creation/Destroying Shader* Create(LPCSTR s_shader = 0, LPCSTR s_textures = 0, LPCSTR s_constants = 0, LPCSTR s_matrices = 0); + Shader* CreateLevelShader(LPCSTR s_shader, LPCSTR s_textures, u64 recipe_identity = 0); + ref_shader CreateLevelCppShader(LPCSTR s_shader, LPCSTR s_textures, LPCSTR s_constants = nullptr, + LPCSTR s_matrices = nullptr, u64 recipe_identity = 0, LPCSTR canonical_level_path = nullptr); Shader* Create(IBlender* B, LPCSTR s_shader = 0, LPCSTR s_textures = 0, LPCSTR s_constants = 0, LPCSTR s_matrices = 0); void Delete(const Shader* S); @@ -225,12 +320,18 @@ class ECORE_API CResourceManager v_constant_setup.push_back(mk_pair(shared_str(name), s)); } - SGeometry* CreateGeom(D3DVERTEXELEMENT9* decl, ID3DVertexBuffer* vb, ID3DIndexBuffer* ib); - SGeometry* CreateGeom(u32 FVF, ID3DVertexBuffer* vb, ID3DIndexBuffer* ib); + SGeometry* CreateGeom(D3DVERTEXELEMENT9* decl, ID3DVertexBuffer* vb, ID3DIndexBuffer* ib, ref_geom* keep_alive = nullptr); + SGeometry* CreateGeom(u32 FVF, ID3DVertexBuffer* vb, ID3DIndexBuffer* ib, ref_geom* keep_alive = nullptr); void DeleteGeom(const SGeometry* VS); void DeferredLoad(BOOL E) { bDeferredLoad = E; } + void PrepareLoad(); void DeferredUpload(); void DeferredUnload(); + void WaitForTextureLoads(); + bool IsTextureOwnerThread() const { return textureOwnerThread == GetCurrentThreadId(); } + u64 BeginLoadGeneration(); + void AbortLoadGeneration(u64 generation); + void FinalizeLoadGeneration(u64 generation); void UnloadAllTexturesOnLevelUnload(); void Evict(); void EvictStalledTextures(); @@ -247,8 +348,8 @@ class ECORE_API CResourceManager template T& GetShaderMap(); - template - T* CreateShader(const char* name); + template + T* CreateShader(const char* name, Ref* keep_alive); template void DestroyShader(const T* sh); @@ -256,4 +357,6 @@ class ECORE_API CResourceManager #endif // USE_DX10 }; +extern thread_local xr_string g_resource_level_path_override; + #endif //ResourceManagerH diff --git a/src/Layers/xrRender/ResourceManager_Loader.cpp b/src/Layers/xrRender/ResourceManager_Loader.cpp index 74b5679348..614ff01a17 100644 --- a/src/Layers/xrRender/ResourceManager_Loader.cpp +++ b/src/Layers/xrRender/ResourceManager_Loader.cpp @@ -8,6 +8,10 @@ void CResourceManager::OnDeviceDestroy(BOOL) { if (RDEVICE.b_is_Ready) return; + WaitForTextureLoads(); + m_level_shader_cache.clear(); + m_level_shader_jobs.clear(); + m_reduceLodTextureList.clear(); m_textures_description.UnLoad(); // Matrices @@ -53,12 +57,15 @@ void CResourceManager::OnDeviceCreate(IReader* F) { if (!RDEVICE.b_is_Ready) return; + CTimer startupTimer; + startupTimer.Start(); string256 name; #ifndef _EDITOR // scripting LS_Load(); #endif + const u32 scriptingMs = startupTimer.GetElapsed_ms(); IReader* fs = 0; // Load constants fs = F->open_chunk(0); @@ -129,7 +136,20 @@ void CResourceManager::OnDeviceCreate(IReader* F) fs->close(); } + const u32 libraryMs = startupTimer.GetElapsed_ms() - scriptingMs; m_textures_description.Load(); + const u32 texturesMs = startupTimer.GetElapsed_ms() - scriptingMs - libraryMs; + m_reduceLodTextureList.clear(); + if (pSettings && pSettings->section_exist("reduce_lod_texture_list")) + { + const CInifile::Sect& section = pSettings->r_section("reduce_lod_texture_list"); + m_reduceLodTextureList.reserve(section.Data.size()); + for (CInifile::SectCIt item = section.Data.begin(); item != section.Data.end(); ++item) + m_reduceLodTextureList.push_back(item->first); + } + const u32 settingsMs = startupTimer.GetElapsed_ms() - scriptingMs - libraryMs - texturesMs; + Msg("* [STARTUP/RENDER RESOURCES] scripting=%u library=%u textures=%u settings=%u total=%u ms", + scriptingMs, libraryMs, texturesMs, settingsMs, startupTimer.GetElapsed_ms()); } void CResourceManager::OnDeviceCreate(LPCSTR shName) @@ -154,6 +174,7 @@ void CResourceManager::OnDeviceCreate(LPCSTR shName) void CResourceManager::StoreNecessaryTextures() { + xrCriticalSectionGuard guard(creationGuard); if (!m_necessary.empty()) return; @@ -175,4 +196,9 @@ void CResourceManager::StoreNecessaryTextures() void CResourceManager::DestroyNecessaryTextures() { m_necessary.clear(); + xr_map prefetched; + { + xrCriticalSectionGuard guard(creationGuard); + prefetched.swap(m_prefetchedTextures); + } } diff --git a/src/Layers/xrRender/ResourceManager_Reset.cpp b/src/Layers/xrRender/ResourceManager_Reset.cpp index 67975f5933..b190c50f4e 100644 --- a/src/Layers/xrRender/ResourceManager_Reset.cpp +++ b/src/Layers/xrRender/ResourceManager_Reset.cpp @@ -10,6 +10,10 @@ void CResourceManager::reset_begin() { + NativeLoadExecutor::Instance().WaitCurrentGenerationIdle(); + WaitForTextureLoads(); + InvalidateTextureSourceCache(); + // destroy everything, renderer may use ::Render->reset_begin(); @@ -108,12 +112,76 @@ void mdump(C c) CResourceManager::~CResourceManager() { + WaitForTextureLoads(); DestroyNecessaryTextures(); + m_deferredTextureLoads.clear(); + m_ownerTextureLoads.clear(); + m_prefetchedTextures.clear(); Dump(false); } +void CResourceManager::InvalidateTextureSourceCache() +{ + xrCriticalSectionGuard guard(textureSourceGuard); + m_textureSourceCache.clear(); +} + +void CResourceManager::InvalidateLevelShaderCache() +{ + xr_map cache; + for (;;) + { + xr_vector> jobs; + { + xrCriticalSectionGuard guard(creationGuard); + if (m_level_shader_jobs.empty()) + { + cache.swap(m_level_shader_cache); + break; + } + jobs.reserve(m_level_shader_jobs.size()); + for (const auto& item : m_level_shader_jobs) + jobs.push_back(item.second); + } + for (const xr_shared_ptr& job : jobs) + WaitForSingleObject(job->completed, INFINITE); + } + cache.clear(); +} + +void CResourceManager::ReleaseLevelShaderCache(LPCSTR canonical_level_path, u64 recipe_identity) +{ + if (!canonical_level_path || !canonical_level_path[0]) + return; + + string32 identity; + xr_sprintf(identity, "%016llx", recipe_identity); + xr_string suffix = "\n"; + suffix += canonical_level_path; + suffix += '\n'; + suffix += identity; + + xr_map released; + { + xrCriticalSectionGuard guard(creationGuard); + for (auto item = m_level_shader_cache.begin(); item != m_level_shader_cache.end();) + { + const xr_string& key = item->first; + if (key.size() < suffix.size() || key.compare(key.size() - suffix.size(), suffix.size(), suffix)) + { + ++item; + continue; + } + released.emplace(item->first, item->second); + item = m_level_shader_cache.erase(item); + } + } + released.clear(); +} + void CResourceManager::Dump(bool bBrief) { + xrCriticalSectionGuard guard(creationGuard); Msg("* RM_Dump: textures : %d", m_textures.size()); if (!bBrief) mdump(m_textures); Msg("* RM_Dump: rtargets : %d", m_rtargets.size()); diff --git a/src/Layers/xrRender/ResourceManager_Resources.cpp b/src/Layers/xrRender/ResourceManager_Resources.cpp index 00918a8ddc..d6cc7b88fd 100644 --- a/src/Layers/xrRender/ResourceManager_Resources.cpp +++ b/src/Layers/xrRender/ResourceManager_Resources.cpp @@ -47,7 +47,7 @@ BOOL reclaim(xr_vector& vec, const T* ptr) } //-------------------------------------------------------------------------------------------------------------- -SState* CResourceManager::_CreateState(SimulatorStates& state_code) +SState* CResourceManager::_CreateState(SimulatorStates& state_code, ref_state* keep_alive) { xrCriticalSectionGuard guard(creationGuard); // Search equal state-code @@ -75,7 +75,7 @@ void CResourceManager::_DeleteState(const SState* state) } //-------------------------------------------------------------------------------------------------------------- -SPass* CResourceManager::_CreatePass(const SPass& proto) +SPass* CResourceManager::_CreatePass(const SPass& proto, ref_pass* keep_alive) { xrCriticalSectionGuard guard(creationGuard); for (u32 it = 0; it < v_passes.size(); it++) @@ -146,7 +146,7 @@ void CResourceManager::_DeleteDecl(const SDeclaration* dcl) //-------------------------------------------------------------------------------------------------------------- #ifndef _EDITOR -SVS* CResourceManager::_CreateVS(LPCSTR _name) +SVS* CResourceManager::_CreateVS(LPCSTR _name, ref_vs* keep_alive) { xrCriticalSectionGuard guard(creationGuard); xr_string res_name = _name; @@ -241,7 +241,7 @@ void CResourceManager::_DeleteVS(const SVS* vs) #ifndef _EDITOR //-------------------------------------------------------------------------------------------------------------- -SPS* CResourceManager::_CreatePS(LPCSTR name) +SPS* CResourceManager::_CreatePS(LPCSTR name, ref_ps* keep_alive) { LPSTR N = LPSTR(name); xrCriticalSectionGuard guard(creationGuard); @@ -335,7 +335,7 @@ void CResourceManager::_DeletePS(const SPS* ps) Msg("! ERROR: Failed to find compiled pixel-shader '%s'", *ps->cName); } -R_constant_table* CResourceManager::_CreateConstantTable(R_constant_table& C) +R_constant_table* CResourceManager::_CreateConstantTable(R_constant_table& C, ref_ctable* keep_alive) { if (C.empty()) return NULL; @@ -411,7 +411,8 @@ void CResourceManager::DBG_VerifyGeoms() */ } -SGeometry* CResourceManager::CreateGeom(D3DVERTEXELEMENT9* decl, IDirect3DVertexBuffer9* vb, IDirect3DIndexBuffer9* ib) +SGeometry* CResourceManager::CreateGeom(D3DVERTEXELEMENT9* decl, IDirect3DVertexBuffer9* vb, IDirect3DIndexBuffer9* ib, + ref_geom* keep_alive) { xrCriticalSectionGuard guard(creationGuard); R_ASSERT(decl && vb); @@ -436,12 +437,13 @@ SGeometry* CResourceManager::CreateGeom(D3DVERTEXELEMENT9* decl, IDirect3DVertex return Geom; } -SGeometry* CResourceManager::CreateGeom(u32 FVF, IDirect3DVertexBuffer9* vb, IDirect3DIndexBuffer9* ib) +SGeometry* CResourceManager::CreateGeom(u32 FVF, IDirect3DVertexBuffer9* vb, IDirect3DIndexBuffer9* ib, + ref_geom* keep_alive) { D3DVERTEXELEMENT9 dcl [MAX_FVF_DECL_SIZE]; xrCriticalSectionGuard guard(creationGuard); CHK_DX(D3DXDeclaratorFromFVF(FVF,dcl)); - SGeometry* g = CreateGeom(dcl, vb, ib); + SGeometry* g = CreateGeom(dcl, vb, ib, keep_alive); return g; } @@ -454,13 +456,11 @@ void CResourceManager::DeleteGeom(const SGeometry* Geom) } //-------------------------------------------------------------------------------------------------------------- -xr_task_group textures_load_tasks; -CTexture* CResourceManager::_CreateTexture(LPCSTR _Name) +ref_texture CResourceManager::_CreateTexture(LPCSTR _Name, bool prefetch, LPCSTR canonical_level_path) { // DBG_VerifyTextures (); - if (0 == xr_strcmp(_Name, "null")) return 0; + if (0 == xr_strcmp(_Name, "null")) return ref_texture(); R_ASSERT(_Name && _Name[0]); - xrCriticalSectionGuard guard(creationGuard); string_path Name; xr_strcpy(Name, _Name); //. andy if (strext(Name)) *strext(Name)=0; fix_texture_name(Name); @@ -469,28 +469,40 @@ CTexture* CResourceManager::_CreateTexture(LPCSTR _Name) simplify_texture(Name); #endif // DEBUG - // ***** first pass - search already loaded texture - LPSTR N = LPSTR(Name); - map_TextureIt I = m_textures.find(N); - if (I != m_textures.end()) return I->second; - else + ref_texture texture; + bool queueLoad = false; { - CTexture* T = xr_new(); - T->dwFlags |= xr_resource_flagged::RF_REGISTERED; - m_textures.insert(mk_pair(T->set_name(Name), T)); - T->Preload(); - if (Device.b_is_Ready) + xrCriticalSectionGuard guard(creationGuard); + map_TextureIt I = m_textures.find(Name); + if (I != m_textures.end()) { - static DWORD this_thread_id = 0; - this_thread_id = GetCurrentThreadId(); - textures_load_tasks.run([=]() - { - if (this_thread_id != GetCurrentThreadId()) { PROF_THREAD("X-Ray PPL Thread") } - T->Load(); - }); + texture = ref_texture(I->second); + } + else + { + CTexture* T = xr_new(); + T->dwFlags |= xr_resource_flagged::RF_REGISTERED; + m_textures.insert(mk_pair(T->set_name(Name), T)); + T->Preload(); + texture = ref_texture(T); + } + + if (prefetch) + m_prefetchedTextures.emplace(texture._get(), texture); + else + m_prefetchedTextures.erase(texture._get()); + + queueLoad = Device.b_is_Ready; + if (!queueLoad && !texture->is_loaded()) + { + m_deferredTextureLoads.push_back(texture); } - return T; } + + if (queueLoad) + QueueTextureLoad(texture); + + return texture; } void CResourceManager::_DeleteTexture(const CTexture* T) @@ -598,7 +610,7 @@ bool cmp_tl(const std::pair& _1, const std::pairAddRef(); _RELEASE(pSurface); @@ -67,10 +65,7 @@ void CTexture::surface_set(ID3DBaseTexture* surf) ID3DBaseTexture* CTexture::surface_get() { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (pSurface) pSurface->AddRef(); return pSurface; } @@ -86,7 +81,7 @@ void CTexture::PostLoad() void CTexture::apply_load(u32 dwStage) { - if (!flags.bLoaded) Load(); + if (!is_loaded()) Load(); else PostLoad(); if (bind == xr_make_delegate(this, &CTexture::apply_load)) { @@ -103,10 +98,7 @@ void CTexture::apply_load(u32 dwStage) void CTexture::apply_theora(u32 dwStage) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (pTheora->Update(m_play_time != 0xFFFFFFFF ? m_play_time : RDEVICE.dwTimeContinual)) { R_ASSERT(D3DRTYPE_TEXTURE == pSurface->GetType()); @@ -132,10 +124,7 @@ void CTexture::apply_theora(u32 dwStage) void CTexture::apply_avi(u32 dwStage) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (pAVI->NeedUpdate()) { R_ASSERT(D3DRTYPE_TEXTURE == pSurface->GetType()); @@ -158,10 +147,7 @@ void CTexture::apply_avi(u32 dwStage) void CTexture::apply_seq(u32 dwStage) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); // SEQ u32 frame = RDEVICE.dwTimeContinual / seqMSPF; //RDEVICE.dwTimeGlobal u32 frame_data = seqDATA.size(); @@ -181,10 +167,7 @@ void CTexture::apply_seq(u32 dwStage) void CTexture::apply_gif(u32 dwStage) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (gifPlayer->UpdateFrame()) { const CGIFAnimationPlayer::Frame* const gifFrame = gifPlayer->GetActiveFrame(); @@ -197,10 +180,7 @@ void CTexture::apply_gif(u32 dwStage) void CTexture::apply_normal(u32 dwStage) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); dwLastUsedFrame = Device.dwFrame; CHK_DX(HW.pDevice->SetTexture(dwStage,pSurface)); }; @@ -211,17 +191,119 @@ void CTexture::Preload() m_material = DEV->m_textures_description.GetMaterial(cName); } +bool CTexture::TryQueueLoad() +{ + u32 expected = LoadStateUnloaded; + return loadState.compare_exchange_strong(expected, LoadStateQueued, std::memory_order_acq_rel, + std::memory_order_acquire); +} + +void CTexture::CancelQueuedLoad() +{ + u32 expected = LoadStateQueued; + loadState.compare_exchange_strong(expected, LoadStateUnloaded, std::memory_order_acq_rel, + std::memory_order_acquire); +} + +bool CTexture::CanLoadAsync() const +{ + u32 kind = loadKind.load(std::memory_order_acquire); + if (!kind) + { + string_path path; + kind = FS.exist(path, "$game_textures$", *cName, ".ogm") || + FS.exist(path, "$game_textures$", *cName, ".avi") || + FS.exist(path, "$game_textures$", *cName, ".seq") || + FS.exist(path, "$game_textures$", *cName, ".gif") ? 2u : 1u; + loadKind.store(kind, std::memory_order_release); + } + return kind == 1; +} + +bool CTexture::is_loaded() const +{ + return loadState.load(std::memory_order_acquire) == LoadStateLoaded; +} + +void CTexture::wait_for_loading() const +{ + for (;;) + { + const u32 state = loadState.load(std::memory_order_acquire); + if (state != LoadStateQueued && state != LoadStateLoading && state != LoadStateUnloading) + return; + if (state == LoadStateQueued && DEV && DEV->IsTextureOwnerThread()) + { + const_cast(this)->Load(); + continue; + } + SwitchToThread(); + } +} + +bool CTexture::BeginLoad(bool queued) +{ + for (;;) + { + u32 expected = queued ? LoadStateQueued : LoadStateUnloaded; + if (loadState.compare_exchange_strong(expected, LoadStateLoading, std::memory_order_acq_rel, + std::memory_order_acquire)) + { + return true; + } + if (!queued && expected == LoadStateQueued) + { + expected = LoadStateQueued; + if (loadState.compare_exchange_strong(expected, LoadStateLoading, std::memory_order_acq_rel, + std::memory_order_acquire)) + { + return true; + } + } + + if (expected == LoadStateLoaded || expected == LoadStateFailed || (queued && expected == LoadStateUnloaded)) + return false; + + wait_for_loading(); + } +} + +void CTexture::FinishLoad() +{ + flags.bLoaded = true; + loadState.store(LoadStateLoaded, std::memory_order_release); +} + +void CTexture::FailLoad() +{ + loadState.store(LoadStateUnloading, std::memory_order_release); + ReleaseLoadedData(); + loadState.store(LoadStateFailed, std::memory_order_release); +} + void CTexture::Load() +{ + Load(false); +} + +void CTexture::LoadQueued() +{ + Load(true); +} + +void CTexture::Load(bool queued) { PROF_EVENT("CTexture::Load"); - if (flags.bLoaded || flags.bLoading) return; - flags.bLoading = true; + if (!BeginLoad(queued)) + return; + try + { + flags.bLoaded = false; desc_cache = 0; if (pSurface) { - flags.bLoading = false; - flags.bLoaded = true; + FinishLoad(); return; } @@ -229,15 +311,13 @@ void CTexture::Load() flags.MemoryUsage = 0; if (0==_stricmp(*cName,"$null")) { - flags.bLoading = false; - flags.bLoaded = true; + FinishLoad(); return; } if (0!=strstr(*cName,"$user$")) { flags.bUser = true; - flags.bLoading = false; - flags.bLoaded = true; + FinishLoad(); return; } @@ -384,21 +464,39 @@ void CTexture::Load() //#endif } PostLoad(); - flags.bLoading = false; - flags.bLoaded = true; + FinishLoad(); + } + catch (...) + { + FailLoad(); + throw; + } } void CTexture::Unload() { - while (flags.bLoading) + for (;;) { - SwitchToThread(); + u32 state = loadState.load(std::memory_order_acquire); + if (state == LoadStateUnloaded || state == LoadStateFailed) + return; + if (state == LoadStateQueued || state == LoadStateLoading || state == LoadStateUnloading) + { + wait_for_loading(); + continue; + } + if (loadState.compare_exchange_strong(state, LoadStateUnloading, std::memory_order_acq_rel, + std::memory_order_acquire)) + { + break; + } } + ReleaseLoadedData(); + loadState.store(LoadStateUnloaded, std::memory_order_release); +} - // Already unloaded or never loaded: nothing to do. - if (!flags.bLoaded) - return; - +void CTexture::ReleaseLoadedData() +{ #ifdef DEBUG string_path msg_buff; xr_sprintf (msg_buff,sizeof(msg_buff),"* Unloading texture [%s] pSurface RefCount=",cName.c_str()); @@ -438,10 +536,7 @@ void CTexture::Unload() void CTexture::desc_update() { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); desc_cache = pSurface; if (pSurface && (D3DRTYPE_TEXTURE == pSurface->GetType())) { @@ -452,36 +547,24 @@ void CTexture::desc_update() void CTexture::video_Play(BOOL looped, u32 _time) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (pTheora) pTheora->Play(looped, (_time != 0xFFFFFFFF) ? (m_play_time = _time) : RDEVICE.dwTimeContinual); } void CTexture::video_Pause(BOOL state) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (pTheora) pTheora->Pause(state); } void CTexture::video_Stop() { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (pTheora) pTheora->Stop(); } BOOL CTexture::video_IsPlaying() { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); return (pTheora) ? pTheora->IsPlaying() : FALSE; } diff --git a/src/Layers/xrRender/SH_Texture.h b/src/Layers/xrRender/SH_Texture.h index ef921bbc2a..f4a0d0249b 100644 --- a/src/Layers/xrRender/SH_Texture.h +++ b/src/Layers/xrRender/SH_Texture.h @@ -35,8 +35,31 @@ class ECORE_API CTexture : public xr_resource_named void Preload(); void Load(); + void LoadQueued(); void PostLoad(); void Unload(void); + void Bind(u32 stage) + { + wait_for_loading(); + bind(stage); + } + bool TryQueueLoad(); + void CancelQueuedLoad(); + bool CanLoadAsync() const; + bool is_loaded() const; + void wait_for_loading() const; +#if defined(USE_DX10) || defined(USE_DX11) + enum ELoadKind : u32 + { + LoadKindUnknown, + LoadKindDds, + LoadKindOgm, + LoadKindAvi, + LoadKindSequence, + LoadKindGif, + }; + void SetLoadSource(LPCSTR logical_name, LPCSTR resolved_path, ELoadKind kind); +#endif // void Apply (u32 dwStage); void surface_set(ID3DBaseTexture* surf); @@ -69,7 +92,21 @@ class ECORE_API CTexture : public xr_resource_named #endif // USE_DX10 private: - IC void wait_for_loading() const { while (flags.bLoading){SwitchToThread();} } + enum ELoadState : u32 + { + LoadStateUnloaded, + LoadStateQueued, + LoadStateLoading, + LoadStateLoaded, + LoadStateUnloading, + LoadStateFailed, + }; + + void Load(bool queued); + bool BeginLoad(bool queued); + void FinishLoad(); + void FailLoad(); + void ReleaseLoadedData(); IC BOOL desc_valid() { wait_for_loading(); return pSurface==desc_cache; } IC void desc_enshure() { wait_for_loading(); if (!desc_valid()) desc_update(); } void desc_update(); @@ -84,7 +121,6 @@ class ECORE_API CTexture : public xr_resource_named struct { u32 bLoaded : 1; - u32 bLoading : 1; u32 bUser : 1; u32 seqCycles : 1; u32 MemoryUsage : 27; @@ -92,6 +128,8 @@ class ECORE_API CTexture : public xr_resource_named u32 bLoadedAsStaging: 1; #endif // USE_DX10 } flags; + xr_atomic_u32 loadState; + mutable xr_atomic_u32 loadKind; u32 dwLastUsedFrame = 0; // frame index of last Apply() call — used for eviction @@ -125,6 +163,8 @@ class ECORE_API CTexture : public xr_resource_named #if defined(USE_DX10) || defined(USE_DX11) ID3DShaderResourceView* m_pSRView; + shared_str m_loadName; + shared_str m_resolvedSourcePath; // Sequence view data xr_vectorm_seqSRView; #endif // USE_DX10 @@ -134,8 +174,8 @@ struct resptrcode_texture : public resptr_base { void create(LPCSTR _name); void destroy() { _set(NULL); } - shared_str bump_get() { while (_get() && _get()->flags.bLoading) { SwitchToThread(); }return _get()->m_bumpmap; } - bool bump_exist() { while (_get() && _get()->flags.bLoading) { SwitchToThread(); }return 0!=bump_get().size(); } + shared_str bump_get() { return _get() ? _get()->m_bumpmap : shared_str(); } + bool bump_exist() { return 0 != bump_get().size(); } }; typedef resptr_core diff --git a/src/Layers/xrRender/Shader.cpp b/src/Layers/xrRender/Shader.cpp index 937ff77450..208a0262a9 100644 --- a/src/Layers/xrRender/Shader.cpp +++ b/src/Layers/xrRender/Shader.cpp @@ -68,15 +68,28 @@ void resptrcode_shader::create(IBlender* B, LPCSTR s_shader, LPCSTR s_textures, _set(DEV->Create(B, s_shader, s_textures, s_constants, s_matrices)); } +void resptrcode_shader::create_parallel( + IBlender* B, LPCSTR s_shader, LPCSTR s_textures, LPCSTR s_constants, LPCSTR s_matrices) +{ +#ifdef SPAWN_ANTIFREEZE + xrCriticalSectionGuard g(shaderCreate_cs); +#endif + _set(DEV->Create(B, s_shader, s_textures, s_constants, s_matrices)); +} + ////////////////////////////////////////////////////////////////////////// void resptrcode_geom::create(u32 FVF, ID3DVertexBuffer* vb, ID3DIndexBuffer* ib) { - _set(DEV->CreateGeom(FVF, vb, ib)); + ref_geom keep_alive; + DEV->CreateGeom(FVF, vb, ib, &keep_alive); + _set(keep_alive); } void resptrcode_geom::create(D3DVERTEXELEMENT9* decl, ID3DVertexBuffer* vb, ID3DIndexBuffer* ib) { - _set(DEV->CreateGeom(decl, vb, ib)); + ref_geom keep_alive; + DEV->CreateGeom(decl, vb, ib, &keep_alive); + _set(keep_alive); } ////////////////////////////////////////////////////////////////////// diff --git a/src/Layers/xrRender/Shader.h b/src/Layers/xrRender/Shader.h index 76fa848888..efd565eca3 100644 --- a/src/Layers/xrRender/Shader.h +++ b/src/Layers/xrRender/Shader.h @@ -184,6 +184,8 @@ struct ECORE_API resptrcode_shader : public resptr_base { void create(LPCSTR s_shader = 0, LPCSTR s_textures = 0, LPCSTR s_constants = 0, LPCSTR s_matrices = 0); void create(IBlender* B, LPCSTR s_shader = 0, LPCSTR s_textures = 0, LPCSTR s_constants = 0, LPCSTR s_matrices = 0); + void create_parallel(IBlender* B, LPCSTR s_shader = 0, LPCSTR s_textures = 0, LPCSTR s_constants = 0, + LPCSTR s_matrices = 0); void destroy() { _set(NULL); } }; diff --git a/src/Layers/xrRender/ShaderResourceTraits.h b/src/Layers/xrRender/ShaderResourceTraits.h index c3a18e9899..16741150a5 100644 --- a/src/Layers/xrRender/ShaderResourceTraits.h +++ b/src/Layers/xrRender/ShaderResourceTraits.h @@ -73,62 +73,77 @@ inline CResourceManager::map_HS& CResourceManager::GetShaderMap() { return m_hs; template <> inline CResourceManager::map_CS& CResourceManager::GetShaderMap() { return m_cs; } -template -inline T* CResourceManager::CreateShader(const char* name) +template +inline T* CResourceManager::CreateShader(const char* name, Ref* keep_alive) { - xrCriticalSectionGuard guard(creationGuard); + xrCriticalSectionGuard shader_guard(shader_creation_guard(name)); ShaderTypeTraits::MapType& sh_map = GetShaderMap::MapType>(); LPSTR N = LPSTR(name); - ShaderTypeTraits::MapType::iterator I = sh_map.find(N); - - if (I != sh_map.end()) - return I->second; - else { - T* sh = xr_new(); + xrCriticalSectionGuard guard(creationGuard); + typename ShaderTypeTraits::MapType::iterator I = sh_map.find(N); + if (I != sh_map.end()) + { + if (keep_alive) + *keep_alive = I->second; + return I->second; + } + } - sh->dwFlags |= xr_resource_flagged::RF_REGISTERED; - sh_map.insert(mk_pair(sh->set_name(name), sh)); - if (0 == stricmp(name, "null")) + T* sh = xr_new(); + sh->dwFlags |= xr_resource_flagged::RF_REGISTERED; + sh->set_name(name); + if (0 == stricmp(name, "null")) + { { - sh->sh = NULL; - return sh; + xrCriticalSectionGuard guard(creationGuard); + sh_map.insert(mk_pair(*sh->cName, sh)); + if (keep_alive) + *keep_alive = sh; } + sh->sh = NULL; + return sh; + } - string_path shName; - const char* pchr = strchr(name, '('); - ptrdiff_t strSize = pchr ? pchr - name : xr_strlen(name); - strncpy(shName, name, strSize); - shName[strSize] = 0; + string_path shName; + const char* pchr = strchr(name, '('); + ptrdiff_t strSize = pchr ? pchr - name : xr_strlen(name); + strncpy(shName, name, strSize); + shName[strSize] = 0; - // Open file - string_path cname; - strconcat(sizeof(cname), cname, ::Render->getShaderPath(),/*name*/shName, ShaderTypeTraits::GetShaderExt()); - FS.update_path(cname, "$game_shaders$", cname); + // Open file + string_path cname; + strconcat(sizeof(cname), cname, ::Render->getShaderPath(),/*name*/shName, ShaderTypeTraits::GetShaderExt()); + FS.update_path(cname, "$game_shaders$", cname); - // duplicate and zero-terminate - IReader* file = FS.r_open(cname); - R_ASSERT2(file, cname); + // duplicate and zero-terminate + IReader* file = FS.r_open(cname); + R_ASSERT2(file, cname); - // Select target - LPCSTR c_target = ShaderTypeTraits::GetCompilationTarget(); - LPCSTR c_entry = "main"; + // Select target + LPCSTR c_target = ShaderTypeTraits::GetCompilationTarget(); + LPCSTR c_entry = "main"; - // Compile - HRESULT const _hr = ::Render->shader_compile(name, (DWORD const*)file->pointer(), file->length(), c_entry, - c_target, D3D10_SHADER_PACK_MATRIX_ROW_MAJOR, (void*&)sh); + // Compile + HRESULT const _hr = ::Render->shader_compile(name, (DWORD const*)file->pointer(), file->length(), c_entry, + c_target, D3D10_SHADER_PACK_MATRIX_ROW_MAJOR, (void*&)sh); - FS.r_close(file); + FS.r_close(file); - VERIFY(SUCCEEDED(_hr)); + VERIFY(SUCCEEDED(_hr)); - CHECK_OR_EXIT( - !FAILED(_hr), - make_string("Shader compilation failed, check your log file for additional information.") - ); + CHECK_OR_EXIT( + !FAILED(_hr), + make_string("Shader compilation failed, check your log file for additional information.") + ); - return sh; + { + xrCriticalSectionGuard guard(creationGuard); + sh_map.insert(mk_pair(*sh->cName, sh)); + if (keep_alive) + *keep_alive = sh; } + return sh; } template diff --git a/src/Layers/xrRender/TextureDescrManager.cpp b/src/Layers/xrRender/TextureDescrManager.cpp index c2c575f7c5..bc608593a9 100644 --- a/src/Layers/xrRender/TextureDescrManager.cpp +++ b/src/Layers/xrRender/TextureDescrManager.cpp @@ -35,24 +35,6 @@ void fix_texture_thm_name(LPSTR fn) *_ext = 0; } -struct TH_LoadTHM -{ - using map_TD = xr_map; - using map_CS = xr_map; - - LPCSTR initial; - map_TD& s_texture_details; - map_CS& s_detail_scalers; -}; - -void CTextureDescrMngr::LoadTHMThread(void* args) -{ - PROF_EVENT(); - - TH_LoadTHM* p = (TH_LoadTHM*)args; - LoadTHM(p->initial, p->s_texture_details, p->s_detail_scalers); -} - void CTextureDescrMngr::LoadTHM(LPCSTR initial, map_TD& s_texture_details, map_CS& s_detail_scalers) { PROF_EVENT(); @@ -60,79 +42,215 @@ void CTextureDescrMngr::LoadTHM(LPCSTR initial, map_TD& s_texture_details, map_C FS_FileSet flist; FS.file_list(flist, initial, FS_ListFiles, "*.thm"); - STextureParams tp; - string_path fn; + struct PreparedThm + { + xr_string file; + xr_string name; + xr_string detail_name; + xr_string bump_name; + u32 flags = STextureParams::flGenerateMipMaps | STextureParams::flDitherColor; + u32 material = STextureParams::tmBlin_Phong; + float detail_scale = 1.f; + float material_weight = 0.f; + STextureParams::ETType type = STextureParams::ttImage; + STextureParams::ETBumpMode bump_mode = STextureParams::tbmNone; + }; + CTimer timer; + timer.Start(); + xr_vector prepared(flist.size()); + u32 sourceIndex = 0; for (const FS_File& fs_iter : flist) { - FS.update_path(fn, initial, fs_iter.name.c_str()); - IReader* F = FS.r_open(fn); - xr_strcpy(fn, fs_iter.name.c_str()); - fix_texture_thm_name(fn); + PreparedThm& result = prepared[sourceIndex++]; + result.file = fs_iter.name.c_str(); + string_path name; + xr_strcpy(name, fs_iter.name.c_str()); + fix_texture_thm_name(name); + result.name = name; + } + + xr_parallel_for(0u, static_cast(prepared.size()), [&](u32 index) + { + PreparedThm& result = prepared[index]; + string_path path; + FS.update_path(path, initial, result.file.c_str()); + IReader* F = FS.r_open(path); R_ASSERT(F->find_chunk(THM_CHUNK_TYPE)); F->r_u32(); - tp.Clear(); - tp.Load(*F); + R_ASSERT(F->find_chunk(THM_CHUNK_TEXTUREPARAM)); + F->r_u32(); + result.flags = F->r_u32(); + if (F->find_chunk(THM_CHUNK_TEXTURE_TYPE)) + result.type = static_cast(F->r_u32()); + if (F->find_chunk(THM_CHUNK_DETAIL_EXT)) + { + F->r_stringZ(result.detail_name); + result.detail_scale = F->r_float(); + } + if (F->find_chunk(THM_CHUNK_MATERIAL)) + { + result.material = F->r_u32(); + result.material_weight = F->r_float(); + } + if (F->find_chunk(THM_CHUNK_BUMP)) + { + F->r_float(); + result.bump_mode = static_cast(F->r_u32()); + if (result.bump_mode < STextureParams::tbmNone) + result.bump_mode = STextureParams::tbmNone; + F->r_stringZ(result.bump_name); + } FS.r_close(F); - if (STextureParams::ttImage == tp.type || STextureParams::ttTerrain == tp.type || STextureParams::ttNormalMap == - tp.type) + }); + const u32 prepareMs = timer.GetElapsed_ms(); + + for (const PreparedThm& result : prepared) + { + if (STextureParams::ttImage == result.type || STextureParams::ttTerrain == result.type || + STextureParams::ttNormalMap == result.type) { - texture_desc& desc = s_texture_details[fn]; - cl_dt_scaler*& dts = s_detail_scalers[fn]; + texture_desc& desc = s_texture_details[result.name.c_str()]; + cl_dt_scaler*& dts = s_detail_scalers[result.name.c_str()]; - if (tp.detail_name.size() && tp.flags.is_any(STextureParams::flDiffuseDetail | STextureParams::flBumpDetail) - ) + if (!result.detail_name.empty() && + (result.flags & (STextureParams::flDiffuseDetail | STextureParams::flBumpDetail))) { if (desc.m_assoc) xr_delete(desc.m_assoc); desc.m_assoc = xr_new(); - desc.m_assoc->detail_name = tp.detail_name; + desc.m_assoc->detail_name = result.detail_name.c_str(); if (dts) - dts->scale = tp.detail_scale; + dts->scale = result.detail_scale; else - /*desc.m_assoc->cs*/dts = xr_new(tp.detail_scale); + /*desc.m_assoc->cs*/dts = xr_new(result.detail_scale); desc.m_assoc->usage = 0; - if (tp.flags.is(STextureParams::flDiffuseDetail)) + if (result.flags & STextureParams::flDiffuseDetail) desc.m_assoc->usage |= (1 << 0); - if (tp.flags.is(STextureParams::flBumpDetail)) + if (result.flags & STextureParams::flBumpDetail) desc.m_assoc->usage |= (1 << 1); } if (desc.m_spec) xr_delete(desc.m_spec); desc.m_spec = xr_new(); - desc.m_spec->m_material = tp.material + (tp.material < 4 ? tp.material_weight : 0); + desc.m_spec->m_material = + result.material + (result.material < 4 ? result.material_weight : 0); desc.m_spec->m_use_steep_parallax = false; - if (tp.bump_mode == STextureParams::tbmUse) + if (result.bump_mode == STextureParams::tbmUse) { - desc.m_spec->m_bump_name = tp.bump_name; + desc.m_spec->m_bump_name = result.bump_name.c_str(); } - else if (tp.bump_mode == STextureParams::tbmUseParallax) + else if (result.bump_mode == STextureParams::tbmUseParallax) { - desc.m_spec->m_bump_name = tp.bump_name; + desc.m_spec->m_bump_name = result.bump_name.c_str(); desc.m_spec->m_use_steep_parallax = true; } } } + Msg("* [STARTUP/THM] source=%s files=%u prepare=%u ms commit=%u ms total=%u ms", + initial, static_cast(prepared.size()), prepareMs, timer.GetElapsed_ms() - prepareMs, timer.GetElapsed_ms()); } void CTextureDescrMngr::Load() { - TH_LoadTHM* gtex = new TH_LoadTHM({"$game_textures$", m_texture_details, m_detail_scalers}); - TH_LoadTHM* lvl = new TH_LoadTHM({"$level$", m_texture_details, m_detail_scalers}); - thread_spawn(LoadTHMThread, "X-Ray THM Loader 1", 0, gtex); - thread_spawn(LoadTHMThread, "X-Ray THM Loader 2", 0, lvl); - Sleep(5); + map_TD gameDetails; + map_TD levelDetails; + map_CS gameScalers; + map_CS levelScalers; + std::exception_ptr gameFailure; + std::exception_ptr levelFailure; + + xr_task_group scans; + scans.run([&]() + { + try + { + LoadTHM("$game_textures$", gameDetails, gameScalers); + } + catch (...) + { + gameFailure = std::current_exception(); + } + }); + scans.run([&]() + { + try + { + LoadTHM("$level$", levelDetails, levelScalers); + } + catch (...) + { + levelFailure = std::current_exception(); + } + }); + scans.wait(); + + auto clearTemporary = [](map_TD& details, map_CS& scalers) + { + for (auto& item : details) + { + xr_delete(item.second.m_assoc); + xr_delete(item.second.m_spec); + } + for (auto& item : scalers) + xr_delete(item.second); + details.clear(); + scalers.clear(); + }; + + if (gameFailure || levelFailure) + { + clearTemporary(gameDetails, gameScalers); + clearTemporary(levelDetails, levelScalers); + std::rethrow_exception(gameFailure ? gameFailure : levelFailure); + } + + xrSRWLockGuard dataGuard(m_data_lock); + auto merge = [&](map_TD& details, map_CS& scalers) + { + for (auto& item : details) + { + texture_desc& destination = m_texture_details[item.first]; + xr_delete(destination.m_assoc); + xr_delete(destination.m_spec); + destination.m_assoc = item.second.m_assoc; + destination.m_spec = item.second.m_spec; + item.second.m_assoc = nullptr; + item.second.m_spec = nullptr; + + auto sourceScaler = scalers.find(item.first); + if (sourceScaler == scalers.end()) + continue; + cl_dt_scaler*& destinationScaler = m_detail_scalers[item.first]; + if (destinationScaler) + { + destinationScaler->scale = sourceScaler->second->scale; + xr_delete(sourceScaler->second); + } + else + { + destinationScaler = sourceScaler->second; + } + sourceScaler->second = nullptr; + } + clearTemporary(details, scalers); + }; + + // Level THMs override game THMs deterministically, as intended by the old two-source load. + merge(gameDetails, gameScalers); + merge(levelDetails, levelScalers); } void CTextureDescrMngr::UnLoad() { + xrSRWLockGuard dataGuard(m_data_lock); for (auto& it : m_texture_details) { xr_delete(it.second.m_assoc); @@ -143,6 +261,7 @@ void CTextureDescrMngr::UnLoad() CTextureDescrMngr::~CTextureDescrMngr() { + xrSRWLockGuard dataGuard(m_data_lock); map_CS::iterator I = m_detail_scalers.begin(); map_CS::iterator E = m_detail_scalers.end(); @@ -154,6 +273,7 @@ CTextureDescrMngr::~CTextureDescrMngr() shared_str CTextureDescrMngr::GetBumpName(const shared_str& tex_name) const { + xrSRWLockGuard dataGuard(m_data_lock, true); map_TD::const_iterator I = m_texture_details.find(tex_name); if (I != m_texture_details.end()) { @@ -167,6 +287,7 @@ shared_str CTextureDescrMngr::GetBumpName(const shared_str& tex_name) const BOOL CTextureDescrMngr::UseSteepParallax(const shared_str& tex_name) const { + xrSRWLockGuard dataGuard(m_data_lock, true); map_TD::const_iterator I = m_texture_details.find(tex_name); if (I != m_texture_details.end()) { @@ -180,6 +301,7 @@ BOOL CTextureDescrMngr::UseSteepParallax(const shared_str& tex_name) const float CTextureDescrMngr::GetMaterial(const shared_str& tex_name) const { + xrSRWLockGuard dataGuard(m_data_lock, true); map_TD::const_iterator I = m_texture_details.find(tex_name); if (I != m_texture_details.end()) { @@ -193,6 +315,7 @@ float CTextureDescrMngr::GetMaterial(const shared_str& tex_name) const void CTextureDescrMngr::GetTextureUsage(const shared_str& tex_name, BOOL& bDiffuse, BOOL& bBump) const { + xrSRWLockGuard dataGuard(m_data_lock, true); map_TD::const_iterator I = m_texture_details.find(tex_name); if (I != m_texture_details.end()) { @@ -207,6 +330,7 @@ void CTextureDescrMngr::GetTextureUsage(const shared_str& tex_name, BOOL& bDiffu BOOL CTextureDescrMngr::GetDetailTexture(const shared_str& tex_name, LPCSTR& res, R_constant_setup* & CS) const { + xrSRWLockGuard dataGuard(m_data_lock, true); map_TD::const_iterator I = m_texture_details.find(tex_name); if (I != m_texture_details.end()) { diff --git a/src/Layers/xrRender/TextureDescrManager.h b/src/Layers/xrRender/TextureDescrManager.h index 65e4307879..7c911439cc 100644 --- a/src/Layers/xrRender/TextureDescrManager.h +++ b/src/Layers/xrRender/TextureDescrManager.h @@ -5,7 +5,6 @@ #include "ETextureParams.h" class cl_dt_scaler; -struct TH_LoadTHM; class CTextureDescrMngr { @@ -49,9 +48,9 @@ class CTextureDescrMngr private: map_TD m_texture_details; map_CS m_detail_scalers; + mutable xrSRWLock m_data_lock; static void LoadTHM(LPCSTR initial, map_TD& s_texture_details, map_CS& s_detail_scalers); - static void LoadTHMThread(void* args); public: ~CTextureDescrMngr(); diff --git a/src/Layers/xrRender/dxRenderDeviceRender.cpp b/src/Layers/xrRender/dxRenderDeviceRender.cpp index 911bfaea5f..831c420ea4 100644 --- a/src/Layers/xrRender/dxRenderDeviceRender.cpp +++ b/src/Layers/xrRender/dxRenderDeviceRender.cpp @@ -2,6 +2,7 @@ #include "dxRenderDeviceRender.h" #include "ResourceManager.h" +#include "../../xrCore/ShaderSourceCRC.h" dxRenderDeviceRender::dxRenderDeviceRender() : Resources(0) @@ -151,11 +152,17 @@ void dxRenderDeviceRender::SetupStates() void dxRenderDeviceRender::OnDeviceCreate(LPCSTR shName) { + CTimer startupTimer; + startupTimer.Start(); + // Signal everyone - device created RCache.OnDeviceCreate(); m_Gamma.Update(); + const u32 backendMs = startupTimer.GetElapsed_ms(); Resources->OnDeviceCreate(shName); + const u32 resourcesMs = startupTimer.GetElapsed_ms() - backendMs; ::Render->create(); + const u32 rendererMs = startupTimer.GetElapsed_ms() - backendMs - resourcesMs; Device.Statistic->OnDeviceCreate(); //#ifndef DEDICATED_SERVER @@ -167,12 +174,18 @@ void dxRenderDeviceRender::OnDeviceCreate(LPCSTR shName) DUImpl.OnDeviceCreate(); } //#endif + const u32 utilitiesMs = startupTimer.GetElapsed_ms() - backendMs - resourcesMs - rendererMs; + Msg("* [STARTUP/RENDER CREATE] backend=%u resources=%u renderer=%u utilities=%u total=%u ms", + backendMs, resourcesMs, rendererMs, utilitiesMs, startupTimer.GetElapsed_ms()); } void dxRenderDeviceRender::Create(HWND hWnd, u32& dwWidth, u32& dwHeight, float& fWidth_2, float& fHeight_2, bool move_window) { + CTimer startupTimer; + startupTimer.Start(); HW.CreateDevice(hWnd, move_window); + const u32 hardwareMs = startupTimer.GetElapsed_ms(); #if defined(USE_DX11) dwWidth = HW.m_ChainDesc.Width; dwHeight = HW.m_ChainDesc.Height; @@ -186,6 +199,8 @@ void dxRenderDeviceRender::Create(HWND hWnd, u32& dwWidth, u32& dwHeight, float& fWidth_2 = float(dwWidth / 2); fHeight_2 = float(dwHeight / 2); Resources = xr_new(); + Msg("* [STARTUP/RENDER DEVICE] hardware=%u manager=%u total=%u ms", hardwareMs, + startupTimer.GetElapsed_ms() - hardwareMs, startupTimer.GetElapsed_ms()); } void dxRenderDeviceRender::SetupGPU(BOOL bForceGPU_SW, BOOL bForceGPU_NonPure, BOOL bForceGPU_REF) @@ -267,6 +282,11 @@ void dxRenderDeviceRender::DeferredLoad(BOOL E) Resources->DeferredLoad(E); } +void dxRenderDeviceRender::ResourcesPrepareLoad() +{ + Resources->PrepareLoad(); +} + void dxRenderDeviceRender::ResourcesDeferredUpload() { Resources->DeferredUpload(); @@ -277,9 +297,24 @@ void dxRenderDeviceRender::ResourcesDeferredUnload() Resources->DeferredUnload(); } -void dxRenderDeviceRender::ResourcesPrefetchCreateTexture(LPCSTR name) +void dxRenderDeviceRender::ResourcesPrefetchCreateTexture(LPCSTR name, LPCSTR canonical_level_path) +{ + Resources->PrefetchTexture(name, canonical_level_path); +} + +u64 dxRenderDeviceRender::ResourcesBeginLoadGeneration() +{ + return Resources->BeginLoadGeneration(); +} + +void dxRenderDeviceRender::ResourcesAbortLoadGeneration(u64 generation) +{ + Resources->AbortLoadGeneration(generation); +} + +void dxRenderDeviceRender::ResourcesFinalizeLoadGeneration(u64 generation) { - Resources->_CreateTexture(name); + Resources->FinalizeLoadGeneration(generation); } xrCriticalSection resources_lock; @@ -422,6 +457,9 @@ void dxRenderDeviceRender::End() void dxRenderDeviceRender::ResourcesDestroyNecessaryTextures() { + // The last precache frame is the hard end of the active load generation: + // no first-use texture work may leak into interactive gameplay. + Resources->WaitForTextureLoads(); Resources->DestroyNecessaryTextures(); } @@ -456,6 +494,12 @@ bool dxRenderDeviceRender::HWSupportsShaderYUV2RGB() void dxRenderDeviceRender::OnAssetsChanged() { + Resources->WaitForTextureLoads(); + Resources->InvalidateTextureSourceCache(); + Resources->InvalidateLevelShaderCache(); + clearShaderSourceCrcCache(); + ::Render->level_InvalidateStaticCache(); + ::Render->models_InvalidatePrepared(); Resources->m_textures_description.UnLoad(); Resources->m_textures_description.Load(); } diff --git a/src/Layers/xrRender/dxRenderDeviceRender.h b/src/Layers/xrRender/dxRenderDeviceRender.h index 2c891d6f98..ec22efe3c2 100644 --- a/src/Layers/xrRender/dxRenderDeviceRender.h +++ b/src/Layers/xrRender/dxRenderDeviceRender.h @@ -48,13 +48,17 @@ class dxRenderDeviceRender : public IRenderDeviceRender // Resources control virtual void DeferredLoad(BOOL E); + virtual void ResourcesPrepareLoad(); virtual void ResourcesDeferredUpload(); virtual void ResourcesDeferredUnload(); virtual void ResourcesGetMemoryUsage(u32& m_base, u32& c_base, u32& m_lmaps, u32& c_lmaps); virtual void ResourcesDestroyNecessaryTextures(); virtual void ResourcesStoreNecessaryTextures(); virtual void ResourcesDumpMemoryUsage(); - virtual void ResourcesPrefetchCreateTexture(LPCSTR name); + virtual void ResourcesPrefetchCreateTexture(LPCSTR name, LPCSTR canonical_level_path = nullptr); + virtual u64 ResourcesBeginLoadGeneration(); + virtual void ResourcesAbortLoadGeneration(u64 generation); + virtual void ResourcesFinalizeLoadGeneration(u64 generation); // HWSupport virtual bool HWSupportsShaderYUV2RGB(); diff --git a/src/Layers/xrRender/light.cpp b/src/Layers/xrRender/light.cpp index a51f56be73..bea8cf9f32 100644 --- a/src/Layers/xrRender/light.cpp +++ b/src/Layers/xrRender/light.cpp @@ -6,7 +6,7 @@ extern int ps_r2_shadow_omnipart_vischeck; -light::light() +light::light(bool publish) { ISpatialOwner::spatial_create(g_SpatialSpaceLights, this, STYPE_LIGHTSOURCE); //ISpatialOwner::spatial_create(g_SpatialSpace, this, STYPE_LIGHTSOURCE); @@ -38,7 +38,14 @@ light::light() omnipart_num = 0; sss_id = -1; sss_refresh = 0; - sss_remove_latency = 0; + sss_remove_latency = 0; + sss_priority = 0; + sss_is_playerlight = false; + m_published = publish; + omipart_parent = nullptr; + distance = 0.f; + distance_lpos = 0.f; + m_moving_frames = 0; #if (RENDER==R_R2) || (RENDER==R_R3) || (RENDER==R_R4) @@ -49,11 +56,17 @@ light::light() CHK_DX(CreateQuery(&vis.Q, D3DQUERYTYPE_OCCLUSION)); vis.visible = true; vis.pending = false; + vis.smap_ID = 0; + vis.distance = 0.f; + m_xform_frame = u32(-1); + m_parent_p_frame = u32(-1); + m_parent_u_frame = u32(-1); m_sectors = {}; X.S.posX = 0; X.S.posY = 0; X.S.size = SMAP_adapt_max; - RImplementation.v_all_lights.emplace(this); + if (m_published) + RImplementation.v_all_lights.emplace(this); #endif // (RENDER==R_R2) || (RENDER==R_R3) || (RENDER==R_R4) } @@ -61,7 +74,8 @@ light::~light() { m_parent = nullptr; #if (RENDER==R_R2) || (RENDER==R_R3) || (RENDER==R_R4) - RImplementation.v_all_lights.erase(this); + if (m_published) + RImplementation.v_all_lights.erase(this); for (int f = 0; f < 6; f++) xr_delete(omnipart[f]); _RELEASE(vis.Q); #endif // (RENDER==R_R2) || (RENDER==R_R3) || (RENDER==R_R4) @@ -80,6 +94,11 @@ light::~light() void light::destroy(bool deffered) { set_active(false); + if (!m_published) + { + xr_delete(this); + return; + } if (deffered) { if (std::find(RImplementation.v_all_lights_dque.begin(), RImplementation.v_all_lights_dque.end(), this) == RImplementation.v_all_lights_dque.end()) @@ -89,6 +108,70 @@ void light::destroy(bool deffered) xr_delete(this); } +void light::publish_for_render() +{ + if (m_published) + return; +#if (RENDER==R_R2) || (RENDER==R_R3) || (RENDER==R_R4) + RImplementation.v_all_lights.emplace(this); + m_published = true; + for (light* child : omnipart) + if (child) + child->publish_for_render(); +#else + m_published = true; +#endif +} + +void light::reset_for_cache() +{ + set_active(false); + if (sss_on_light_destroy) + sss_on_light_destroy(this); + sss_on_light_destroy.clear(); + sss_id = -1; + sss_refresh = 0; + sss_remove_latency = 0; + sss_priority = 0; + sss_is_playerlight = false; + frame_render = 0; + m_moving_frames = 0; + omnipart_num = 0; + omipart_parent = nullptr; + SpatialComponent->spatial.sector = nullptr; +#if (RENDER==R_R2) || (RENDER==R_R3) || (RENDER==R_R4) + for (light* child : omnipart) + if (child) + child->reset_for_cache(); + if (m_published) + { + RImplementation.v_all_lights.erase(this); + m_published = false; + } + vis.frame2test = 0; + vis.visible = true; + vis.pending = false; + vis.smap_ID = 0; + vis.distance = 0.f; + m_xform_frame = 0; + m_parent_p_frame = 0; + m_parent_u_frame = 0; + distance = 0.f; + distance_lpos = 0.f; + indirect.clear_and_free(); +#if !defined(XRCPU_PIPE_EXPORTS) && !defined(_EDITOR) + GMLight.clear(); +#endif +#if !defined(XRCPU_PIPE_EXPORTS) + xrCriticalSectionGuard guard(§ors_lc); + m_sectors.clear(); +#endif +#else + m_published = false; +#endif + hom.clear(); +} + #if (RENDER==R_R2) || (RENDER==R_R3) || (RENDER==R_R4) void light::set_texture(LPCSTR name) { @@ -223,7 +306,7 @@ void light::set_shadow(bool b) { for (int f=0; f<6; f++) { - omnipart[f] = xr_new(); + omnipart[f] = xr_new(m_published); omnipart[f]->m_parent = this; omnipart[f]->set_type(IRender_Light::OMNIPART); omnipart[f]->set_shadow(true); @@ -335,6 +418,12 @@ void light::spatial_move() break; } + if (!m_published) + { + m_moving_frames = 0; + return; + } + // update spatial DB ISpatialOwner::spatial_move(); diff --git a/src/Layers/xrRender/light.h b/src/Layers/xrRender/light.h index e8a4ea464a..d53ae068a1 100644 --- a/src/Layers/xrRender/light.h +++ b/src/Layers/xrRender/light.h @@ -50,6 +50,7 @@ class light : s8 sss_priority; bool sss_is_playerlight; xr_delegate sss_on_light_destroy; + bool m_published; light* omipart_parent; float distance; @@ -206,9 +207,11 @@ class light : float get_LOD(); - light(); + explicit light(bool publish = true); virtual ~light(); virtual void destroy(bool deffered = true); + void publish_for_render(); + void reset_for_cache(); }; #endif // #define LAYERS_XRRENDER_LIGHT_H_INCLUDED diff --git a/src/Layers/xrRender/r_constants.cpp b/src/Layers/xrRender/r_constants.cpp index 6f1a611903..a6a3ae1549 100644 --- a/src/Layers/xrRender/r_constants.cpp +++ b/src/Layers/xrRender/r_constants.cpp @@ -292,3 +292,32 @@ BOOL R_constant_table::equal(R_constant_table& C) return TRUE; } + +u64 R_constant_table::hash() const +{ + u64 result = 1469598103934665603ull; + auto mix = [&result](u64 value) + { + for (u32 i = 0; i < sizeof(value); ++i) + { + result ^= static_cast(value >> (i * 8)); + result *= 1099511628211ull; + } + }; + for (const ref_constant& reference : table) + { + const R_constant& constant = *reference; + for (LPCSTR name = constant.name.c_str(); name && *name; ++name) + { + result ^= static_cast(*name); + result *= 1099511628211ull; + } + mix(constant.type); + mix(constant.destination); + mix((u64(constant.ps.index) << 16) | constant.ps.cls); + mix((u64(constant.vs.index) << 16) | constant.vs.cls); + mix((u64(constant.samp.index) << 16) | constant.samp.cls); + mix(reinterpret_cast(constant.handler)); + } + return result; +} diff --git a/src/Layers/xrRender/r_constants.h b/src/Layers/xrRender/r_constants.h index c9e9b93fe1..0902325780 100644 --- a/src/Layers/xrRender/r_constants.h +++ b/src/Layers/xrRender/r_constants.h @@ -219,6 +219,7 @@ class ECORE_API R_constant_table : public xr_resource_flagged BOOL equal(R_constant_table& C); BOOL equal(R_constant_table* C) { return equal(*C); } + u64 hash() const; BOOL empty() { return 0 == table.size(); } }; diff --git a/src/Layers/xrRender/stats_manager.cpp b/src/Layers/xrRender/stats_manager.cpp index d26930d0c1..e430d33f60 100644 --- a/src/Layers/xrRender/stats_manager.cpp +++ b/src/Layers/xrRender/stats_manager.cpp @@ -15,6 +15,7 @@ void stats_manager::increment_stats(u32 size, enum_stats_buffer_type type, _D3DP { if (g_dedicated_server) return; + xrCriticalSectionGuard guard(m_guard); R_ASSERT(type >= 0 && type < enum_stats_buffer_type_COUNT); R_ASSERT(location >= 0 && location <= D3DPOOL_SCRATCH); @@ -25,6 +26,7 @@ void stats_manager::increment_stats(u32 size, enum_stats_buffer_type type, _D3DP { if (g_dedicated_server) return; + xrCriticalSectionGuard guard(m_guard); R_ASSERT(buff_ptr != NULL); R_ASSERT(type >= 0 && type < enum_stats_buffer_type_COUNT); @@ -163,6 +165,7 @@ void stats_manager::decrement_stats(u32 size, enum_stats_buffer_type type, _D3DP { if (g_dedicated_server) return; + xrCriticalSectionGuard guard(m_guard); R_ASSERT(type >= 0 && type < enum_stats_buffer_type_COUNT); R_ASSERT(location >= 0 && location <= D3DPOOL_SCRATCH); @@ -173,6 +176,7 @@ void stats_manager::decrement_stats(u32 size, enum_stats_buffer_type type, _D3DP { if (buff_ptr == 0 || g_dedicated_server) return; + xrCriticalSectionGuard guard(m_guard); #ifdef DEBUG xr_vector::iterator it = m_buffers_list.begin(); diff --git a/src/Layers/xrRender/stats_manager.h b/src/Layers/xrRender/stats_manager.h index e8c63b4c65..9ce845bf1a 100644 --- a/src/Layers/xrRender/stats_manager.h +++ b/src/Layers/xrRender/stats_manager.h @@ -33,6 +33,7 @@ class stats_manager u32 memory_usage_summary[enum_stats_buffer_type_COUNT][4]; private: + xrCriticalSection m_guard; void increment_stats(u32 size, enum_stats_buffer_type type, _D3DPOOL location, void* buff_ptr); void decrement_stats(u32 size, enum_stats_buffer_type type, _D3DPOOL location, void* buff_ptr); diff --git a/src/Layers/xrRender/tss_def.cpp b/src/Layers/xrRender/tss_def.cpp index 79e6b96b7f..a2c5428e4c 100644 --- a/src/Layers/xrRender/tss_def.cpp +++ b/src/Layers/xrRender/tss_def.cpp @@ -99,6 +99,19 @@ BOOL SimulatorStates::equal(SimulatorStates& S) return TRUE; } +u64 SimulatorStates::hash() const +{ + u64 result = 1469598103934665603ull; + const u8* bytes = reinterpret_cast(States.data()); + const size_t size = States.size() * sizeof(State); + for (size_t i = 0; i < size; ++i) + { + result ^= bytes[i]; + result *= 1099511628211ull; + } + return result; +} + void SimulatorStates::clear() { States.clear(); diff --git a/src/Layers/xrRender/tss_def.h b/src/Layers/xrRender/tss_def.h index 846a54cd42..2b853aea3f 100644 --- a/src/Layers/xrRender/tss_def.h +++ b/src/Layers/xrRender/tss_def.h @@ -43,6 +43,7 @@ class SimulatorStates void set_TSS(u32 a, u32 b, u32 c); void set_SAMP(u32 a, u32 b, u32 c); BOOL equal(SimulatorStates& S); + u64 hash() const; void clear(); IDirect3DStateBlock9* record(); #if defined(USE_DX10) || defined(USE_DX11) diff --git a/src/Layers/xrRenderDX10/3DFluid/dx103DFluidData.cpp b/src/Layers/xrRenderDX10/3DFluid/dx103DFluidData.cpp index 33096c3697..d30dcba198 100644 --- a/src/Layers/xrRenderDX10/3DFluid/dx103DFluidData.cpp +++ b/src/Layers/xrRenderDX10/3DFluid/dx103DFluidData.cpp @@ -92,30 +92,43 @@ void dx103DFluidData::DestroyRTTextureAndViews(int rtIndex) _RELEASE(m_pRenderTargetViews[rtIndex]); } -void dx103DFluidData::Load(IReader* data) +void dx103DFluidData::Prepare(IReader* data, PreparedData& prepared) { // Version 3 - - xr_string Profile; - data->r_string(Profile); + data->r_string(prepared.profile); // Prepare transform - data->r(&m_Transform, sizeof(m_Transform)); + data->r(&prepared.transform, sizeof(prepared.transform)); // Read obstacles u32 uiObstCnt = data->r_u32(); - m_Obstacles.reserve(uiObstCnt); + prepared.obstacles.resize(uiObstCnt); for (u32 i = 0; i < uiObstCnt; ++i) - { - Fmatrix ObstTransform; - data->r(&ObstTransform, sizeof(ObstTransform)); - m_Obstacles.push_back(ObstTransform); - } + data->r(&prepared.obstacles[i], sizeof(prepared.obstacles[i])); + + ParseProfile(prepared.profile, prepared); +} + +void dx103DFluidData::Load(IReader* data) +{ + PreparedData prepared; + Prepare(data, prepared); + LoadPrepared(prepared); +} - ParseProfile(Profile); +void dx103DFluidData::LoadPrepared(const PreparedData& prepared) +{ + m_Transform = prepared.transform; + m_Obstacles = prepared.obstacles; + m_Emitters = prepared.emitters; + m_Settings = prepared.settings; + +#ifdef DEBUG + FluidManager.RegisterFluidData(this, prepared.profile); +#endif } -void dx103DFluidData::ParseProfile(const xr_string& Profile) +void dx103DFluidData::ParseProfile(const xr_string& Profile, PreparedData& prepared) { string_path fn; FS.update_path(fn, "$game_config$", Profile.c_str()); @@ -124,11 +137,11 @@ void dx103DFluidData::ParseProfile(const xr_string& Profile) Msg("Reading fog volume config: %s", fn); - m_Settings.m_SimulationType = ST_FOG; - m_Settings.m_fHemi = 0.2f; - m_Settings.m_fConfinementScale = 0.06f; - m_Settings.m_fDecay = 0.994f; - m_Settings.m_fGravityBuoyancy = 0.0f; + prepared.settings.m_SimulationType = ST_FOG; + prepared.settings.m_fHemi = 0.2f; + prepared.settings.m_fConfinementScale = 0.06f; + prepared.settings.m_fDecay = 0.994f; + prepared.settings.m_fGravityBuoyancy = 0.0f; Fmatrix WorldToFluid; { @@ -148,35 +161,35 @@ void dx103DFluidData::ParseProfile(const xr_string& Profile) // Actually it is mul(Translate, Scale). // Our matrix multiplication is not correct. TranslateScale.mul(Scale, Translate); - InvFluidTranform.invert(m_Transform); + InvFluidTranform.invert(prepared.transform); WorldToFluid.mul(TranslateScale, InvFluidTranform); } // Read Volume data if (ini.line_exist("volume", "Type")) - m_Settings.m_SimulationType = (SimulationType)ini.r_token("volume", "Type", simulation_type_token); + prepared.settings.m_SimulationType = (SimulationType)ini.r_token("volume", "Type", simulation_type_token); if (ini.line_exist("volume", "Hemi")) - m_Settings.m_fHemi = ini.r_float("volume", "Hemi"); + prepared.settings.m_fHemi = ini.r_float("volume", "Hemi"); if (ini.line_exist("volume", "ConfinementScale")) - m_Settings.m_fConfinementScale = ini.r_float("volume", "ConfinementScale"); + prepared.settings.m_fConfinementScale = ini.r_float("volume", "ConfinementScale"); if (ini.line_exist("volume", "Decay")) - m_Settings.m_fDecay = ini.r_float("volume", "Decay"); + prepared.settings.m_fDecay = ini.r_float("volume", "Decay"); if (ini.line_exist("volume", "GravityBuoyancy")) - m_Settings.m_fGravityBuoyancy = ini.r_float("volume", "GravityBuoyancy"); + prepared.settings.m_fGravityBuoyancy = ini.r_float("volume", "GravityBuoyancy"); u32 iEmittersNum = ini.r_u32("volume", "EmittersNum"); - m_Emitters.resize(iEmittersNum); + prepared.emitters.resize(iEmittersNum); for (u32 i = 0; i < iEmittersNum; ++i) { string32 EmitterSectionName; - CEmitter& Emitter = m_Emitters[i]; + CEmitter& Emitter = prepared.emitters[i]; ZeroMemory(&Emitter, sizeof(Emitter)); xr_sprintf(EmitterSectionName, "emitter%02d", i); @@ -218,17 +231,18 @@ void dx103DFluidData::ParseProfile(const xr_string& Profile) } } - // Allow real-time config reload -#ifdef DEBUG - FluidManager.RegisterFluidData(this, Profile); -#endif // DEBUG } // Allow real-time config reload #ifdef DEBUG void dx103DFluidData::ReparseProfile(const xr_string &Profile) { - m_Emitters.clear_not_free(); - ParseProfile(Profile); + PreparedData prepared; + prepared.profile = Profile; + prepared.transform = m_Transform; + ParseProfile(Profile, prepared); + m_Settings = prepared.settings; + m_Emitters.swap(prepared.emitters); + FluidManager.RegisterFluidData(this, Profile); } #endif // DEBUG diff --git a/src/Layers/xrRenderDX10/3DFluid/dx103DFluidData.h b/src/Layers/xrRenderDX10/3DFluid/dx103DFluidData.h index 1f9e9e47fb..40af51669c 100644 --- a/src/Layers/xrRenderDX10/3DFluid/dx103DFluidData.h +++ b/src/Layers/xrRenderDX10/3DFluid/dx103DFluidData.h @@ -31,11 +31,22 @@ class dx103DFluidData SimulationType m_SimulationType; }; + struct PreparedData + { + xr_string profile; + Fmatrix transform; + xr_vector obstacles; + xr_vector emitters; + Settings settings; + }; + public: dx103DFluidData(); ~dx103DFluidData(); + static void Prepare(IReader* data, PreparedData& prepared); void Load(IReader* data); + void LoadPrepared(const PreparedData& prepared); void SetTexture(eVolumePrivateRT id, ID3DTexture3D* pT) { @@ -81,7 +92,7 @@ class dx103DFluidData void CreateRTTextureAndViews(int rtIndex, D3D_TEXTURE3D_DESC TexDesc); void DestroyRTTextureAndViews(int rtIndex); - void ParseProfile(const xr_string& Profile); + static void ParseProfile(const xr_string& Profile, PreparedData& prepared); private: Fmatrix m_Transform; diff --git a/src/Layers/xrRenderDX10/3DFluid/dx103DFluidVolume.cpp b/src/Layers/xrRenderDX10/3DFluid/dx103DFluidVolume.cpp index 1171d18968..f8b4c098cd 100644 --- a/src/Layers/xrRenderDX10/3DFluid/dx103DFluidVolume.cpp +++ b/src/Layers/xrRenderDX10/3DFluid/dx103DFluidVolume.cpp @@ -11,11 +11,13 @@ dx103DFluidVolume::~dx103DFluidVolume() { } -void dx103DFluidVolume::Load(LPCSTR N, IReader* data, u32 dwFlags) +void dx103DFluidVolume::Prepare(IReader* data, PreparedData& prepared) { - // Uncomment this if choose to read from OGF - // dxRender_Visual::Load (N,data,dwFlags); + dx103DFluidData::Prepare(data, prepared); +} +void dx103DFluidVolume::InitializeVisual() +{ // Create shader for correct sort while rendering // shader name can't start from a digit shader.create("fluid3d_stub", "water\\water_ryaska1"); @@ -24,21 +26,34 @@ void dx103DFluidVolume::Load(LPCSTR N, IReader* data, u32 dwFlags) m_Geom.create(FVF::F_LIT, RCache.Vertex.Buffer(), RCache.QuadIB); Type = MT_3DFLUIDVOLUME; +} - // Version 3> - m_FluidData.Load(data); - - // Prepare transform - const Fmatrix& Transform = m_FluidData.GetTransform(); - - // Update visibility data +void dx103DFluidVolume::UpdateVisibility() +{ + const Fmatrix& transform = m_FluidData.GetTransform(); vis.box.min = Fvector3().set(-0.5f, -0.5f, -0.5f); vis.box.max = Fvector3().set(0.5f, 0.5f, 0.5f); - - vis.box.xform(Transform); - + vis.box.xform(transform); vis.box.getcenter(vis.sphere.P); vis.sphere.R = vis.box.getradius(); +} + +void dx103DFluidVolume::Load(LPCSTR N, IReader* data, u32 dwFlags) +{ + // Uncomment this if choose to read from OGF + // dxRender_Visual::Load (N,data,dwFlags); + InitializeVisual(); + + // Version 3> + m_FluidData.Load(data); + UpdateVisibility(); +} + +void dx103DFluidVolume::LoadPrepared(const PreparedData& prepared) +{ + InitializeVisual(); + m_FluidData.LoadPrepared(prepared); + UpdateVisibility(); /* // Version 2 diff --git a/src/Layers/xrRenderDX10/3DFluid/dx103DFluidVolume.h b/src/Layers/xrRenderDX10/3DFluid/dx103DFluidVolume.h index f4d7d43940..990816a562 100644 --- a/src/Layers/xrRenderDX10/3DFluid/dx103DFluidVolume.h +++ b/src/Layers/xrRenderDX10/3DFluid/dx103DFluidVolume.h @@ -8,10 +8,14 @@ class dx103DFluidVolume : public dxRender_Visual { public: + typedef dx103DFluidData::PreparedData PreparedData; + dx103DFluidVolume(); virtual ~dx103DFluidVolume(); + static void Prepare(IReader* data, PreparedData& prepared); virtual void Load(LPCSTR N, IReader* data, u32 dwFlags); + void LoadPrepared(const PreparedData& prepared); virtual void Render(float LOD); // LOD - Level Of Detail [0.0f - min, 1.0f - max], Ignored ? virtual void Copy(dxRender_Visual* pFrom); virtual void Release(); @@ -21,6 +25,9 @@ class dx103DFluidVolume : public dxRender_Visual ref_geom m_Geom; dx103DFluidData m_FluidData; + + void InitializeVisual(); + void UpdateVisibility(); }; #endif // dx103DFluidVolume_included diff --git a/src/Layers/xrRenderDX10/Blender_Recorder_R3.cpp b/src/Layers/xrRenderDX10/Blender_Recorder_R3.cpp index abcd7fd02b..995e22dc45 100644 --- a/src/Layers/xrRenderDX10/Blender_Recorder_R3.cpp +++ b/src/Layers/xrRenderDX10/Blender_Recorder_R3.cpp @@ -213,17 +213,17 @@ void CBlender_Compile::r_Pass(LPCSTR _vs, LPCSTR _gs, LPCSTR _ps, bool bFog, BOO PassSET_LightFog(FALSE, bFog); // Create shaders - SPS* ps = DEV->_CreatePS(_ps); - SVS* vs = DEV->_CreateVS(_vs); - SGS* gs = DEV->_CreateGS(_gs); - dest.ps = ps; - dest.vs = vs; - dest.gs = gs; + DEV->_CreatePS(_ps, &dest.ps); + DEV->_CreateVS(_vs, &dest.vs); + DEV->_CreateGS(_gs, &dest.gs); + SPS* ps = dest.ps._get(); + SVS* vs = dest.vs._get(); + SGS* gs = dest.gs._get(); #ifdef USE_DX11 - dest.hs = DEV->_CreateHS("null"); - dest.ds = DEV->_CreateDS("null"); + DEV->_CreateHS("null", &dest.hs); + DEV->_CreateDS("null", &dest.ds); // LVutner: add _CreateCS - dest.cs = DEV->_CreateCS("null"); + DEV->_CreateCS("null", &dest.cs); #endif ctable.merge(&ps->constants); ctable.merge(&vs->constants); @@ -243,8 +243,8 @@ void CBlender_Compile::r_TessPass(LPCSTR vs, LPCSTR hs, LPCSTR ds, LPCSTR gs, LP { r_Pass(vs, gs, ps, bFog, bZtest, bZwrite, bABlend, abSRC, abDST, aTest, aRef); - dest.hs = DEV->_CreateHS(hs); - dest.ds = DEV->_CreateDS(ds); + DEV->_CreateHS(hs, &dest.hs); + DEV->_CreateDS(ds, &dest.ds); ctable.merge(&dest.hs->constants); ctable.merge(&dest.ds->constants); @@ -254,7 +254,7 @@ void CBlender_Compile::r_ComputePass(LPCSTR cs) { ctable.clear(); - dest.cs = DEV->_CreateCS(cs); + DEV->_CreateCS(cs, &dest.cs); ctable.merge(&dest.cs->constants); } @@ -263,11 +263,13 @@ void CBlender_Compile::r_ComputePass(LPCSTR cs) void CBlender_Compile::r_End() { SetMapping(); - dest.constants = DEV->_CreateConstantTable(ctable); - dest.state = DEV->_CreateState(RS.GetContainer()); - dest.T = DEV->_CreateTextureList(passTextures); + DEV->_CreateConstantTable(ctable, &dest.constants); + DEV->_CreateState(RS.GetContainer(), &dest.state); + DEV->_CreateTextureList(passTextures, &dest.T); dest.C = 0; ref_matrix_list temp(0); - SH->passes.push_back(DEV->_CreatePass(dest)); + ref_pass pass; + DEV->_CreatePass(dest, &pass); + SH->passes.push_back(pass); //SH->passes.push_back (DEV->_CreatePass(dest.state,dest.ps,dest.vs,dest.gs,dest.constants,dest.T,temp,dest.C)); } diff --git a/src/Layers/xrRenderDX10/dx10HW.cpp b/src/Layers/xrRenderDX10/dx10HW.cpp index bde69ad8ca..b853cde7d2 100644 --- a/src/Layers/xrRenderDX10/dx10HW.cpp +++ b/src/Layers/xrRenderDX10/dx10HW.cpp @@ -301,11 +301,18 @@ extern u32 g_screenmode; void CHW::CreateDevice(HWND hwnd, bool move_window) { +#if defined(USE_DX11) + CTimer startupTimer; + startupTimer.Start(); +#endif #if defined(USE_DX10) || defined(USE_DX11) m_hWnd = hwnd; #endif m_move_window = move_window; CreateD3D(); +#if defined(USE_DX11) + const u32 factoryMs = startupTimer.GetElapsed_ms(); +#endif /* Partially implemented dynamic load typedef HRESULT _D3DxxCreateDeviceAndSwapChain( @@ -581,7 +588,9 @@ void CHW::CreateDevice(HWND hwnd, bool move_window) _RELEASE(context); // create swapchain + const u32 deviceMs = startupTimer.GetElapsed_ms() - factoryMs; R_CHK(m_pFactory->CreateSwapChainForHwnd(pDevice, m_hWnd, &sd, &sd_fullscreen, NULL, &m_pSwapChain)); + const u32 swapchainMs = startupTimer.GetElapsed_ms() - factoryMs - deviceMs; // setup colorspace // HDR10 (U10 output) -> DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 @@ -609,6 +618,7 @@ void CHW::CreateDevice(HWND hwnd, bool move_window) _RELEASE(swapchain3); R_CHK(pContext->QueryInterface(__uuidof(ID3DUserDefinedAnnotation), (void**)&pAnnotation)); + const u32 setupMs = startupTimer.GetElapsed_ms() - factoryMs - deviceMs - swapchainMs; #else R = D3DX10CreateDeviceAndSwapChain(m_pAdapter, @@ -700,6 +710,11 @@ void CHW::CreateDevice(HWND hwnd, bool move_window) Reset(hwnd); fill_vid_mode_list(this); } +#if defined(USE_DX11) + const u32 viewsMs = startupTimer.GetElapsed_ms() - factoryMs - deviceMs - swapchainMs - setupMs; + Msg("* [STARTUP/RENDER HW] factory=%u device=%u swapchain=%u setup=%u views=%u total=%u ms", + factoryMs, deviceMs, swapchainMs, setupMs, viewsMs, startupTimer.GetElapsed_ms()); +#endif // #ifndef _EDITOR diff --git a/src/Layers/xrRenderDX10/dx10ResourceManager_Resources.cpp b/src/Layers/xrRenderDX10/dx10ResourceManager_Resources.cpp index dd2557b08a..aceca462da 100644 --- a/src/Layers/xrRenderDX10/dx10ResourceManager_Resources.cpp +++ b/src/Layers/xrRenderDX10/dx10ResourceManager_Resources.cpp @@ -22,9 +22,9 @@ #include "../xrRender/ShaderResourceTraits.h" #ifdef USE_DX11 -SHS* CResourceManager::_CreateHS(LPCSTR Name) +SHS* CResourceManager::_CreateHS(LPCSTR Name, ref_hs* keep_alive) { - return CreateShader(Name); + return CreateShader(Name, keep_alive); } void CResourceManager::_DeleteHS(const SHS* HS) @@ -32,9 +32,9 @@ void CResourceManager::_DeleteHS(const SHS* HS) DestroyShader(HS); } -SDS* CResourceManager::_CreateDS(LPCSTR Name) +SDS* CResourceManager::_CreateDS(LPCSTR Name, ref_ds* keep_alive) { - return CreateShader(Name); + return CreateShader(Name, keep_alive); } void CResourceManager::_DeleteDS(const SDS* DS) @@ -42,9 +42,9 @@ void CResourceManager::_DeleteDS(const SDS* DS) DestroyShader(DS); } -SCS* CResourceManager::_CreateCS(LPCSTR Name) +SCS* CResourceManager::_CreateCS(LPCSTR Name, ref_cs* keep_alive) { - return CreateShader(Name); + return CreateShader(Name, keep_alive); } void CResourceManager::_DeleteCS(const SCS* CS) @@ -55,6 +55,16 @@ void CResourceManager::_DeleteCS(const SCS* CS) void fix_texture_name(LPSTR fn); +static xrCriticalSection shaderCreationGuards[64]; + +ECORE_API xrCriticalSection& shader_creation_guard(LPCSTR name) +{ + u32 hash = 2166136261u; + for (; *name; ++name) + hash = (hash ^ u8(*name)) * 16777619u; + return shaderCreationGuards[hash % std::size(shaderCreationGuards)]; +} + template BOOL reclaim(xr_vector& vec, const T* ptr) { @@ -69,18 +79,75 @@ BOOL reclaim(xr_vector& vec, const T* ptr) return FALSE; } -//-------------------------------------------------------------------------------------------------------------- -SState* CResourceManager::_CreateState(SimulatorStates& state_code) +template +void remove_indexed(xr_unordered_flat_map>& index, u64 hash, const T* value) { - xrCriticalSectionGuard guard(creationGuard); + auto bucket = index.find(hash); + if (bucket == index.end()) + return; + auto& values = bucket->second; + values.erase(std::remove(values.begin(), values.end(), value), values.end()); + if (values.empty()) + index.erase(bucket); +} - // Search equal state-code - for (u32 it = 0; it < v_states.size(); it++) +static u64 hash_pointer(u64 hash, const void* pointer) +{ + const uintptr_t value = reinterpret_cast(pointer); + for (u32 i = 0; i < sizeof(value); ++i) { - SState* C = v_states[it];; - SimulatorStates& base = C->state_code; - if (base.equal(state_code)) return C; + hash ^= static_cast(value >> (i * 8)); + hash *= 1099511628211ull; } + return hash; +} + +static u64 pass_hash(const SPass& pass) +{ + u64 hash = 1469598103934665603ull; + hash = hash_pointer(hash, pass.state._get()); + hash = hash_pointer(hash, pass.ps._get()); + hash = hash_pointer(hash, pass.vs._get()); + hash = hash_pointer(hash, pass.gs._get()); +#ifdef USE_DX11 + hash = hash_pointer(hash, pass.hs._get()); + hash = hash_pointer(hash, pass.ds._get()); + hash = hash_pointer(hash, pass.cs._get()); +#endif + hash = hash_pointer(hash, pass.constants._get()); + hash = hash_pointer(hash, pass.T._get()); + hash = hash_pointer(hash, pass.C._get()); +#ifdef _EDITOR + hash = hash_pointer(hash, pass.M._get()); +#endif + return hash; +} + +static u64 texture_list_hash(const STextureList& list) +{ + u64 hash = 1469598103934665603ull; + for (const auto& entry : list) + { + hash ^= entry.first; + hash *= 1099511628211ull; + hash = hash_pointer(hash, entry.second._get()); + } + return hash; +} + +//-------------------------------------------------------------------------------------------------------------- +SState* CResourceManager::_CreateState(SimulatorStates& state_code, ref_state* keep_alive) +{ + xrCriticalSectionGuard guard(creationGuard); + const u64 hash = state_code.hash(); + auto& candidates = m_state_index[hash]; + for (SState* candidate : candidates) + if (candidate->state_code.equal(state_code)) + { + if (keep_alive) + *keep_alive = candidate; + return candidate; + } // Create New v_states.push_back(xr_new()); @@ -91,6 +158,9 @@ SState* CResourceManager::_CreateState(SimulatorStates& state_code) v_states.back()->state = state_code.record(); #endif // USE_DX10 v_states.back()->state_code = state_code; + candidates.push_back(v_states.back()); + if (keep_alive) + *keep_alive = v_states.back(); return v_states.back(); } @@ -98,17 +168,24 @@ void CResourceManager::_DeleteState(const SState* state) { if (0 == (state->dwFlags & xr_resource_flagged::RF_REGISTERED)) return; xrCriticalSectionGuard guard(creationGuard); + remove_indexed(m_state_index, state->state_code.hash(), state); if (reclaim(v_states, state)) return; Msg("! ERROR: Failed to find compiled stateblock"); } //-------------------------------------------------------------------------------------------------------------- -SPass* CResourceManager::_CreatePass(const SPass& proto) +SPass* CResourceManager::_CreatePass(const SPass& proto, ref_pass* keep_alive) { xrCriticalSectionGuard guard(creationGuard); - for (u32 it = 0; it < v_passes.size(); it++) - if (v_passes[it]->equal(proto)) - return v_passes[it]; + const u64 hash = pass_hash(proto); + auto& candidates = m_pass_index[hash]; + for (SPass* candidate : candidates) + if (candidate->equal(proto)) + { + if (keep_alive) + *keep_alive = candidate; + return candidate; + } SPass* P = xr_new(); P->dwFlags |= xr_resource_flagged::RF_REGISTERED; @@ -129,6 +206,9 @@ SPass* CResourceManager::_CreatePass(const SPass& proto) P->C = proto.C; v_passes.push_back(P); + candidates.push_back(P); + if (keep_alive) + *keep_alive = P; return v_passes.back(); } @@ -136,14 +216,14 @@ void CResourceManager::_DeletePass(const SPass* P) { if (0 == (P->dwFlags & xr_resource_flagged::RF_REGISTERED)) return; xrCriticalSectionGuard guard(creationGuard); + remove_indexed(m_pass_index, pass_hash(*P), P); if (reclaim(v_passes, P)) return; Msg("! ERROR: Failed to find compiled pass"); } //-------------------------------------------------------------------------------------------------------------- -SVS* CResourceManager::_CreateVS(LPCSTR _name) +SVS* CResourceManager::_CreateVS(LPCSTR _name, ref_vs* keep_alive) { - xrCriticalSectionGuard guard(creationGuard); xr_string res_name = _name; const int m_skinning = Engine.External.GetSkinningMode(); @@ -154,18 +234,30 @@ SVS* CResourceManager::_CreateVS(LPCSTR _name) LPCSTR name = res_name.c_str(); LPSTR N = LPSTR(name); - map_VS::iterator I = m_vs.find(N); - if (I != m_vs.end()) return I->second; - else + xrCriticalSectionGuard shader_guard(shader_creation_guard(name)); { - SVS* _vs = xr_new(); - _vs->skinning = m_skinning; - _vs->dwFlags |= xr_resource_flagged::RF_REGISTERED; - m_vs.insert(mk_pair(_vs->set_name(name), _vs)); + xrCriticalSectionGuard guard(creationGuard); + map_VS::iterator I = m_vs.find(N); + if (I != m_vs.end()) + { + if (keep_alive) + *keep_alive = I->second; + return I->second; + } + } + + SVS* _vs = xr_new(); + _vs->skinning = m_skinning; + _vs->dwFlags |= xr_resource_flagged::RF_REGISTERED; + _vs->set_name(name); //_vs->vs = NULL; //_vs->signature = NULL; if (0 == stricmp(_name, "null")) { + xrCriticalSectionGuard guard(creationGuard); + m_vs.insert(mk_pair(*_vs->cName, _vs)); + if (keep_alive) + *keep_alive = _vs; return _vs; } @@ -227,8 +319,13 @@ SVS* CResourceManager::_CreateVS(LPCSTR _name) make_string("Shader compilation failed, check your log file for additional information.") ); - return _vs; + { + xrCriticalSectionGuard guard(creationGuard); + m_vs.insert(mk_pair(*_vs->cName, _vs)); + if (keep_alive) + *keep_alive = _vs; } + return _vs; } void CResourceManager::_DeleteVS(const SVS* vs) @@ -258,9 +355,8 @@ void CResourceManager::_DeleteVS(const SVS* vs) } //-------------------------------------------------------------------------------------------------------------- -SPS* CResourceManager::_CreatePS(LPCSTR _name) +SPS* CResourceManager::_CreatePS(LPCSTR _name, ref_ps* keep_alive) { - xrCriticalSectionGuard guard(creationGuard); string_path name; xr_strcpy(name, _name); if (0 == ::Render->m_MSAASample) xr_strcat(name, "_0"); @@ -272,16 +368,28 @@ SPS* CResourceManager::_CreatePS(LPCSTR _name) if (6 == ::Render->m_MSAASample) xr_strcat(name, "_6"); if (7 == ::Render->m_MSAASample) xr_strcat(name, "_7"); LPSTR N = LPSTR(name); - map_PS::iterator I = m_ps.find(N); - if (I != m_ps.end()) return I->second; - else + xrCriticalSectionGuard shader_guard(shader_creation_guard(name)); { - SPS* _ps = xr_new(); - _ps->dwFlags |= xr_resource_flagged::RF_REGISTERED; - m_ps.insert(mk_pair(_ps->set_name(name), _ps)); + xrCriticalSectionGuard guard(creationGuard); + map_PS::iterator I = m_ps.find(N); + if (I != m_ps.end()) + { + if (keep_alive) + *keep_alive = I->second; + return I->second; + } + } + + SPS* _ps = xr_new(); + _ps->dwFlags |= xr_resource_flagged::RF_REGISTERED; + _ps->set_name(name); if (0 == stricmp(_name, "null")) { _ps->ps = NULL; + xrCriticalSectionGuard guard(creationGuard); + m_ps.insert(mk_pair(*_ps->cName, _ps)); + if (keep_alive) + *keep_alive = _ps; return _ps; } @@ -357,8 +465,13 @@ SPS* CResourceManager::_CreatePS(LPCSTR _name) make_string("Shader compilation failed, check your log file for additional information.") ); - return _ps; + { + xrCriticalSectionGuard guard(creationGuard); + m_ps.insert(mk_pair(*_ps->cName, _ps)); + if (keep_alive) + *keep_alive = _ps; } + return _ps; } void CResourceManager::_DeletePS(const SPS* ps) @@ -376,63 +489,79 @@ void CResourceManager::_DeletePS(const SPS* ps) } //-------------------------------------------------------------------------------------------------------------- -SGS* CResourceManager::_CreateGS(LPCSTR name) +SGS* CResourceManager::_CreateGS(LPCSTR name, ref_gs* keep_alive) { - xrCriticalSectionGuard guard(creationGuard); + xrCriticalSectionGuard shader_guard(shader_creation_guard(name)); LPSTR N = LPSTR(name); - map_GS::iterator I = m_gs.find(N); - if (I != m_gs.end()) return I->second; - else { - SGS* _gs = xr_new(); - _gs->dwFlags |= xr_resource_flagged::RF_REGISTERED; - m_gs.insert(mk_pair(_gs->set_name(name), _gs)); - if (0 == stricmp(name, "null")) + xrCriticalSectionGuard guard(creationGuard); + map_GS::iterator I = m_gs.find(N); + if (I != m_gs.end()) { - _gs->gs = NULL; - return _gs; + if (keep_alive) + *keep_alive = I->second; + return I->second; } + } - // Open file - string_path cname; - strconcat(sizeof(cname), cname, ::Render->getShaderPath(), name, ".gs"); - FS.update_path(cname, "$game_shaders$", cname); - - // duplicate and zero-terminate - IReader* file = FS.r_open(cname); - // TODO: DX10: HACK: Implement all shaders. Remove this for PS - if (!file) + SGS* _gs = xr_new(); + _gs->dwFlags |= xr_resource_flagged::RF_REGISTERED; + _gs->set_name(name); + if (0 == stricmp(name, "null")) + { { - string1024 tmp; - // TODO: HACK: Test failure - //Memory.mem_compact(); - xr_sprintf(tmp, "DX10: %s is missing. Replace with stub_default.gs", cname); - Msg(tmp); - strconcat(sizeof(cname), cname, ::Render->getShaderPath(), "stub_default", ".gs"); - FS.update_path(cname, "$game_shaders$", cname); - file = FS.r_open(cname); + xrCriticalSectionGuard guard(creationGuard); + m_gs.insert(mk_pair(*_gs->cName, _gs)); + if (keep_alive) + *keep_alive = _gs; } + _gs->gs = NULL; + return _gs; + } - R_ASSERT2(file, cname); + // Open file + string_path cname; + strconcat(sizeof(cname), cname, ::Render->getShaderPath(), name, ".gs"); + FS.update_path(cname, "$game_shaders$", cname); - // Select target - LPCSTR c_target = "gs_4_0"; - LPCSTR c_entry = "main"; + // duplicate and zero-terminate + IReader* file = FS.r_open(cname); + // TODO: DX10: HACK: Implement all shaders. Remove this for PS + if (!file) + { + string1024 tmp; + xr_sprintf(tmp, "DX10: %s is missing. Replace with stub_default.gs", cname); + Msg(tmp); + strconcat(sizeof(cname), cname, ::Render->getShaderPath(), "stub_default", ".gs"); + FS.update_path(cname, "$game_shaders$", cname); + file = FS.r_open(cname); + } - HRESULT const _hr = ::Render->shader_compile(name, (DWORD const*)file->pointer(), file->length(), c_entry, - c_target, D3D10_SHADER_PACK_MATRIX_ROW_MAJOR, (void*&)_gs); + R_ASSERT2(file, cname); - VERIFY(SUCCEEDED(_hr)); + // Select target + LPCSTR c_target = "gs_4_0"; + LPCSTR c_entry = "main"; - FS.r_close(file); + HRESULT const _hr = ::Render->shader_compile(name, (DWORD const*)file->pointer(), file->length(), c_entry, + c_target, D3D10_SHADER_PACK_MATRIX_ROW_MAJOR, (void*&)_gs); - CHECK_OR_EXIT( - !FAILED(_hr), - make_string("Shader compilation failed, check your log file for additional information.") - ); + VERIFY(SUCCEEDED(_hr)); - return _gs; + FS.r_close(file); + + CHECK_OR_EXIT( + !FAILED(_hr), + make_string("Shader compilation failed, check your log file for additional information.") + ); + + { + xrCriticalSectionGuard guard(creationGuard); + m_gs.insert(mk_pair(*_gs->cName, _gs)); + if (keep_alive) + *keep_alive = _gs; } + return _gs; } void CResourceManager::_DeleteGS(const SGS* gs) @@ -490,20 +619,28 @@ void CResourceManager::_DeleteDecl(const SDeclaration* dcl) } //-------------------------------------------------------------------------------------------------------------- -R_constant_table* CResourceManager::_CreateConstantTable(R_constant_table& C) +R_constant_table* CResourceManager::_CreateConstantTable(R_constant_table& C, ref_ctable* keep_alive) { if (C.empty()) return NULL; xrCriticalSectionGuard guard(creationGuard); - - for (u32 it = 0; it < v_constant_tables.size(); it++) - if (v_constant_tables[it]->equal(C)) - return v_constant_tables[it]; + const u64 hash = C.hash(); + auto& candidates = m_constant_table_index[hash]; + for (R_constant_table* candidate : candidates) + if (candidate->equal(C)) + { + if (keep_alive) + *keep_alive = candidate; + return candidate; + } auto NewElem = xr_new(C); //NewElem->_copy(C); NewElem->dwFlags |= xr_resource_flagged::RF_REGISTERED; v_constant_tables.push_back(NewElem); + candidates.push_back(NewElem); + if (keep_alive) + *keep_alive = NewElem; return NewElem; return v_constant_tables.back(); @@ -513,6 +650,7 @@ void CResourceManager::_DeleteConstantTable(const R_constant_table* C) { if (0 == (C->dwFlags & xr_resource_flagged::RF_REGISTERED)) return; xrCriticalSectionGuard guard(creationGuard); + remove_indexed(m_constant_table_index, C->hash(), C); if (reclaim(v_constant_tables, C)) return; Msg("! ERROR: Failed to find compiled constant-table"); } @@ -577,7 +715,8 @@ void CResourceManager::DBG_VerifyGeoms() */ } -SGeometry* CResourceManager::CreateGeom(D3DVERTEXELEMENT9* decl, ID3DVertexBuffer* vb, ID3DIndexBuffer* ib) +SGeometry* CResourceManager::CreateGeom(D3DVERTEXELEMENT9* decl, ID3DVertexBuffer* vb, ID3DIndexBuffer* ib, + ref_geom* keep_alive) { xrCriticalSectionGuard guard(creationGuard); R_ASSERT(decl && vb); @@ -589,7 +728,12 @@ SGeometry* CResourceManager::CreateGeom(D3DVERTEXELEMENT9* decl, ID3DVertexBuffe for (u32 it = 0; it < v_geoms.size(); it++) { SGeometry& G = *(v_geoms[it]); - if ((G.dcl == dcl) && (G.vb == vb) && (G.ib == ib) && (G.vb_stride == vb_stride)) return v_geoms[it]; + if ((G.dcl == dcl) && (G.vb == vb) && (G.ib == ib) && (G.vb_stride == vb_stride)) + { + if (keep_alive) + *keep_alive = v_geoms[it]; + return v_geoms[it]; + } } SGeometry* Geom = xr_new(); @@ -599,15 +743,17 @@ SGeometry* CResourceManager::CreateGeom(D3DVERTEXELEMENT9* decl, ID3DVertexBuffe Geom->vb_stride = vb_stride; Geom->ib = ib; v_geoms.push_back(Geom); + if (keep_alive) + *keep_alive = Geom; return Geom; } -SGeometry* CResourceManager::CreateGeom(u32 FVF, ID3DVertexBuffer* vb, ID3DIndexBuffer* ib) +SGeometry* CResourceManager::CreateGeom(u32 FVF, ID3DVertexBuffer* vb, ID3DIndexBuffer* ib, ref_geom* keep_alive) { D3DVERTEXELEMENT9 dcl [MAX_FVF_DECL_SIZE]; xrCriticalSectionGuard guard(creationGuard); CHK_DX(D3DXDeclaratorFromFVF(FVF,dcl)); - SGeometry* g = CreateGeom(dcl, vb, ib); + SGeometry* g = CreateGeom(dcl, vb, ib, keep_alive); return g; } @@ -620,40 +766,176 @@ void CResourceManager::DeleteGeom(const SGeometry* Geom) } //-------------------------------------------------------------------------------------------------------------- -xr_task_group textures_load_tasks; -CTexture* CResourceManager::_CreateTexture(LPCSTR _Name) +void CResourceManager::ResolveTextureSource(LPCSTR name, LPCSTR canonical_level_path, TextureSourceInfo& result) +{ + xr_string key = name; + key += '\n'; + if (canonical_level_path && canonical_level_path[0]) + key += canonical_level_path; + else if (FS_Path* level_path = FS.get_path("$level$")) + key += level_path->m_Path; + std::transform(key.begin(), key.end(), key.begin(), [](char value) + { + if (value == '/') + return '\\'; + return static_cast(tolower(static_cast(value))); + }); + + xr_shared_ptr job; + bool producer = false; + { + xrCriticalSectionGuard guard(textureSourceGuard); + auto existing = m_textureSourceCache.find(key); + if (existing != m_textureSourceCache.end()) + job = existing->second; + else + { + job = xr_make_shared(); + m_textureSourceCache.emplace(key, job); + producer = true; + } + } + + if (producer) + { + try + { + TextureSourceInfo sourceInfo; + sourceInfo.loadKind = CTexture::LoadKindDds; + string_path path = {}; + const CLocatorAPI::file* source = nullptr; + if (FS.exist(path, "$game_textures$", name, ".ogm")) + sourceInfo.loadKind = CTexture::LoadKindOgm; + else if (FS.exist(path, "$game_textures$", name, ".avi")) + sourceInfo.loadKind = CTexture::LoadKindAvi; + else if (FS.exist(path, "$game_textures$", name, ".seq")) + sourceInfo.loadKind = CTexture::LoadKindSequence; + else if (FS.exist(path, "$game_textures$", name, ".gif")) + sourceInfo.loadKind = CTexture::LoadKindGif; + + if (sourceInfo.loadKind != CTexture::LoadKindDds) + sourceInfo.resolvedPath = path; + else + { + if (canonical_level_path && canonical_level_path[0]) + { + xr_string candidate = canonical_level_path; + if (candidate.back() != '\\' && candidate.back() != '/') + candidate += '\\'; + candidate += name; + candidate += ".dds"; + source = FS.exist(candidate.c_str()); + if (source) + sourceInfo.resolvedPath = candidate; + } + if (!source && (!canonical_level_path || !canonical_level_path[0])) + { + source = FS.exist(path, "$level$", name, ".dds"); + if (source) + sourceInfo.resolvedPath = path; + } + sourceInfo.levelLocal = source != nullptr; + if (!source) + { + source = FS.exist(path, "$game_saves$", name, ".dds"); + if (source) + sourceInfo.resolvedPath = path; + } + if (!source) + { + source = FS.exist(path, "$game_textures$", name, ".dds"); + if (source) + sourceInfo.resolvedPath = path; + } + if (sourceInfo.levelLocal) + { + sourceInfo.crc = source->crc; + sourceInfo.sizeReal = source->size_real; + sourceInfo.sizeCompressed = source->size_compressed; + sourceInfo.modified = source->modif; + } + } + job->source = std::move(sourceInfo); + } + catch (...) + { + job->failure = std::current_exception(); + } + SetEvent(job->completed); + } + else + WaitForSingleObject(job->completed, INFINITE); + + if (job->failure) + std::rethrow_exception(job->failure); + result = job->source; +} + +//-------------------------------------------------------------------------------------------------------------- +ref_texture CResourceManager::_CreateTexture(LPCSTR _Name, bool prefetch, LPCSTR canonical_level_path) { PROF_EVENT("_CreateTexture"); // DBG_VerifyTextures (); - if (0 == xr_strcmp(_Name, "null")) return 0; + if (0 == xr_strcmp(_Name, "null")) return ref_texture(); //Msg("texture %s", _Name); R_ASSERT(_Name && _Name[0]); string_path Name; xr_strcpy(Name, _Name); //. andy if (strext(Name)) *strext(Name)=0; - xrCriticalSectionGuard guard(creationGuard); fix_texture_name(Name); - // ***** first pass - search already loaded texture - LPSTR N = LPSTR(Name); - map_TextureIt I = m_textures.find(N); - if (I != m_textures.end()) return I->second; - else + if ((!canonical_level_path || !canonical_level_path[0]) && !g_resource_level_path_override.empty()) + canonical_level_path = g_resource_level_path_override.c_str(); + + TextureSourceInfo source; + ResolveTextureSource(Name, canonical_level_path, source); + + xr_string registryName = Name; + if (source.levelLocal) + { + string128 identity; + xr_sprintf(identity, "\n@level:%08x:%08x:%08x:%08x:", source.crc, source.sizeReal, + source.sizeCompressed, source.modified); + registryName += identity; + registryName += source.resolvedPath; + } + + ref_texture texture; + bool queueLoad = false; + bool created = false; { - CTexture* T = xr_new(); - T->dwFlags |= xr_resource_flagged::RF_REGISTERED; - m_textures.insert(mk_pair(T->set_name(Name), T)); - T->Preload(); - if (Device.b_is_Ready) + xrCriticalSectionGuard guard(creationGuard); + map_TextureIt I = m_textures.find(registryName.c_str()); + if (I != m_textures.end()) { - static DWORD this_thread_id = 0; - this_thread_id = GetCurrentThreadId(); - textures_load_tasks.run([=]() - { - if (this_thread_id != GetCurrentThreadId()) { PROF_THREAD("X-Ray PPL Thread") } - T->Load(); - }); + texture = ref_texture(I->second); + } + else + { + CTexture* T = xr_new(); + T->dwFlags |= xr_resource_flagged::RF_REGISTERED; + m_textures.insert(mk_pair(T->set_name(registryName.c_str()), T)); + T->SetLoadSource(Name, source.resolvedPath.empty() ? nullptr : source.resolvedPath.c_str(), + static_cast(source.loadKind)); + T->Preload(); + texture = ref_texture(T); + created = true; + } + + if (prefetch) + m_prefetchedTextures.emplace(texture._get(), texture); + else + m_prefetchedTextures.erase(texture._get()); + + queueLoad = Device.b_is_Ready; + if (!queueLoad && created && !texture->is_loaded()) + { + m_deferredTextureLoads.push_back(texture); } - return T; } + + if (queueLoad) + QueueTextureLoad(texture); + + return texture; } void CResourceManager::_DeleteTexture(const CTexture* T) @@ -761,20 +1043,27 @@ bool cmp_tl(const std::pair& _1, const std::pair(L); //lst->_copy(L); lst->dwFlags |= xr_resource_flagged::RF_REGISTERED; lst_textures.push_back(lst); + candidates.push_back(lst); + if (keep_alive) + *keep_alive = lst; return lst; } @@ -782,6 +1071,7 @@ void CResourceManager::_DeleteTextureList(const STextureList* L) { if (0 == (L->dwFlags & xr_resource_flagged::RF_REGISTERED)) return; xrCriticalSectionGuard guard(creationGuard); + remove_indexed(m_texture_list_index, texture_list_hash(*L), L); if (reclaim(lst_textures, L)) return; Msg("! ERROR: Failed to find compiled list of textures"); } @@ -857,7 +1147,8 @@ void CResourceManager::_DeleteConstantList(const SConstantList* L) } //-------------------------------------------------------------------------------------------------------------- -dx10ConstantBuffer* CResourceManager::_CreateConstantBuffer(ID3DShaderReflectionConstantBuffer* pTable) +dx10ConstantBuffer* CResourceManager::_CreateConstantBuffer(ID3DShaderReflectionConstantBuffer* pTable, + ref_cbuffer* keep_alive) { VERIFY(pTable); xrCriticalSectionGuard guard(creationGuard); @@ -869,12 +1160,16 @@ dx10ConstantBuffer* CResourceManager::_CreateConstantBuffer(ID3DShaderReflection if (pTempBuffer->Similar(*buf)) { xr_delete(pTempBuffer); + if (keep_alive) + *keep_alive = buf; return buf; } } pTempBuffer->dwFlags |= xr_resource_flagged::RF_REGISTERED; v_constant_buffer.push_back(pTempBuffer); + if (keep_alive) + *keep_alive = pTempBuffer; return pTempBuffer; } @@ -888,7 +1183,7 @@ void CResourceManager::_DeleteConstantBuffer(const dx10ConstantBuffer* pBuffer) } //-------------------------------------------------------------------------------------------------------------- -SInputSignature* CResourceManager::_CreateInputSignature(ID3DBlob* pBlob) +SInputSignature* CResourceManager::_CreateInputSignature(ID3DBlob* pBlob, ref_input_sign* keep_alive) { VERIFY(pBlob); xrCriticalSectionGuard guard(creationGuard); @@ -899,6 +1194,8 @@ SInputSignature* CResourceManager::_CreateInputSignature(ID3DBlob* pBlob) if ((pBlob->GetBufferSize() == sign->signature->GetBufferSize()) && (!(memcmp(pBlob->GetBufferPointer(), sign->signature->GetBufferPointer(), pBlob->GetBufferSize())))) { + if (keep_alive) + *keep_alive = sign; return sign; } } @@ -907,6 +1204,8 @@ SInputSignature* CResourceManager::_CreateInputSignature(ID3DBlob* pBlob) pSign->dwFlags |= xr_resource_flagged::RF_REGISTERED; v_input_signature.push_back(pSign); + if (keep_alive) + *keep_alive = pSign; return pSign; } diff --git a/src/Layers/xrRenderDX10/dx10ResourceManager_Scripting.cpp b/src/Layers/xrRenderDX10/dx10ResourceManager_Scripting.cpp index f81405e198..aa94d8adaf 100644 --- a/src/Layers/xrRenderDX10/dx10ResourceManager_Scripting.cpp +++ b/src/Layers/xrRenderDX10/dx10ResourceManager_Scripting.cpp @@ -513,7 +513,6 @@ BOOL CResourceManager::_lua_HasShader(LPCSTR s_shader) Shader* CResourceManager::_lua_Create(LPCSTR d_shader, LPCSTR s_textures) { - xrCriticalSectionGuard guard(creationGuard); CBlender_Compile C; Shader S; @@ -604,16 +603,7 @@ Shader* CResourceManager::_lua_Create(LPCSTR d_shader, LPCSTR s_textures) S.E[4] = C._lua_Compile(s_shader, "l_special"); } - // Search equal in shaders array - for (u32 it = 0; it < v_shaders.size(); it++) - if (S.equal(v_shaders[it])) return v_shaders[it]; - - // Create _new_ entry - Shader* N = xr_new(S); - //N->_copy(S); - N->dwFlags |= xr_resource_flagged::RF_REGISTERED; - v_shaders.push_back(N); - return N; + return _CreateShader(&S); } ShaderElement* CBlender_Compile::_lua_Compile(LPCSTR namesp, LPCSTR name) diff --git a/src/Layers/xrRenderDX10/dx10SH_Texture.cpp b/src/Layers/xrRenderDX10/dx10SH_Texture.cpp index e161ef143d..dc03098f14 100644 --- a/src/Layers/xrRenderDX10/dx10SH_Texture.cpp +++ b/src/Layers/xrRenderDX10/dx10SH_Texture.cpp @@ -40,11 +40,12 @@ CTexture::CTexture() seqMSPF = 0; flags.MemoryUsage = 0; flags.bLoaded = false; - flags.bLoading = false; flags.bUser = false; flags.seqCycles = FALSE; flags.bLoadedAsStaging = FALSE; m_material = 1.0f; + loadState.store(LoadStateUnloaded, std::memory_order_relaxed); + loadKind.store(0, std::memory_order_relaxed); bind = xr_make_delegate(this, &CTexture::apply_load); } @@ -58,10 +59,7 @@ CTexture::~CTexture() void CTexture::surface_set(ID3DBaseTexture* surf) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (cName.size() && strstr(cName.c_str(), "$user$")) flags.bUser = true; @@ -129,10 +127,7 @@ void CTexture::surface_set(ID3DBaseTexture* surf) ID3DBaseTexture* CTexture::surface_get() { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (flags.bLoadedAsStaging) ProcessStaging(); @@ -151,7 +146,7 @@ void CTexture::PostLoad() void CTexture::apply_load(u32 dwStage) { - if (!flags.bLoaded) Load(); + if (!is_loaded()) Load(); else PostLoad(); if (bind == xr_make_delegate(this, &CTexture::apply_load)) { @@ -250,10 +245,7 @@ void CTexture::ProcessStaging() void CTexture::Apply(u32 dwStage) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); dwLastUsedFrame = RDEVICE.dwFrame; if (flags.bLoadedAsStaging) @@ -302,10 +294,7 @@ void CTexture::Apply(u32 dwStage) void CTexture::apply_theora(u32 dwStage) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (pTheora->Update(m_play_time != 0xFFFFFFFF ? m_play_time : Device.dwTimeContinual)) { D3D_RESOURCE_DIMENSION type; @@ -345,10 +334,7 @@ void CTexture::apply_theora(u32 dwStage) void CTexture::apply_avi(u32 dwStage) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (pAVI->NeedUpdate()) { D3D_RESOURCE_DIMENSION type; @@ -381,10 +367,7 @@ void CTexture::apply_avi(u32 dwStage) void CTexture::apply_seq(u32 dwStage) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); // SEQ u32 frame = Device.dwTimeContinual / seqMSPF; //Device.dwTimeGlobal u32 frame_data = seqDATA.size(); @@ -407,10 +390,7 @@ void CTexture::apply_seq(u32 dwStage) void CTexture::apply_gif(u32 dwStage) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (gifPlayer->UpdateFrame()) { const CGIFAnimationPlayer::Frame* const gifFrame = gifPlayer->GetActiveFrame(); @@ -424,62 +404,178 @@ void CTexture::apply_gif(u32 dwStage) void CTexture::apply_normal(u32 dwStage) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); //CHK_DX(HW.pDevice->SetTexture(dwStage,pSurface)); Apply(dwStage); }; void CTexture::Preload() { + const shared_str& name = m_loadName.size() ? m_loadName : cName; if (!Core.ParamsData.test(ECoreParams::r4_dev)) { - m_bumpmap = DEV->m_textures_description.GetBumpName(cName); + m_bumpmap = DEV->m_textures_description.GetBumpName(name); } - m_material = DEV->m_textures_description.GetMaterial(cName); + m_material = DEV->m_textures_description.GetMaterial(name); +} + +void CTexture::SetLoadSource(LPCSTR logical_name, LPCSTR resolved_path, ELoadKind kind) +{ + m_loadName = logical_name; + m_resolvedSourcePath = resolved_path; + loadKind.store(kind, std::memory_order_release); +} + +bool CTexture::TryQueueLoad() +{ + u32 expected = LoadStateUnloaded; + return loadState.compare_exchange_strong(expected, LoadStateQueued, std::memory_order_acq_rel, + std::memory_order_acquire); +} + +void CTexture::CancelQueuedLoad() +{ + u32 expected = LoadStateQueued; + loadState.compare_exchange_strong(expected, LoadStateUnloaded, std::memory_order_acq_rel, + std::memory_order_acquire); +} + +bool CTexture::CanLoadAsync() const +{ + u32 kind = loadKind.load(std::memory_order_acquire); + if (!kind) + { + const shared_str& name = m_loadName.size() ? m_loadName : cName; + string_path path; + if (FS.exist(path, "$game_textures$", name.c_str(), ".ogm")) + kind = LoadKindOgm; + else if (FS.exist(path, "$game_textures$", name.c_str(), ".avi")) + kind = LoadKindAvi; + else if (FS.exist(path, "$game_textures$", name.c_str(), ".seq")) + kind = LoadKindSequence; + else if (FS.exist(path, "$game_textures$", name.c_str(), ".gif")) + kind = LoadKindGif; + else + kind = LoadKindDds; + loadKind.store(kind, std::memory_order_release); + } + return kind == LoadKindDds; +} + +bool CTexture::is_loaded() const +{ + return loadState.load(std::memory_order_acquire) == LoadStateLoaded; +} + +void CTexture::wait_for_loading() const +{ + for (;;) + { + const u32 state = loadState.load(std::memory_order_acquire); + if (state != LoadStateQueued && state != LoadStateLoading && state != LoadStateUnloading) + return; + if (state == LoadStateQueued && DEV && DEV->IsTextureOwnerThread()) + { + const_cast(this)->Load(); + continue; + } + SwitchToThread(); + } +} + +bool CTexture::BeginLoad(bool queued) +{ + for (;;) + { + u32 expected = queued ? LoadStateQueued : LoadStateUnloaded; + if (loadState.compare_exchange_strong(expected, LoadStateLoading, std::memory_order_acq_rel, + std::memory_order_acquire)) + { + return true; + } + if (!queued && expected == LoadStateQueued) + { + expected = LoadStateQueued; + if (loadState.compare_exchange_strong(expected, LoadStateLoading, std::memory_order_acq_rel, + std::memory_order_acquire)) + { + return true; + } + } + + if (expected == LoadStateLoaded || expected == LoadStateFailed || (queued && expected == LoadStateUnloaded)) + return false; + + wait_for_loading(); + } +} + +void CTexture::FinishLoad() +{ + flags.bLoaded = true; + loadState.store(LoadStateLoaded, std::memory_order_release); +} + +void CTexture::FailLoad() +{ + loadState.store(LoadStateUnloading, std::memory_order_release); + ReleaseLoadedData(); + loadState.store(LoadStateFailed, std::memory_order_release); } void CTexture::Load() +{ + Load(false); +} + +void CTexture::LoadQueued() +{ + Load(true); +} + +void CTexture::Load(bool queued) { PROF_EVENT("CTexture::Load"); + if (!BeginLoad(queued)) + return; + try + { - if (flags.bLoaded || flags.bLoading) return; - flags.bLoading = true; flags.bLoaded = false; desc_cache = 0; + const shared_str& name = m_loadName.size() ? m_loadName : cName; if (pSurface) { - flags.bLoading = false; - flags.bLoaded = true; + FinishLoad(); return; } flags.bUser = false; flags.MemoryUsage = 0; - if (0 == stricmp(*cName, "$null")) + if (0 == stricmp(name.c_str(), "$null")) { - flags.bLoading = false; - flags.bLoaded = true; + FinishLoad(); return; } - if (0 != strstr(*cName, "$user$")) + if (0 != strstr(name.c_str(), "$user$")) { flags.bUser = true; - flags.bLoading = false; - flags.bLoaded = true; + FinishLoad(); return; } Preload(); bool bCreateView = true; + const u32 kind = loadKind.load(std::memory_order_acquire); + const LPCSTR resolvedSource = m_resolvedSourcePath.size() ? m_resolvedSourcePath.c_str() : nullptr; // Check for OGM string_path fn; - if (FS.exist(fn, "$game_textures$", *cName, ".ogm")) + if (kind == LoadKindOgm || (kind == LoadKindUnknown && FS.exist(fn, "$game_textures$", name.c_str(), ".ogm"))) { + if (kind == LoadKindOgm) + xr_strcpy(fn, resolvedSource); // AVI pTheora = xr_new(); m_play_time = 0xFFFFFFFF; @@ -530,8 +626,10 @@ void CTexture::Load() } } } - else if (FS.exist(fn, "$game_textures$", *cName, ".avi")) + else if (kind == LoadKindAvi || (kind == LoadKindUnknown && FS.exist(fn, "$game_textures$", name.c_str(), ".avi"))) { + if (kind == LoadKindAvi) + xr_strcpy(fn, resolvedSource); // AVI pAVI = xr_new(); @@ -579,8 +677,10 @@ void CTexture::Load() } } } - else if (FS.exist(fn, "$game_textures$", *cName, ".seq")) + else if (kind == LoadKindSequence || (kind == LoadKindUnknown && FS.exist(fn, "$game_textures$", name.c_str(), ".seq"))) { + if (kind == LoadKindSequence) + xr_strcpy(fn, resolvedSource); // Sequence string256 buffer; IReader* _fs = FS.r_open(fn); @@ -617,8 +717,10 @@ void CTexture::Load() pSurface = 0; FS.r_close(_fs); } - else if (FS.exist(fn, "$game_textures$", *cName, ".gif")) + else if (kind == LoadKindGif || (kind == LoadKindUnknown && FS.exist(fn, "$game_textures$", name.c_str(), ".gif"))) { + if (kind == LoadKindGif) + xr_strcpy(fn, resolvedSource); gifPlayer = xr_new(); if (!gifPlayer->Load(fn)) { @@ -641,8 +743,8 @@ void CTexture::Load() { // Normal texture u32 mem = 0; - //pSurface = ::RImplementation.texture_load (*cName,mem); - pSurface = ::RImplementation.texture_load(*cName, mem, true); + pSurface = ::RImplementation.texture_load(name.c_str(), mem, false, + kind == LoadKindDds ? resolvedSource : nullptr); if (GetUsage() == D3D_USAGE_STAGING) { @@ -661,20 +763,39 @@ void CTexture::Load() CHK_DX(HW.pDevice->CreateShaderResourceView(pSurface, NULL, &m_pSRView)); } PostLoad(); - flags.bLoading = false; - flags.bLoaded = true; + FinishLoad(); + } + catch (...) + { + FailLoad(); + throw; + } } void CTexture::Unload() { - while (flags.bLoading) + for (;;) { - SwitchToThread(); + u32 state = loadState.load(std::memory_order_acquire); + if (state == LoadStateUnloaded || state == LoadStateFailed) + return; + if (state == LoadStateQueued || state == LoadStateLoading || state == LoadStateUnloading) + { + wait_for_loading(); + continue; + } + if (loadState.compare_exchange_strong(state, LoadStateUnloading, std::memory_order_acq_rel, + std::memory_order_acquire)) + { + break; + } } + ReleaseLoadedData(); + loadState.store(LoadStateUnloaded, std::memory_order_release); +} - // Already unloaded or never loaded: nothing to do. - if (!flags.bLoaded) - return; +void CTexture::ReleaseLoadedData() +{ #ifdef DEBUG string_path msg_buff; xr_sprintf (msg_buff,sizeof(msg_buff),"* Unloading texture [%s] pSurface RefCount=",cName.c_str()); @@ -718,10 +839,7 @@ void CTexture::Unload() void CTexture::desc_update() { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); desc_cache = pSurface; if (pSurface) { @@ -782,36 +900,24 @@ D3D_USAGE CTexture::GetUsage() void CTexture::video_Play(BOOL looped, u32 _time) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (pTheora) pTheora->Play(looped, (_time != 0xFFFFFFFF) ? (m_play_time = _time) : Device.dwTimeContinual); } void CTexture::video_Pause(BOOL state) { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (pTheora) pTheora->Pause(state); } void CTexture::video_Stop() { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); if (pTheora) pTheora->Stop(); } BOOL CTexture::video_IsPlaying() { - while (flags.bLoading) - { - SwitchToThread(); - } + wait_for_loading(); return (pTheora) ? pTheora->IsPlaying() : FALSE; } diff --git a/src/Layers/xrRenderDX10/dx10Texture.cpp b/src/Layers/xrRenderDX10/dx10Texture.cpp index 1a52e69b9e..d9f79e5f45 100644 --- a/src/Layers/xrRenderDX10/dx10Texture.cpp +++ b/src/Layers/xrRenderDX10/dx10Texture.cpp @@ -30,45 +30,7 @@ void fix_texture_name(LPSTR fn) int get_texture_load_lod(LPCSTR fn) { - CInifile::Sect& sect = pSettings->r_section("reduce_lod_texture_list"); - CInifile::SectCIt it_ = sect.Data.begin(); - CInifile::SectCIt it_e_ = sect.Data.end(); - - ENGINE_API bool is_enough_address_space_available(); - static bool enough_address_space_available = is_enough_address_space_available(); - - CInifile::SectCIt it = it_; - CInifile::SectCIt it_e = it_e_; - - for (; it != it_e; ++it) - { - if (strstr(fn, it->first.c_str())) - { - if (psTextureLOD < 1) - { - if (enough_address_space_available) - return 0; - else - return 1; - } - else if (psTextureLOD < 3) - return 1; - else - return 2; - } - } - - if (psTextureLOD < 2) - { - // if ( enough_address_space_available ) - return 0; - // else - // return 1; - } - else if (psTextureLOD < 4) - return 1; - else - return 2; + return dxRenderDeviceRender::Instance().Resources->GetTextureLoadLod(fn); } u32 calc_texture_size(int lod, u32 mip_cnt, u32 orig_size) @@ -307,7 +269,7 @@ IC u32 it_height_rev_base(u32 d, u32 s) { return color_rgba ( (color_get_R(s)+color_get_G(s)+color_get_B(s))/3 ); // height } */ -ID3DBaseTexture* CRender::texture_load(LPCSTR fRName, u32& ret_msize, bool bStaging) +ID3DBaseTexture* CRender::texture_load(LPCSTR fRName, u32& ret_msize, bool bStaging, LPCSTR resolvedPath) { // Moved here just to avoid warning #ifdef USE_DX11 @@ -371,6 +333,11 @@ ID3DBaseTexture* CRender::texture_load(LPCSTR fRName, u32& ret_msize, bool bStag goto _DDS_2D; } } + if (resolvedPath && resolvedPath[0]) + { + xr_strcpy(fn, resolvedPath); + goto _DDS; + } if (FS.exist(fn, "$level$", fname, ".dds")) goto _DDS; if (FS.exist(fn, "$game_saves$", fname, ".dds")) goto _DDS; if (FS.exist(fn, "$game_textures$", fname, ".dds")) goto _DDS; diff --git a/src/Layers/xrRenderDX10/dx10r_constants.cpp b/src/Layers/xrRenderDX10/dx10r_constants.cpp index 81b31f54e5..d6295da011 100644 --- a/src/Layers/xrRenderDX10/dx10r_constants.cpp +++ b/src/Layers/xrRenderDX10/dx10r_constants.cpp @@ -392,7 +392,8 @@ BOOL R_constant_table::parse(void* _desc, u32 destination) ? CB_BufferVertexShader : CB_BufferGeometryShader;*/ parseConstants(pTable, updatedDest); - ref_cbuffer tempBuffer = dxRenderDeviceRender::Instance().Resources->_CreateConstantBuffer(pTable); + ref_cbuffer tempBuffer; + dxRenderDeviceRender::Instance().Resources->_CreateConstantBuffer(pTable, &tempBuffer); m_CBTable.push_back(cb_table_record(uiBufferIndex, tempBuffer)); } } diff --git a/src/Layers/xrRenderPC_R3/r3.h b/src/Layers/xrRenderPC_R3/r3.h index 08b1b909bd..98af5172d6 100644 --- a/src/Layers/xrRenderPC_R3/r3.h +++ b/src/Layers/xrRenderPC_R3/r3.h @@ -281,7 +281,7 @@ class CRender : public IRender_interface, public pureFrame virtual void level_Load(IReader*); virtual void level_Unload(); - ID3DBaseTexture* texture_load(LPCSTR fname, u32& msize, bool bStaging = false); + ID3DBaseTexture* texture_load(LPCSTR fname, u32& msize, bool bStaging = false, LPCSTR resolvedPath = nullptr); virtual HRESULT shader_compile( LPCSTR name, DWORD const* pSrcData, diff --git a/src/Layers/xrRenderPC_R4/r4.cpp b/src/Layers/xrRenderPC_R4/r4.cpp index 1d4853f060..6e463b27e2 100644 --- a/src/Layers/xrRenderPC_R4/r4.cpp +++ b/src/Layers/xrRenderPC_R4/r4.cpp @@ -199,6 +199,8 @@ extern ENGINE_API BOOL r2_advanced_pp; // advanced post process and effects // Just two static storage void CRender::create() { + CTimer startupTimer; + startupTimer.Start(); Device.seqFrame.Add(this,REG_PRIORITY_HIGH + 0x12345678); Engine.External.SetSkinningMode(); @@ -529,25 +531,55 @@ void CRender::create() m_bMakeAsyncSS = false; + const u32 setupMs = startupTimer.GetElapsed_ms(); + string_path particleLibraryPath; + FS.update_path(particleLibraryPath, "$game_data$", "particles.xr"); + bool particleLoadResult = false; + xr_task_group particlePrepare; + particlePrepare.run([this, &particleLoadResult, particleLibraryPath]() + { + particleLoadResult = PSLibrary.LoadDefinitions(particleLibraryPath); + }); Target = xr_new(); // Main target + const u32 targetMs = startupTimer.GetElapsed_ms() - setupMs; Models = xr_new(); - PSLibrary.OnCreate(); + const u32 modelsMs = startupTimer.GetElapsed_ms() - setupMs - targetMs; + particlePrepare.wait(); + PSLibrary.FinalizeLoad(); + const u32 particlesMs = startupTimer.GetElapsed_ms() - setupMs - targetMs - modelsMs; HWOCC.occq_create(occq_size); + const u32 occlusionMs = startupTimer.GetElapsed_ms() - setupMs - targetMs - modelsMs - particlesMs; rmNormal(); + const u32 viewportMs = + startupTimer.GetElapsed_ms() - setupMs - targetMs - modelsMs - particlesMs - occlusionMs; GMBase.initialize(); + const u32 geometryMs = + startupTimer.GetElapsed_ms() - setupMs - targetMs - modelsMs - particlesMs - occlusionMs - viewportMs; FluidManager.Initialize(70, 70, 70); // FluidManager.Initialize( 100, 100, 100 ); + const u32 fluidMs = startupTimer.GetElapsed_ms() - setupMs - targetMs - modelsMs - particlesMs - occlusionMs - + viewportMs - geometryMs; FluidManager.SetScreenSize(Device.dwWidth, Device.dwHeight); Device.ModelDefferClear = xr_make_delegate(Models, &CModelPool::DeleteQueuedDeffer); + const u32 supportMs = startupTimer.GetElapsed_ms() - setupMs - targetMs; + Msg("* [STARTUP/RENDER R4] setup=%u target=%u support=%u total=%u ms", + setupMs, targetMs, supportMs, startupTimer.GetElapsed_ms()); + Msg("* [STARTUP/RENDER SUPPORT] models=%u particles=%u occlusion=%u viewport=%u geometry=%u fluid=%u ms", + modelsMs, particlesMs, occlusionMs, viewportMs, geometryMs, fluidMs); } void CRender::destroy() { m_bMakeAsyncSS = false; + WaitLevelPrepare(); + m_prepared_level_path = nullptr; + DiscardPreparedVisuals(); + DestroyActiveLevel(); + ReleaseLevelCache(); FluidManager.Destroy(); GMBase.destroy(); @@ -561,6 +593,11 @@ void CRender::destroy() void CRender::reset_begin() { + WaitLevelPrepare(); + m_prepared_level_path = nullptr; + Models->InvalidateBlueprints(); + ReleaseLevelCache(); + //AVO: let's reload details while changed details options on vid_restart if (b_loaded && ((dm_current_size != dm_size) || (ps_r__Detail_density != ps_current_detail_density) || ( ps_r__Detail_height != ps_current_detail_height))) @@ -690,6 +727,15 @@ IRenderVisual* CRender::model_CreateParticles(LPCSTR name) void CRender::models_Prefetch() { Models->Prefetch(); } void CRender::models_PrefetchOne(LPCSTR name, bool assert) { Models->Prefetch_One(name, assert); } +void CRender::model_CollectTextures(LPCSTR name, LPCSTR canonical_level_path, xr_vector& textures) +{ + Models->CollectTextures(name, canonical_level_path, textures); +} +bool CRender::models_PrefetchPrepared(LPCSTR name, LPCSTR canonical_level_path, bool assert) +{ + return Models->PrefetchPrepared(name, canonical_level_path, assert); +} +void CRender::models_InvalidatePrepared() { Models->InvalidateBlueprints(); } void CRender::models_Clear(BOOL b_complete) { Models->ClearPool(b_complete); } bool CRender::models_Exists(LPCSTR name) { return Models->Exists(name); } @@ -717,12 +763,27 @@ IRender_Sector* CRender::getSectorActive() { return pLastSector; } IRenderVisual* CRender::getVisual(int id) { + if (m_visual_table_source) + { + VERIFY(id < int(m_visual_table_source->size())); + return (*m_visual_table_source)[id]; + } VERIFY(id* CRender::m_visual_table_source = nullptr; + D3DVERTEXELEMENT9* CRender::getVB_Format(int id, BOOL _alt) { + if (m_visual_geometry_source) + { + const xr_vector& declarations = _alt ? + *m_visual_geometry_source->fast_declarations : *m_visual_geometry_source->normal_declarations; + VERIFY(id < int(declarations.size())); + return const_cast(declarations[id]).begin(); + } if (_alt) { VERIFY(id& buffers = _alt ? + *m_visual_geometry_source->fast_vertex_buffers : *m_visual_geometry_source->normal_vertex_buffers; + VERIFY(id < int(buffers.size())); + return buffers[id]; + } if (_alt) { VERIFY(id& buffers = _alt ? + *m_visual_geometry_source->fast_index_buffers : *m_visual_geometry_source->normal_index_buffers; + VERIFY(id < int(buffers.size())); + return buffers[id]; + } if (_alt) { VERIFY(idswis->size())); + return const_cast(&(*m_visual_geometry_source->swis)[id]); + } VERIFY(idsignature = dxRenderDeviceRender::Instance().Resources->_CreateInputSignature(pSignatureBlob); + dxRenderDeviceRender::Instance().Resources->_CreateInputSignature(pSignatureBlob, + &svs_result->signature); _RELEASE(pSignatureBlob); @@ -1232,8 +1314,30 @@ class includer : public ID3DInclude } }; +using ShaderVariantNames = xr_vector; + +static xr_shared_ptr indexed_shader_variants(LPCSTR folder) +{ + static xrCriticalSection* lock = xr_new(); + static xr_map>* cache = + xr_new>>(); + xrCriticalSectionGuard guard(*lock); + auto found = cache->find(folder); + if (found != cache->end()) + return found->second; + + auto names = xr_make_shared(); + FS_FileSet files; + FS.file_list(files, folder, FS_ListFiles | FS_RootOnly, "*"); + names->reserve(files.size()); + for (const FS_File& file : files) + names->push_back(file.name.c_str()); + cache->emplace(folder, names); + return names; +} + static inline bool match_shader_id(LPCSTR const debug_shader_id, LPCSTR const full_shader_id, - FS_FileSet const& file_set, string_path& result); + ShaderVariantNames const& file_set, string_path& result); HRESULT CRender::shader_compile( LPCSTR name, @@ -1264,6 +1368,8 @@ HRESULT CRender::shader_compile( char c_ssr_quality[32]; char c_rain_quality[32]; char c_inter_grass[32]; + char c_msaa_sample[2]; + char c_msaa_samples[2]; char sh_name[MAX_PATH] = ""; @@ -1459,10 +1565,9 @@ HRESULT CRender::shader_compile( if (o.dx10_msaa) { - static char def[ 256 ]; //if( m_MSAASample < 0 ) //{ - def[0] = '0'; + c_msaa_sample[0] = '0'; // sh_name[len]='0'; ++len; //} //else @@ -1470,9 +1575,9 @@ HRESULT CRender::shader_compile( // def[0]= '0' + char(m_MSAASample); // sh_name[len]='0' + char(m_MSAASample); ++len; //} - def[1] = 0; + c_msaa_sample[1] = 0; defines[def_it].Name = "ISAMPLE"; - defines[def_it].Definition = def; + defines[def_it].Definition = c_msaa_sample; def_it ++; sh_name[len] = '0'; ++len; @@ -1833,12 +1938,10 @@ HRESULT CRender::shader_compile( sh_name[len] = '1'; ++len; - static char samples[2]; - defines[def_it].Name = "MSAA_SAMPLES"; - samples[0] = char(o.dx10_msaa_samples) + '0'; - samples[1] = 0; - defines[def_it].Definition = samples; + c_msaa_samples[0] = char(o.dx10_msaa_samples) + '0'; + c_msaa_samples[1] = 0; + defines[def_it].Definition = c_msaa_samples; def_it ++; sh_name[len] = '0' + char(o.dx10_msaa_samples); ++len; @@ -1970,12 +2073,13 @@ HRESULT CRender::shader_compile( FS.update_path(folder_name, "$game_shaders$", folder); xr_strcat(folder_name, "\\"); - m_file_set.clear(); - FS.file_list(m_file_set, folder_name, FS_ListFiles | FS_RootOnly, "*"); - string_path temp_file_name, file_name; - bool const useGeneratedShaderCache = - psDeviceFlags2.test(rsPrecompiledShaders) || !match_shader_id(name, sh_name, m_file_set, temp_file_name); + bool useGeneratedShaderCache = psDeviceFlags2.test(rsPrecompiledShaders); + if (!useGeneratedShaderCache) + { + const xr_shared_ptr file_set = indexed_shader_variants(folder_name); + useGeneratedShaderCache = !match_shader_id(name, sh_name, *file_set, temp_file_name); + } if (useGeneratedShaderCache) { string_path file; @@ -1995,7 +2099,7 @@ HRESULT CRender::shader_compile( u32 source_crc = 0; if (useGeneratedShaderCache) - source_crc = getShaderSourceCrc32(pSrcData, SrcDataLen, ::Render->getShaderPath()); + source_crc = getShaderSourceCrc32Cached(pSrcData, SrcDataLen, ::Render->getShaderPath(), name, pTarget); if (FS.exist(file_name)) { @@ -2114,7 +2218,7 @@ static inline bool match_shader(LPCSTR const debug_shader_id, LPCSTR const full_ } static inline bool match_shader_id(LPCSTR const debug_shader_id, LPCSTR const full_shader_id, - FS_FileSet const& file_set, string_path& result) + ShaderVariantNames const& file_set, string_path& result) { #if 0 strcpy_s ( result, "" ); @@ -2123,26 +2227,26 @@ static inline bool match_shader_id(LPCSTR const debug_shader_id, LPCSTR const fu #ifdef DEBUG LPCSTR temp = ""; bool found = false; - FS_FileSet::const_iterator i = file_set.begin(); - FS_FileSet::const_iterator const e = file_set.end(); + ShaderVariantNames::const_iterator i = file_set.begin(); + ShaderVariantNames::const_iterator const e = file_set.end(); for ( ; i != e; ++i ) { - if ( match_shader(debug_shader_id, full_shader_id, (*i).name.c_str(), (*i).name.size() ) ) { + if ( match_shader(debug_shader_id, full_shader_id, i->c_str(), i->size() ) ) { VERIFY ( !found ); found = true; - temp = (*i).name.c_str(); + temp = i->c_str(); } } xr_strcpy ( result, temp ); return found; #else // #ifdef DEBUG - FS_FileSet::const_iterator i = file_set.begin(); - FS_FileSet::const_iterator const e = file_set.end(); + ShaderVariantNames::const_iterator i = file_set.begin(); + ShaderVariantNames::const_iterator const e = file_set.end(); for (; i != e; ++i) { - if (match_shader(debug_shader_id, full_shader_id, (*i).name.c_str(), (*i).name.size())) + if (match_shader(debug_shader_id, full_shader_id, i->c_str(), i->size())) { - xr_strcpy(result, (*i).name.c_str()); + xr_strcpy(result, i->c_str()); return true; } } diff --git a/src/Layers/xrRenderPC_R4/r4.h b/src/Layers/xrRenderPC_R4/r4.h index af5a6e0ebd..247767a14b 100644 --- a/src/Layers/xrRenderPC_R4/r4.h +++ b/src/Layers/xrRenderPC_R4/r4.h @@ -18,6 +18,7 @@ #include "../xrRender/light_db.h" #include "../xrRender/LightTrack.h" #include "../xrRender/r_sun_cascades.h" +#include "../xrRenderDX10/3DFluid/dx103DFluidData.h" #include "../../xrEngine/irenderable.h" #include "../../xrEngine/fmesh.h" @@ -206,13 +207,80 @@ class CRender : public IRender_interface, public pureFrame private: // Loading / Unloading - void LoadBuffers(CStreamReader* fs, BOOL _alternative); - void LoadVisuals(IReader* fs); + struct LevelShaderDescription + { + xr_string shader; + xr_string textures; + }; + struct LevelStaticPackage; + struct VisualGeometrySource + { + const xr_vector* normal_declarations; + const xr_vector* fast_declarations; + const xr_vector* normal_vertex_buffers; + const xr_vector* fast_vertex_buffers; + const xr_vector* normal_index_buffers; + const xr_vector* fast_index_buffers; + const xr_vector* swis; + }; + static thread_local const VisualGeometrySource* m_visual_geometry_source; + static thread_local const xr_vector* m_visual_table_source; + xr_vector m_level_cache; + LevelStaticPackage* m_prepared_level_geometry = nullptr; + shared_str m_active_level_key; + u64 m_active_level_identity = 0; + xr_task_group m_level_prepare_tasks; + NativeLoadExecutor::Batch m_level_prepare_batch; + shared_str m_prepared_level_path; + u64 m_level_prepare_generation = 0; + u32 m_level_prepare_started_at = 0; + xr_vector m_level_shader_descriptions; + xr_vector m_level_shader_cpp_results; + xr_vector m_level_shader_owner_results; + xr_vector m_active_level_shader_descriptions; + xr_vector m_active_level_shader_cpp_results; + xr_vector m_active_level_shader_indices; + u64 m_level_shader_identity = 0; + bool m_level_shader_owner_required = false; + CHOM::StaticData m_level_prepared_hom; + xr_vector m_level_prepared_lights_dynamic; + xr_vector m_level_prepared_lights_hemi; + CLight_DB* m_level_prepared_lights = nullptr; + xr_vector m_active_level_lights_dynamic; + xr_vector m_active_level_lights_hemi; + xr_vector m_level_prepared_portals; + xr_vector> m_level_prepared_sectors; + CDB::MODEL* m_level_prepared_portals_model = nullptr; + xr_vector m_level_fluid_descriptors; + xr_vector m_level_prepared_visuals; + xr_vector m_level_prepared_visual_data; + IReader* m_level_owner_reader = nullptr; + xr_atomic_bool m_level_async_failed; + bool m_level_cache_attach_pending = false; + bool m_level_prepared_sectors_ready = false; + bool m_level_prepared_visuals_ready = false; + + void CommitLevelShaderComponentsOwner(); + void CommitLevelEnvironmentOwner(); + void LoadBuffers(CStreamReader* fs, xr_vector& declarations, + xr_vector& vertex_buffers, xr_vector& index_buffers); + void PrepareVisuals(IReader* fs, xr_vector& visuals, const VisualGeometrySource* geometry); + void LinkPreparedVisuals(IReader* fs, xr_vector& visuals); + void LoadVisualLeaf(dxRender_Visual* visual, IReader* chunk, const VisualGeometrySource* geometry); + void DiscardPreparedVisuals(); void LoadLights(IReader* fs); void LoadPortals(IReader* fs); void LoadSectors(IReader* fs); - void LoadSWIs(CStreamReader* fs); - void Load3DFluid(); + void LoadPreparedSectors(); + void LoadSWIs(CStreamReader* fs, xr_vector& swis); + void Commit3DFluid(); + void Remove3DFluid(); + void WaitLevelPrepare(); + LevelStaticPackage* DetachLevelStaticPackage(); + bool RestoreLevelStaticPackage(const shared_str& key, u64 identity); + void ReleaseLevelCache(); + void EvictLevelCacheUnderPressure(); + void DestroyActiveLevel(); public: IRender_Sector* rimp_detectSector(Fvector& P, Fvector& D); @@ -309,8 +377,13 @@ class CRender : public IRender_interface, public pureFrame virtual void level_Load(IReader*); virtual void level_Unload(); + virtual bool level_StaticCacheReady(LPCSTR canonical_level_path); + virtual void level_Prepare(LPCSTR canonical_level_path); + virtual void level_InvalidateStaticCache(); + virtual void level_BeginAsyncLoad(); + virtual void level_AbortAsyncLoad(); - ID3DBaseTexture* texture_load(LPCSTR fname, u32& msize, bool bStaging = false); + ID3DBaseTexture* texture_load(LPCSTR fname, u32& msize, bool bStaging = false, LPCSTR resolvedPath = nullptr); virtual HRESULT shader_compile( LPCSTR name, DWORD const* pSrcData, @@ -375,6 +448,10 @@ class CRender : public IRender_interface, public pureFrame virtual void model_Logging(BOOL bEnable) { Models->Logging(bEnable); } virtual void models_Prefetch(); virtual void models_PrefetchOne(LPCSTR name, bool assert = true); + virtual void model_CollectTextures(LPCSTR name, LPCSTR canonical_level_path, + xr_vector& textures) override; + virtual bool models_PrefetchPrepared(LPCSTR name, LPCSTR canonical_level_path, bool assert = true) override; + virtual void models_InvalidatePrepared() override; virtual void models_Clear(BOOL b_complete); virtual bool models_Exists(LPCSTR name); @@ -451,7 +528,6 @@ class CRender : public IRender_interface, public pureFrame virtual void ScreenshotImpl(ScreenshotMode mode, LPCSTR name, CMemoryWriter* memory_writer); private: - FS_FileSet m_file_set; }; extern CRender RImplementation; diff --git a/src/Layers/xrRenderPC_R4/r4_loader.cpp b/src/Layers/xrRenderPC_R4/r4_loader.cpp index 9333cfd964..c02a3989ef 100644 --- a/src/Layers/xrRenderPC_R4/r4_loader.cpp +++ b/src/Layers/xrRenderPC_R4/r4_loader.cpp @@ -7,6 +7,7 @@ #include "../../xrEngine/x_ray.h" #include "../../xrEngine/IGame_Persistent.h" #include "../../xrCore/stream_reader.h" +#include "../../xrCDB/xr_area.h" #include "../xrRender/dxRenderDeviceRender.h" @@ -20,10 +21,844 @@ #include #pragma warning(pop) +namespace +{ +xr_atomic_u32 g_level_asset_generation{1}; + +xr_string NormalizeLevelPath(LPCSTR source) +{ + xr_string path = source ? source : ""; + std::transform(path.begin(), path.end(), path.begin(), [](char value) + { + return char(tolower(u8(value))); + }); + if (!path.empty() && path.back() != '\\' && path.back() != '/') + path += '\\'; + return path; +} + +shared_str CurrentLevelCacheKey() +{ + string_path path; + FS.update_path(path, "$level$", ""); + return shared_str(NormalizeLevelPath(path).c_str()); +} + +void MixLevelIdentity(u64& identity, u32 value) +{ + identity ^= value; + identity *= 1099511628211ull; +} + +u64 LevelIdentity(LPCSTR canonical_level_path) +{ + static LPCSTR files[] = + { + "level", "level.geom", "level.geomx", "level.details", "level.hom", "build.lights", "level.fog_vol" + }; + u64 identity = 1469598103934665603ull; + for (LPCSTR name : files) + { + xr_string full_path = canonical_level_path ? canonical_level_path : ""; + if (!full_path.empty() && full_path.back() != '\\' && full_path.back() != '/') + full_path += '\\'; + full_path += name; + const CLocatorAPI::file* file = FS.exist(full_path.c_str()); + MixLevelIdentity(identity, file ? 1u : 0u); + if (!file) + continue; + MixLevelIdentity(identity, file->crc); + MixLevelIdentity(identity, file->size_real); + MixLevelIdentity(identity, file->size_compressed); + MixLevelIdentity(identity, file->modif); + } + // Hash only creation-time options. The raw bitfield contains padding and + // o.distortion is a per-frame phase flag, so hashing the struct causes false + // misses for otherwise identical static packages. + const CRender::_options& o = RImplementation.o; + const u32 option_values[] = + { + o.ssfx_branches, o.ssfx_blood, o.ssfx_rain, o.ssfx_hud_raindrops, o.ssfx_ssr, o.ssfx_terrain, + o.ssfx_volumetric, o.ssfx_water, o.ssfx_ao, o.ssfx_il, o.ssfx_core, o.ssfx_bloom, o.ssfx_sss, + o.ssfx_fog, o.ssfx_motionblur, o.ssfx_taa, o.ssfx_motionvectors, o.ssfx_glass, o.bug, + o.ssao_blur_on, o.ssao_opt_data, o.ssao_half_data, o.ssao_hbao, o.ssao_hdao, o.ssao_ultra, + o.hbao_vectorized, o.volsize, o.smapsize, o.depth16, o.mrt, o.mrtmixdepth, o.fp16_filter, + o.fp16_blend, o.albedo_wo, o.HW_smap, o.HW_smap_PCF, o.HW_smap_FETCH4, o.HW_smap_FORMAT, + o.nvstencil, o.nvdbt, o.nullrt, o.no_ram_textures, o.distortion_enabled, o.sunfilter, o.sunstatic, + o.sjitter, o.noshadows, o.Tshadows, o.disasm, o.advancedpp, o.volumetricfog, o.dx10_msaa, + o.dx10_msaa_hybrid, o.dx10_msaa_opt, o.dx10_sm4_1, o.dx10_msaa_alphatest, o.dx10_msaa_samples, + o.dx10_minmax_sm, o.dx10_minmax_sm_screenarea_threshold, o.dx11_enable_tessellation, + o.forcegloss, o.forceskinw, o.dx11_hdr10 + }; + for (u32 value : option_values) + MixLevelIdentity(identity, value); + MixLevelIdentity(identity, ps_r2_ls_flags.get()); + MixLevelIdentity(identity, ps_r2_ls_flags_ext.get()); + MixLevelIdentity(identity, crc32(&o.forcegloss_v, sizeof(o.forcegloss_v))); + MixLevelIdentity(identity, dm_current_size); + MixLevelIdentity(identity, crc32(&ps_current_detail_density, sizeof(ps_current_detail_density))); + MixLevelIdentity(identity, crc32(&ps_current_detail_height, sizeof(ps_current_detail_height))); + MixLevelIdentity(identity, g_level_asset_generation.load(std::memory_order_acquire)); + return identity; +} + +u64 CurrentLevelIdentity() +{ + const shared_str path = CurrentLevelCacheKey(); + return LevelIdentity(path.c_str()); +} + +struct PreparedLights +{ + xr_vector dynamic; + xr_vector hemi; +}; + +struct b_portal +{ + u16 sector_front; + u16 sector_back; + svector vertices; +}; + +void CopyReader(IReader& reader, xr_vector& data) +{ + data.resize(reader.length()); + if (!data.empty()) + reader.r(data.data(), static_cast(data.size())); +} + +void PrepareLevelLights(const xr_string& level_path, PreparedLights& prepared) +{ + xr_string level_name = level_path + "level"; + IReader* level = FS.r_open(level_name.c_str()); + R_ASSERT2(level, level_name.c_str()); + IReader* dynamic = level->open_chunk(fsL_LIGHT_DYNAMIC); + R_ASSERT(dynamic); + CopyReader(*dynamic, prepared.dynamic); + dynamic->close(); + FS.r_close(level); + + xr_string lights_name = level_path + "build.lights"; + if (!FS.exist(lights_name.c_str())) + return; + IReader* lights = FS.r_open(lights_name.c_str()); + IReader* hemi = lights->open_chunk(1); + if (hemi) + { + CopyReader(*hemi, prepared.hemi); + hemi->close(); + } + FS.r_close(lights); +} + +void PrepareLevelChunk(const xr_string& level_path, u32 chunk_id, xr_vector& data) +{ + xr_string level_name = level_path + "level"; + IReader* level = FS.r_open(level_name.c_str()); + R_ASSERT2(level, level_name.c_str()); + IReader* chunk = level->open_chunk(chunk_id); + R_ASSERT2(chunk, level_name.c_str()); + CopyReader(*chunk, data); + chunk->close(); + FS.r_close(level); +} + +void PrepareLevelSectors(const xr_string& level_path, xr_vector& portals, + xr_vector>& sectors, CDB::MODEL*& portal_model) +{ + xr_string level_name = level_path + "level"; + IReader* level = FS.r_open(level_name.c_str()); + R_ASSERT2(level, level_name.c_str()); + + IReader* portal_chunk = level->open_chunk(fsL_PORTALS); + if (portal_chunk) + { + CopyReader(*portal_chunk, portals); + portal_chunk->close(); + } + R_ASSERT(portals.size() % sizeof(b_portal) == 0); + + IReader* sector_chunk = level->open_chunk(fsL_SECTORS); + R_ASSERT(sector_chunk); + for (u32 index = 0;; ++index) + { + IReader* sector = sector_chunk->open_chunk(index); + if (!sector) + break; + sectors.emplace_back(); + CopyReader(*sector, sectors.back()); + sector->close(); + } + sector_chunk->close(); + FS.r_close(level); + + const b_portal* source = reinterpret_cast(portals.data()); + const u32 portal_count = static_cast(portals.size() / sizeof(b_portal)); + if (!portal_count) + return; + + CDB::Collector collector; + for (u32 index = 0; index < portal_count; ++index) + for (u32 vertex = 2; vertex < source[index].vertices.size(); ++vertex) + collector.add_face_packed_D(source[index].vertices[0], source[index].vertices[vertex - 1], + source[index].vertices[vertex], index); + if (collector.getTS() < 2) + { + Fvector v1, v2, v3; + v1.set(-20000.f, -20000.f, -20000.f); + v2.set(-20001.f, -20001.f, -20001.f); + v3.set(-20002.f, -20002.f, -20002.f); + collector.add_face_packed_D(v1, v2, v3, 0); + } + portal_model = xr_new(); + portal_model->build(collector.getV(), int(collector.getVS()), collector.getT(), int(collector.getTS())); +} + +void PrepareLevelFluids(const xr_string& level_path, xr_vector& prepared) +{ + prepared.clear(); + xr_string file_name = level_path + "level.fog_vol"; + if (!FS.exist(file_name.c_str())) + return; + + IReader* reader = FS.r_open(file_name.c_str()); + R_ASSERT2(reader, file_name.c_str()); + const u16 version = reader->r_u16(); + if (version == 3) + { + prepared.resize(reader->r_u32()); + for (dx103DFluidData::PreparedData& volume : prepared) + dx103DFluidVolume::Prepare(reader, volume); + } + FS.r_close(reader); +} + +void CommitVisualShaderTree(IRenderVisual* visual) +{ + if (!visual) + return; + visual->CommitShaderTexture(); + if (xr_vector* children = visual->get_children()) + for (IRenderVisual* child : *children) + CommitVisualShaderTree(child); + if (xr_vector* children = visual->get_children_invisible()) + for (IRenderVisual* child : *children) + CommitVisualShaderTree(child); +} + +void SuspendVisualShaderTree(IRenderVisual* visual) +{ + if (!visual) + return; + visual->SuspendShaderTexture(); + if (xr_vector* children = visual->get_children()) + for (IRenderVisual* child : *children) + SuspendVisualShaderTree(child); + if (xr_vector* children = visual->get_children_invisible()) + for (IRenderVisual* child : *children) + SuspendVisualShaderTree(child); +} + +bool IsWorkerSafeLevelVisual(u32 type) +{ + return type == MT_NORMAL || type == MT_PROGRESSIVE || type == MT_TREE_ST || type == MT_TREE_PM; +} + +} + +struct CRender::LevelStaticPackage +{ + shared_str key; + u64 identity = 0; + CDB::MODEL* portals_model = nullptr; + CSector* outdoor_sector = nullptr; + CDetailManager* details = nullptr; + CHOM::StaticData hom; + xr_vector light_dynamic; + xr_vector light_hemi; + CLight_DB* prepared_lights = nullptr; + xr_vector portals; + xr_vector sectors; + xr_vector swis; + xr_vector shaders; + xr_vector normal_declarations; + xr_vector fast_declarations; + xr_vector normal_vertex_buffers; + xr_vector fast_vertex_buffers; + xr_vector normal_index_buffers; + xr_vector fast_index_buffers; + xr_vector visuals; + xr_vector shader_descriptions; + xr_vector shader_indices; + xr_vector cpp_shader_results; + xr_vector fluid_descriptors; + bool normal_geometry_ready = false; + bool fast_geometry_ready = false; + xr_vector visual_data; + xr_vector prepared_portals; + xr_vector> prepared_sectors; + CDB::MODEL* prepared_portals_model = nullptr; + xr_vector prepared_lights_dynamic; + xr_vector prepared_lights_hemi; + bool shader_recipes_ready = false; + bool visual_leaves_ready = false; + bool sectors_ready = false; + bool details_ready = false; + bool hom_ready = false; + bool lights_ready = false; + bool fluid_descriptors_ready = false; + + ~LevelStaticPackage() + { + if (details) + { + details->Unload(); + xr_delete(details); + } + xr_delete(prepared_lights); + xr_delete(portals_model); + xr_delete(prepared_portals_model); + for (IRender_Sector*& sector : sectors) + xr_delete(sector); + for (IRender_Portal*& portal : portals) + xr_delete(portal); + for (dxRender_Visual*& visual : visuals) + { + visual->Release(); + xr_delete(visual); + } + for (FSlideWindowItem& swi : swis) + xr_free(swi.sw); + for (ID3DVertexBuffer*& buffer : normal_vertex_buffers) + _RELEASE(buffer); + for (ID3DVertexBuffer*& buffer : fast_vertex_buffers) + _RELEASE(buffer); + for (ID3DIndexBuffer*& buffer : normal_index_buffers) + _RELEASE(buffer); + for (ID3DIndexBuffer*& buffer : fast_index_buffers) + _RELEASE(buffer); + shaders.clear_and_free(); + } +}; + +bool CRender::level_StaticCacheReady(LPCSTR canonical_level_path) +{ + const xr_string path = NormalizeLevelPath(canonical_level_path); + if (path.empty()) + return false; + const u64 identity = LevelIdentity(path.c_str()); + for (const LevelStaticPackage* package : m_level_cache) + if (package->key.equal(path.c_str()) && package->identity == identity) + return true; + return false; +} + +void CRender::WaitLevelPrepare() +{ + NativeLoadExecutor::Batch batch = m_level_prepare_batch; + m_level_prepare_batch = {}; + if (batch.Valid()) + NativeLoadExecutor::Instance().Wait(batch); + m_level_prepare_tasks.wait(); +} + +void CRender::level_Prepare(LPCSTR canonical_level_path) +{ + const xr_string path = NormalizeLevelPath(canonical_level_path); + if (path.empty() || (b_loaded && m_active_level_key.equal(path.c_str()))) + return; + NativeLoadExecutor& executor = NativeLoadExecutor::Instance(); + const u64 generation = executor.CurrentGeneration(); + if (m_prepared_level_path.equal(path.c_str()) && m_level_prepare_generation == generation) + return; + const u64 identity = LevelIdentity(path.c_str()); + bool static_cache_hit = false; + for (const LevelStaticPackage* package : m_level_cache) + if (package->key.equal(path.c_str()) && package->identity == identity) + { + static_cache_hit = true; + break; + } + + WaitLevelPrepare(); + if (m_prepared_level_geometry) + dxRenderDeviceRender::Instance().Resources->ReleaseLevelShaderCache( + m_prepared_level_geometry->key.c_str(), m_prepared_level_geometry->identity); + xr_delete(m_prepared_level_geometry); + m_prepared_level_path = path.c_str(); + m_level_prepare_generation = generation; + m_level_prepare_started_at = Device.TimerAsync(); + m_level_prepare_batch = executor.BeginBatch(executor.CurrentGeneration()); + auto submit = [this, &executor](NativeLoadPriority priority, auto&& work) + { + if (m_level_prepare_batch.Valid()) + executor.Submit(m_level_prepare_batch, priority, std::forward(work)); + else + m_level_prepare_tasks.run(std::forward(work)); + }; + submit(NativeLoadPriority::Geometry, [path]() { CObjectSpace::PrepareStatic(path.c_str()); }); + if (static_cache_hit) + { + Msg("* [LEVEL PREPARE] static cache hit: %s", path.c_str()); + return; + } + m_prepared_level_geometry = xr_new(); + m_prepared_level_geometry->key = path.c_str(); + m_prepared_level_geometry->identity = identity; + LevelStaticPackage* geometry = m_prepared_level_geometry; + const bool detail_recipe_changed = dm_current_size != dm_size || + ps_current_detail_density != ps_r__Detail_density || ps_current_detail_height != ps_r__Detail_height; + if (!g_dedicated_server && (!b_loaded || !detail_recipe_changed)) + { + CDetailManager::SSwingValue swing[2]; + CDetailManager::SnapshotSwing(swing); + const CDetailManager::SSwingValue normal_swing = swing[0]; + const CDetailManager::SSwingValue fast_swing = swing[1]; + submit(NativeLoadPriority::Environment, [path, geometry, normal_swing, fast_swing]() + { + const CDetailManager::SSwingValue prepared_swing[2] = {normal_swing, fast_swing}; + geometry->details = xr_new(); + geometry->details->Load(false, false, path.c_str(), prepared_swing); + geometry->details_ready = true; + }); + } + submit(NativeLoadPriority::Environment, [this, path, geometry]() + { + HOM.Prepare(path.c_str(), geometry->hom); + geometry->hom_ready = true; + }); + submit(NativeLoadPriority::Environment, [path, geometry]() + { + PreparedLights lights; + PrepareLevelLights(path, lights); + geometry->prepared_lights_dynamic.swap(lights.dynamic); + geometry->prepared_lights_hemi.swap(lights.hemi); + geometry->prepared_lights = xr_new(); + geometry->prepared_lights->Prepare(geometry->prepared_lights_dynamic, + geometry->prepared_lights_hemi); + geometry->lights_ready = true; + }); + submit(NativeLoadPriority::Geometry, [this, path, geometry]() + { + PrepareLevelChunk(path, fsL_VISUALS, geometry->visual_data); + auto load_normal = [this, path, geometry]() + { + xr_string file_name = path + "level.geom"; + CStreamReader* reader = FS.rs_open(nullptr, file_name.c_str()); + R_ASSERT2(reader, file_name.c_str()); + LoadBuffers(reader, geometry->normal_declarations, geometry->normal_vertex_buffers, + geometry->normal_index_buffers); + LoadSWIs(reader, geometry->swis); + FS.r_close(reader); + }; + auto load_fast = [this, path, geometry]() + { + xr_string file_name = path + "level.geomx"; + CStreamReader* reader = FS.rs_open(nullptr, file_name.c_str()); + R_ASSERT2(reader, file_name.c_str()); + LoadBuffers(reader, geometry->fast_declarations, geometry->fast_vertex_buffers, + geometry->fast_index_buffers); + FS.r_close(reader); + }; + + NativeLoadExecutor& executor = NativeLoadExecutor::Instance(); + NativeLoadExecutor::Batch batch = executor.BeginBatch(executor.CurrentGeneration()); + if (batch.Valid()) + { + executor.Submit(batch, NativeLoadPriority::Geometry, load_normal); + executor.Submit(batch, NativeLoadPriority::Geometry, load_fast); + executor.Wait(batch); + } + else + { + xr_task_group tasks; + tasks.run(load_normal); + tasks.run(load_fast); + tasks.wait(); + } + + geometry->normal_geometry_ready = true; + geometry->fast_geometry_ready = true; + VisualGeometrySource source = + { + &geometry->normal_declarations, &geometry->fast_declarations, + &geometry->normal_vertex_buffers, &geometry->fast_vertex_buffers, + &geometry->normal_index_buffers, &geometry->fast_index_buffers, &geometry->swis + }; + IReader visuals(geometry->visual_data.data(), static_cast(geometry->visual_data.size())); + PrepareVisuals(&visuals, geometry->visuals, &source); + geometry->visual_leaves_ready = true; + }); + submit(NativeLoadPriority::Environment, [path, geometry]() + { + PrepareLevelSectors(path, geometry->prepared_portals, geometry->prepared_sectors, + geometry->prepared_portals_model); + geometry->sectors_ready = true; + }); + submit(NativeLoadPriority::ShaderTexture, [path, geometry, identity]() + { + xr_string file_name = path + "level"; + IReader* level = FS.r_open(file_name.c_str()); + R_ASSERT2(level, file_name.c_str()); + IReader* shaders = level->open_chunk(fsL_SHADERS); + R_ASSERT2(shaders, "Level doesn't builded correctly."); + const u32 count = shaders->r_u32(); + geometry->shader_indices.assign(count, u32(-1)); + xr_map unique_indices; + for (u32 i = 0; i < count; ++i) + { + string512 shader_name, textures; + LPCSTR description = LPCSTR(shaders->pointer()); + shaders->skip_stringZ(); + if (!description[0]) + continue; + xr_strcpy(shader_name, description); + LPSTR delimiter = strchr(shader_name, '/'); + R_ASSERT(delimiter); + *delimiter = 0; + xr_strcpy(textures, delimiter + 1); + xr_string key = shader_name; + key += '\n'; + key += textures; + auto existing = unique_indices.find(key); + if (existing != unique_indices.end()) + { + geometry->shader_indices[i] = existing->second; + continue; + } + geometry->shader_indices[i] = static_cast(geometry->shader_descriptions.size()); + unique_indices.emplace(std::move(key), geometry->shader_indices[i]); + geometry->shader_descriptions.push_back({shader_name, textures}); + } + shaders->close(); + FS.r_close(level); + + geometry->cpp_shader_results.resize(geometry->shader_descriptions.size()); + NativeLoadExecutor& shader_executor = NativeLoadExecutor::Instance(); + NativeLoadExecutor::Batch shader_batch = shader_executor.BeginBatch(shader_executor.CurrentGeneration()); + auto compile_shader = [path, geometry, identity](u32 index) + { + const LevelShaderDescription& description = geometry->shader_descriptions[index]; + geometry->cpp_shader_results[index] = dxRenderDeviceRender::Instance().Resources->CreateLevelCppShader( + description.shader.c_str(), description.textures.c_str(), nullptr, nullptr, identity, path.c_str()); + }; + if (shader_batch.Valid()) + { + for (u32 index = 0; index < geometry->shader_descriptions.size(); ++index) + shader_executor.Submit(shader_batch, NativeLoadPriority::ShaderTexture, + [compile_shader, index]() { compile_shader(index); }); + shader_executor.Wait(shader_batch); + } + else + xr_parallel_for(0u, static_cast(geometry->shader_descriptions.size()), compile_shader); + geometry->shader_recipes_ready = true; + }); + const bool prepare_fluids = RImplementation.o.volumetricfog; + submit(NativeLoadPriority::Environment, [path, geometry, prepare_fluids]() + { + if (prepare_fluids) + PrepareLevelFluids(path, geometry->fluid_descriptors); + geometry->fluid_descriptors_ready = true; + }); + Msg("* [LEVEL PREPARE] started: %s", path.c_str()); +} + +void CRender::level_InvalidateStaticCache() +{ + g_level_asset_generation.fetch_add(1, std::memory_order_acq_rel); + ReleaseLevelCache(); +} + +void CRender::level_BeginAsyncLoad() +{ + DiscardPreparedVisuals(); + m_level_shader_descriptions.clear(); + m_level_shader_cpp_results.clear(); + m_level_shader_owner_results.clear(); + m_level_shader_identity = 0; + m_level_shader_owner_required = false; + xr_delete(m_level_prepared_hom.model); + xr_free(m_level_prepared_hom.tris); + m_level_prepared_hom.enabled = FALSE; + m_level_prepared_lights_dynamic.clear(); + m_level_prepared_lights_hemi.clear(); + xr_delete(m_level_prepared_lights); + m_level_prepared_portals.clear(); + m_level_prepared_sectors.clear(); + xr_delete(m_level_prepared_portals_model); + m_level_owner_reader = nullptr; + m_level_cache_attach_pending = false; + m_level_prepared_sectors_ready = false; + m_level_async_failed.store(false, std::memory_order_release); +} + +void CRender::CommitLevelShaderComponentsOwner() +{ + CTimer timer; + timer.Start(); + if (m_level_async_failed.load(std::memory_order_acquire)) + return; + if (!m_level_shader_owner_required) + { + if (m_level_cache_attach_pending) + { + CTimer cache_timer; + cache_timer.Start(); + HOM.Resume(m_level_prepared_hom); + g_pGameLevel->ObjectSpace.GetStaticModel()->syncronize(); + CTimer cache_lights_timer; + cache_lights_timer.Start(); + R_ASSERT(m_level_prepared_lights); + Lights.Swap(*m_level_prepared_lights); + Lights.CommitPrepared(); + xr_delete(m_level_prepared_lights); + Msg("* [LEVEL CACHE] R4 owner lights commit: %d ms", cache_lights_timer.GetElapsed_ms()); + m_active_level_lights_dynamic.swap(m_level_prepared_lights_dynamic); + m_active_level_lights_hemi.swap(m_level_prepared_lights_hemi); + if (Details) + Details->Resume(); + Commit3DFluid(); + m_level_cache_attach_pending = false; + Msg("* [LEVEL CACHE] R4 owner attach: %d ms", cache_timer.GetElapsed_ms()); + } + return; + } + + CResourceManager* resources = dxRenderDeviceRender::Instance().Resources; + m_level_shader_owner_results.resize(m_level_shader_descriptions.size()); + u32 lua_count = 0; + for (u32 i = 0; i < m_level_shader_descriptions.size(); ++i) + { + const LevelShaderDescription& description = m_level_shader_descriptions[i]; + if (resources->_lua_HasShader(description.shader.c_str())) + { + ++lua_count; + m_level_shader_owner_results[i] = resources->CreateLevelShader( + description.shader.c_str(), description.textures.c_str(), m_level_shader_identity); + } + else + { + m_level_shader_owner_results[i] = m_level_shader_cpp_results[i]; + if (!m_level_shader_owner_results[i]) + m_level_shader_owner_results[i] = resources->CreateLevelShader( + description.shader.c_str(), description.textures.c_str(), m_level_shader_identity); + } + } + Msg("* [LEVEL LOAD] R4 owner shader commit: %d ms (%u unique, %u lua)", timer.GetElapsed_ms(), + static_cast(m_level_shader_descriptions.size()), lua_count); + HOM.Resume(m_level_prepared_hom); + if (Details) + { + Details->CommitShaders(); + if (m_level_cache_attach_pending) + Details->Resume(); + else + Details->Publish(); + } + CTimer lights_timer; + lights_timer.Start(); + R_ASSERT(m_level_prepared_lights); + Lights.Swap(*m_level_prepared_lights); + Lights.CommitPrepared(); + xr_delete(m_level_prepared_lights); + Msg("* [LEVEL LOAD] R4 owner lights commit: %d ms", lights_timer.GetElapsed_ms()); + m_active_level_lights_dynamic.swap(m_level_prepared_lights_dynamic); + m_active_level_lights_hemi.swap(m_level_prepared_lights_hemi); + if (m_level_cache_attach_pending) + { + g_pGameLevel->ObjectSpace.GetStaticModel()->syncronize(); + Commit3DFluid(); + m_level_cache_attach_pending = false; + } +} + +void CRender::CommitLevelEnvironmentOwner() +{ + if (m_level_async_failed.load(std::memory_order_acquire)) + return; + R_ASSERT(m_level_owner_reader); + CTimer environment_timer; + environment_timer.Start(); + g_pGamePersistent->LoadTitle(); + if (m_level_prepared_visuals_ready) + { + R_ASSERT(Visuals.empty()); + IReader visuals(m_level_prepared_visual_data.data(), + static_cast(m_level_prepared_visual_data.size())); + LinkPreparedVisuals(&visuals, m_level_prepared_visuals); + for (dxRender_Visual* visual : m_level_prepared_visuals) + CommitVisualShaderTree(visual); + Visuals.swap(m_level_prepared_visuals); + m_level_prepared_visual_data.clear_and_free(); + m_level_prepared_visuals_ready = false; + } + for (dxRender_Visual* visual : Visuals) + CommitVisualShaderTree(visual); + if (m_level_prepared_sectors_ready) + LoadPreparedSectors(); + else + LoadSectors(m_level_owner_reader); + g_pGameLevel->ObjectSpace.GetStaticModel()->syncronize(); + Commit3DFluid(); + Msg("* [LEVEL LOAD] R4 owner sectors/fluid commit: %d ms", environment_timer.GetElapsed_ms()); +} + +void CRender::level_AbortAsyncLoad() +{ + m_level_async_failed.store(true, std::memory_order_release); + try + { + WaitLevelPrepare(); + } + catch (...) + { + // The load path owns and reports the original task failure. Preparation + // has still been drained, so its private package can now be discarded. + } + if (m_prepared_level_geometry) + dxRenderDeviceRender::Instance().Resources->ReleaseLevelShaderCache( + m_prepared_level_geometry->key.c_str(), m_prepared_level_geometry->identity); + xr_delete(m_prepared_level_geometry); + m_prepared_level_path = nullptr; + m_level_prepare_generation = 0; +} + void CRender::level_Load(IReader* fs) { + CTimer level_timer; + level_timer.Start(); R_ASSERT(0!=g_pGameLevel); + const shared_str level_key = CurrentLevelCacheKey(); + xr_unique_ptr prepared_geometry; + if (m_prepared_level_path.size()) + { + WaitLevelPrepare(); + if (m_prepared_level_path.equal(level_key)) + Msg("* [LEVEL PREPARE] barrier: %u ms total (%s)", Device.TimerAsync() - m_level_prepare_started_at, + level_key.c_str()); + prepared_geometry.reset(m_prepared_level_geometry); + m_prepared_level_geometry = nullptr; + m_prepared_level_path = nullptr; + m_level_prepare_generation = 0; + } + const bool detail_recipe_changed = dm_current_size != dm_size || + ps_current_detail_density != ps_r__Detail_density || ps_current_detail_height != ps_r__Detail_height; + if (detail_recipe_changed && !b_loaded) + ReleaseLevelCache(); + const u64 level_identity = CurrentLevelIdentity(); + const bool use_prepared_fluids = prepared_geometry && prepared_geometry->key.equal(level_key) && + prepared_geometry->identity == level_identity && prepared_geometry->fluid_descriptors_ready; + xr_vector prepared_fluids; + if (use_prepared_fluids) + prepared_fluids.swap(prepared_geometry->fluid_descriptors); + if (b_loaded) + { + level_Unload(); + // Cached detail arrays are dimensioned by the currently applied global + // recipe. Destroy them before CDetailManager applies a new recipe. + if (detail_recipe_changed) + ReleaseLevelCache(); + } + if (RestoreLevelStaticPackage(level_key, level_identity)) + { + if (prepared_geometry && (!prepared_geometry->key.equal(level_key) || + prepared_geometry->identity != level_identity)) + { + dxRenderDeviceRender::Instance().Resources->ReleaseLevelShaderCache( + prepared_geometry->key.c_str(), prepared_geometry->identity); + } + prepared_geometry.reset(); + CommitLevelShaderComponentsOwner(); + for (u32 i = 0; i < m_active_level_shader_indices.size(); ++i) + if (m_active_level_shader_indices[i] != u32(-1)) + Shaders[i] = m_level_shader_owner_results[m_active_level_shader_indices[i]]; + for (dxRender_Visual* visual : Visuals) + CommitVisualShaderTree(visual); + m_active_level_shader_descriptions = m_level_shader_descriptions; + m_active_level_shader_cpp_results = m_level_shader_cpp_results; + Msg("* [LEVEL CACHE] R4 attach: %d ms (%s)", level_timer.GetElapsed_ms(), level_key.c_str()); + return; + } R_ASSERT(!b_loaded); + const bool use_prepared_geometry = prepared_geometry && prepared_geometry->key.equal(level_key) && + prepared_geometry->identity == level_identity && prepared_geometry->normal_geometry_ready && + prepared_geometry->fast_geometry_ready; + const bool use_prepared_shaders = prepared_geometry && prepared_geometry->key.equal(level_key) && + prepared_geometry->identity == level_identity && prepared_geometry->shader_recipes_ready; + const bool use_prepared_visuals = use_prepared_geometry && prepared_geometry->visual_leaves_ready; + const bool use_prepared_sectors = prepared_geometry && prepared_geometry->key.equal(level_key) && + prepared_geometry->identity == level_identity && prepared_geometry->sectors_ready; + const bool use_prepared_details = prepared_geometry && prepared_geometry->key.equal(level_key) && + prepared_geometry->identity == level_identity && prepared_geometry->details_ready; + const bool use_prepared_hom = prepared_geometry && prepared_geometry->key.equal(level_key) && + prepared_geometry->identity == level_identity && prepared_geometry->hom_ready; + const bool use_prepared_lights = prepared_geometry && prepared_geometry->key.equal(level_key) && + prepared_geometry->identity == level_identity && prepared_geometry->lights_ready && + prepared_geometry->prepared_lights; + m_level_fluid_descriptors.swap(prepared_fluids); + if (!use_prepared_fluids && RImplementation.o.volumetricfog) + PrepareLevelFluids(level_key.c_str(), m_level_fluid_descriptors); + xr_vector unique_shaders; + xr_vector shader_indices; + CDetailManager* prepared_details = nullptr; + if (use_prepared_geometry) + { + SWIs.swap(prepared_geometry->swis); + nDC.swap(prepared_geometry->normal_declarations); + xDC.swap(prepared_geometry->fast_declarations); + nVB.swap(prepared_geometry->normal_vertex_buffers); + xVB.swap(prepared_geometry->fast_vertex_buffers); + nIB.swap(prepared_geometry->normal_index_buffers); + xIB.swap(prepared_geometry->fast_index_buffers); + Msg("* [LEVEL PREPARE] geometry committed: %s", level_key.c_str()); + } + if (use_prepared_shaders) + { + unique_shaders.swap(prepared_geometry->shader_descriptions); + shader_indices.swap(prepared_geometry->shader_indices); + m_level_shader_cpp_results.swap(prepared_geometry->cpp_shader_results); + Msg("* [LEVEL PREPARE] C++ shader recipes committed: %u", static_cast(unique_shaders.size())); + } + if (use_prepared_visuals) + { + m_level_prepared_visual_data.swap(prepared_geometry->visual_data); + m_level_prepared_visuals.swap(prepared_geometry->visuals); + m_level_prepared_visuals_ready = true; + } + if (use_prepared_sectors) + { + m_level_prepared_portals.swap(prepared_geometry->prepared_portals); + m_level_prepared_sectors.swap(prepared_geometry->prepared_sectors); + m_level_prepared_portals_model = prepared_geometry->prepared_portals_model; + prepared_geometry->prepared_portals_model = nullptr; + m_level_prepared_sectors_ready = true; + } + if (use_prepared_details) + { + prepared_details = prepared_geometry->details; + prepared_geometry->details = nullptr; + } + if (use_prepared_hom) + { + m_level_prepared_hom.model = prepared_geometry->hom.model; + m_level_prepared_hom.tris = prepared_geometry->hom.tris; + m_level_prepared_hom.enabled = prepared_geometry->hom.enabled; + prepared_geometry->hom.model = nullptr; + prepared_geometry->hom.tris = nullptr; + prepared_geometry->hom.enabled = FALSE; + } + if (use_prepared_lights) + { + m_level_prepared_lights_dynamic.swap(prepared_geometry->prepared_lights_dynamic); + m_level_prepared_lights_hemi.swap(prepared_geometry->prepared_lights_hemi); + m_level_prepared_lights = prepared_geometry->prepared_lights; + prepared_geometry->prepared_lights = nullptr; + } + if (prepared_geometry && !use_prepared_shaders) + dxRenderDeviceRender::Instance().Resources->ReleaseLevelShaderCache( + prepared_geometry->key.c_str(), prepared_geometry->identity); + prepared_geometry.reset(); // Begin pApp->LoadBegin(); @@ -33,11 +868,14 @@ void CRender::level_Load(IReader* fs) // Shaders // g_pGamePersistent->LoadTitle ("st_loading_shaders"); g_pGamePersistent->LoadTitle(); + if (!use_prepared_shaders) { chunk = fs->open_chunk(fsL_SHADERS); R_ASSERT2(chunk, "Level doesn't builded correctly."); u32 count = chunk->r_u32(); Shaders.resize(count); + shader_indices.assign(count, u32(-1)); + xr_map unique_indices; for (u32 i = 0; i < count; i++) // skip first shader as "reserved" one { string512 n_sh, n_tlist; @@ -48,165 +886,438 @@ void CRender::level_Load(IReader* fs) LPSTR delim = strchr(n_sh, '/'); *delim = 0; xr_strcpy(n_tlist, delim + 1); - Shaders[i] = dxRenderDeviceRender::Instance().Resources->Create(n_sh, n_tlist); + + xr_string key = n_sh; + key += '\n'; + key += n_tlist; + auto existing = unique_indices.find(key); + if (existing != unique_indices.end()) + { + shader_indices[i] = existing->second; + continue; + } + + shader_indices[i] = static_cast(unique_shaders.size()); + unique_indices.emplace(std::move(key), shader_indices[i]); + unique_shaders.push_back({n_sh, n_tlist}); } chunk->close(); } + else + Shaders.resize(shader_indices.size()); + + m_level_shader_descriptions = unique_shaders; + m_level_shader_identity = level_identity; + m_level_shader_owner_required = true; + if (!use_prepared_shaders) + m_level_shader_cpp_results.resize(unique_shaders.size()); + + NativeLoadExecutor& load_executor = NativeLoadExecutor::Instance(); + NativeLoadExecutor::Batch component_batch = load_executor.BeginBatch(load_executor.CurrentGeneration()); + xr_task_group fallback_component_tasks; + auto submit_component = [&load_executor, &component_batch, &fallback_component_tasks]( + NativeLoadPriority priority, auto&& work) + { + if (component_batch.Valid()) + load_executor.Submit(component_batch, priority, std::forward(work)); + else + fallback_component_tasks.run(std::forward(work)); + }; + CTimer shader_timer; + shader_timer.Start(); + if (!use_prepared_shaders) + for (u32 index = 0; index < unique_shaders.size(); ++index) + submit_component(NativeLoadPriority::ShaderTexture, [this, &unique_shaders, level_identity, level_key, index]() + { + const LevelShaderDescription& description = unique_shaders[index]; + m_level_shader_cpp_results[index] = dxRenderDeviceRender::Instance().Resources->CreateLevelCppShader( + description.shader.c_str(), description.textures.c_str(), nullptr, nullptr, level_identity, + level_key.c_str()); + }); // Components Wallmarks = xr_new(); - Details = xr_new(); + Details = prepared_details ? prepared_details : xr_new(); + if (!use_prepared_lights) + submit_component(NativeLoadPriority::Environment, [this, level_key]() + { + CTimer timer; + timer.Start(); + PreparedLights prepared_lights; + PrepareLevelLights(level_key.c_str(), prepared_lights); + m_level_prepared_lights_dynamic.swap(prepared_lights.dynamic); + m_level_prepared_lights_hemi.swap(prepared_lights.hemi); + m_level_prepared_lights = xr_new(); + m_level_prepared_lights->Prepare(m_level_prepared_lights_dynamic, m_level_prepared_lights_hemi); + Msg("* [LEVEL LOAD] R4 lights prepare: %d ms", timer.GetElapsed_ms()); + }); + if (!use_prepared_hom) + submit_component(NativeLoadPriority::Environment, [this, level_key]() + { + CTimer timer; + timer.Start(); + HOM.Prepare(level_key.c_str(), m_level_prepared_hom); + Msg("* [LEVEL LOAD] R4 HOM: %d ms", timer.GetElapsed_ms()); + }); + if (!g_dedicated_server && !use_prepared_details) + { + CDetailManager::SSwingValue swing[2]; + CDetailManager::SnapshotSwing(swing); + const CDetailManager::SSwingValue normal_swing = swing[0]; + const CDetailManager::SSwingValue fast_swing = swing[1]; + submit_component(NativeLoadPriority::Environment, + [this, level_key, normal_swing, fast_swing]() + { + CTimer timer; + timer.Start(); + const CDetailManager::SSwingValue prepared_swing[2] = {normal_swing, fast_swing}; + Details->Load(false, false, level_key.c_str(), prepared_swing); + Msg("* [LEVEL LOAD] R4 details prepare: %d ms", timer.GetElapsed_ms()); + }); + } + + auto commit_prepared_components = [&]() + { + if (component_batch.Valid()) + load_executor.Wait(component_batch); + fallback_component_tasks.wait(); + if (!use_prepared_shaders) + Msg("* [LEVEL LOAD] R4 C++ shader prepare: %d ms (%u speculative)", shader_timer.GetElapsed_ms(), + static_cast(unique_shaders.size())); + CommitLevelShaderComponentsOwner(); + R_ASSERT2(!m_level_async_failed.load(std::memory_order_acquire), "R4 owner shader/environment commit failed"); + for (u32 i = 0; i < shader_indices.size(); ++i) + if (shader_indices[i] != u32(-1)) + Shaders[i] = m_level_shader_owner_results[shader_indices[i]]; + m_active_level_shader_descriptions = m_level_shader_descriptions; + m_active_level_shader_cpp_results = m_level_shader_cpp_results; + m_active_level_shader_indices = shader_indices; + }; if (!g_dedicated_server) { // VB,IB,SWI // g_pGamePersistent->LoadTitle("st_loading_geometry"); g_pGamePersistent->LoadTitle(); + CTimer geometry_timer; + geometry_timer.Start(); + NativeLoadExecutor::Batch geometry_batch = load_executor.BeginBatch(load_executor.CurrentGeneration()); + xr_task_group fallback_geometry_tasks; + auto submit_geometry = [&load_executor, &geometry_batch, &fallback_geometry_tasks](auto&& work) + { + if (geometry_batch.Valid()) + load_executor.Submit(geometry_batch, NativeLoadPriority::Geometry, + std::forward(work)); + else + fallback_geometry_tasks.run(std::forward(work)); + }; + if (!use_prepared_geometry) + submit_geometry([this, level_key]() { - CStreamReader* geom = FS.rs_open("$level$", "level.geom"); - R_ASSERT2(geom, "level.geom"); - LoadBuffers(geom,FALSE); - LoadSWIs(geom); + CTimer timer; + timer.Start(); + xr_string file_name = xr_string(level_key.c_str()) + "level.geom"; + CStreamReader* geom = FS.rs_open(nullptr, file_name.c_str()); + R_ASSERT2(geom, file_name.c_str()); + LoadBuffers(geom, nDC, nVB, nIB); + LoadSWIs(geom, SWIs); FS.r_close(geom); - } + Msg("* [LEVEL LOAD] R4 level.geom: %d ms", timer.GetElapsed_ms()); + }); //...and alternate/fast geometry + if (!use_prepared_geometry) + submit_geometry([this, level_key]() { - CStreamReader* geom = FS.rs_open("$level$", "level.geomx"); - R_ASSERT2(geom, "level.geomX"); - LoadBuffers(geom,TRUE); + CTimer timer; + timer.Start(); + xr_string file_name = xr_string(level_key.c_str()) + "level.geomx"; + CStreamReader* geom = FS.rs_open(nullptr, file_name.c_str()); + R_ASSERT2(geom, file_name.c_str()); + LoadBuffers(geom, xDC, xVB, xIB); FS.r_close(geom); - } + Msg("* [LEVEL LOAD] R4 level.geomx: %d ms", timer.GetElapsed_ms()); + }); + commit_prepared_components(); + if (geometry_batch.Valid()) + load_executor.Wait(geometry_batch); + fallback_geometry_tasks.wait(); + Msg("* [LEVEL LOAD] R4 geometry barrier: %d ms", geometry_timer.GetElapsed_ms()); // Visuals // g_pGamePersistent->LoadTitle("st_loading_spatial_db"); g_pGamePersistent->LoadTitle(); - chunk = fs->open_chunk(fsL_VISUALS); - LoadVisuals(chunk); - chunk->close(); - - // Details - // g_pGamePersistent->LoadTitle("st_loading_details"); - g_pGamePersistent->LoadTitle(); - Details->Load(); + CTimer visuals_timer; + visuals_timer.Start(); + if (!use_prepared_visuals) + { + IReader* visuals = fs->open_chunk(fsL_VISUALS); + R_ASSERT(visuals); + CopyReader(*visuals, m_level_prepared_visual_data); + visuals->close(); + IReader prepared(m_level_prepared_visual_data.data(), + static_cast(m_level_prepared_visual_data.size())); + PrepareVisuals(&prepared, m_level_prepared_visuals, nullptr); + m_level_prepared_visuals_ready = true; + } + Msg("* [LEVEL LOAD] R4 visual leaves prepare: %d ms", visuals_timer.GetElapsed_ms()); + } + else + { + commit_prepared_components(); } - // Sectors - // g_pGamePersistent->LoadTitle("st_loading_sectors_portals"); - g_pGamePersistent->LoadTitle(); - LoadSectors(fs); - - // 3D Fluid - Load3DFluid(); - - // HOM - HOM.Load(); - - // Lights - // pApp->LoadTitle ("Loading lights..."); - LoadLights(fs); + // Sectors, spatial publication and fluid attachment are owner-thread state. + m_level_owner_reader = fs; + CommitLevelEnvironmentOwner(); + R_ASSERT2(!m_level_async_failed.load(std::memory_order_acquire), "R4 owner sectors/fluid commit failed"); + m_level_owner_reader = nullptr; // End pApp->LoadEnd(); // signal loaded b_loaded = TRUE; + m_active_level_key = level_key; + m_active_level_identity = level_identity; + Msg("* [LEVEL LOAD] R4 total: %d ms", level_timer.GetElapsed_ms()); } void CRender::level_Unload() { - if (0 == g_pGameLevel) return; if (!b_loaded) return; + const bool clear_resources = psDeviceFlags2.test(rsClearAllResources); + const bool clear_models = psDeviceFlags2.test(rsClearModels) || clear_resources; + if (!clear_models) + { + dxRenderDeviceRender::Instance().Resources->WaitForTextureLoads(); + LevelStaticPackage* package = DetachLevelStaticPackage(); + Msg("* [LEVEL CACHE] R4 retained: %s", package->key.c_str()); + m_level_cache.push_back(package); + EvictLevelCacheUnderPressure(); + return; + } + + DestroyActiveLevel(); + ReleaseLevelCache(); + Models->ClearPool(true); + Visuals.clear_and_free(); + if (clear_resources) + { + dxRenderDeviceRender::Instance().Resources->UnloadAllTexturesOnLevelUnload(); + dxRenderDeviceRender::Instance().ResourcesDestroyNecessaryTextures(); + dxRenderDeviceRender::Instance().Resources->Evict(); + } + dxRenderDeviceRender::Instance().Resources->Dump(false); +} + +CRender::LevelStaticPackage* CRender::DetachLevelStaticPackage() +{ + VERIFY(b_loaded); + LevelStaticPackage* package = xr_new(); + package->key = m_active_level_key; + package->identity = m_active_level_identity; + GMBase.clear(); GMRainWet.clear(); for (sun::cascade& cascade : m_sun_cascades) cascade.GMCascade.clear(); - u32 I; - - // HOM - HOM.Unload(); - - //*** Details - Details->Unload(); + Remove3DFluid(); + package->fluid_descriptors.swap(m_level_fluid_descriptors); + HOM.Suspend(package->hom); + if (Details) + { + Details->SuspendShaders(); + Details->Suspend(); + } + Lights.PrepareForCache(); + package->prepared_lights = xr_new(); + package->prepared_lights->Swap(Lights); + + package->details = Details; + Details = nullptr; + package->light_dynamic.swap(m_active_level_lights_dynamic); + package->light_hemi.swap(m_active_level_lights_hemi); + package->portals_model = rmPortals; + rmPortals = nullptr; + package->outdoor_sector = pOutdoorSector; + Portals.swap(package->portals); + Sectors.swap(package->sectors); + SWIs.swap(package->swis); + for (ref_shader& shader : Shaders) + shader = nullptr; + Shaders.swap(package->shaders); + package->shader_descriptions.swap(m_active_level_shader_descriptions); + package->shader_indices.swap(m_active_level_shader_indices); + package->cpp_shader_results.swap(m_active_level_shader_cpp_results); + m_level_shader_descriptions.clear(); + m_level_shader_cpp_results.clear(); + m_level_shader_owner_results.clear(); + nDC.swap(package->normal_declarations); + xDC.swap(package->fast_declarations); + nVB.swap(package->normal_vertex_buffers); + xVB.swap(package->fast_vertex_buffers); + nIB.swap(package->normal_index_buffers); + xIB.swap(package->fast_index_buffers); + for (dxRender_Visual* visual : Visuals) + SuspendVisualShaderTree(visual); + Visuals.swap(package->visuals); - //*** Sectors - // 1. - xr_delete(rmPortals); - pLastSector = 0; - pOutdoorSector = 0; - vLastCameraPos.set(0, 0, 0); - // 2. - for (I = 0; I < Sectors.size(); I++) xr_delete(Sectors[I]); - Sectors.clear(); - // 3. - for (I = 0; I < Portals.size(); I++) xr_delete(Portals[I]); - Portals.clear(); - - //*** Lights - // Glows.Unload (); - Lights.Unload(); - - //*** Visuals - for (I = 0; I < Visuals.size(); I++) - { - Visuals[I]->Release(); - xr_delete(Visuals[I]); - } - Visuals.clear(); - - //*** SWI - for (I = 0; I < SWIs.size(); I++)xr_free(SWIs[I].sw); - SWIs.clear(); - - //*** VB/IB - for (I = 0; I < nVB.size(); I++) _RELEASE(nVB[I]); - for (I = 0; I < xVB.size(); I++) _RELEASE(xVB[I]); - nVB.clear(); - xVB.clear(); - for (I = 0; I < nIB.size(); I++) _RELEASE(nIB[I]); - for (I = 0; I < xIB.size(); I++) _RELEASE(xIB[I]); - nIB.clear(); - xIB.clear(); - nDC.clear(); - xDC.clear(); - - //*** Components - xr_delete(Details); xr_delete(Wallmarks); + pLastSector = nullptr; + pOutdoorSector = nullptr; + vLastCameraPos.set(0.f, 0.f, 0.f); + b_loaded = FALSE; + m_active_level_key = nullptr; + m_active_level_identity = 0; + return package; +} - //*** Shaders - Shaders.clear_and_free(); - - const bool clearResources = psDeviceFlags2.test(rsClearAllResources); - if (psDeviceFlags2.test(rsClearModels) || clearResources) +bool CRender::RestoreLevelStaticPackage(const shared_str& key, u64 identity) +{ + for (u32 i = 0; i < m_level_cache.size(); ++i) { - Models->ClearPool(true); - Visuals.clear_and_free(); - if (clearResources) + LevelStaticPackage* package = m_level_cache[i]; + if (!package->key.equal(key)) + continue; + + m_level_cache.erase(m_level_cache.begin() + i); + if (package->identity != identity) { - dxRenderDeviceRender::Instance().Resources->UnloadAllTexturesOnLevelUnload(); - dxRenderDeviceRender::Instance().ResourcesDestroyNecessaryTextures(); - dxRenderDeviceRender::Instance().Resources->Evict(); + Msg("* [LEVEL CACHE] R4 stale: %s", key.c_str()); + dxRenderDeviceRender::Instance().Resources->ReleaseLevelShaderCache( + package->key.c_str(), package->identity); + xr_delete(package); + return false; } - dxRenderDeviceRender::Instance().Resources->Dump(false); - //static int unload_counter = 0; - //Msg("The Level Unloaded.======================== %d", ++unload_counter); + + Details = package->details; + package->details = nullptr; + m_level_prepared_lights_dynamic.swap(package->light_dynamic); + m_level_prepared_lights_hemi.swap(package->light_hemi); + m_level_prepared_lights = package->prepared_lights; + package->prepared_lights = nullptr; + rmPortals = package->portals_model; + package->portals_model = nullptr; + pOutdoorSector = package->outdoor_sector; + Portals.swap(package->portals); + Sectors.swap(package->sectors); + SWIs.swap(package->swis); + Shaders.swap(package->shaders); + m_level_shader_descriptions.swap(package->shader_descriptions); + m_active_level_shader_indices.swap(package->shader_indices); + m_level_shader_cpp_results.swap(package->cpp_shader_results); + m_level_shader_owner_results.clear(); + m_level_shader_identity = identity; + m_level_shader_owner_required = true; + nDC.swap(package->normal_declarations); + xDC.swap(package->fast_declarations); + nVB.swap(package->normal_vertex_buffers); + xVB.swap(package->fast_vertex_buffers); + nIB.swap(package->normal_index_buffers); + xIB.swap(package->fast_index_buffers); + Visuals.swap(package->visuals); + m_level_fluid_descriptors.swap(package->fluid_descriptors); + m_level_prepared_hom.model = package->hom.model; + m_level_prepared_hom.tris = package->hom.tris; + m_level_prepared_hom.enabled = package->hom.enabled; + package->hom.model = nullptr; + package->hom.tris = nullptr; + package->hom.enabled = FALSE; + xr_delete(package); + + Wallmarks = xr_new(); + pLastSector = nullptr; + vLastCameraPos.set(0.f, 0.f, 0.f); + for (dxRender_Visual* visual : Visuals) + { + visual->vis.hom_frame = 0; + visual->vis.hom_tested = 0; + } + + b_loaded = TRUE; + m_active_level_key = key; + m_active_level_identity = identity; + m_level_cache_attach_pending = true; + return true; } + return false; +} - b_loaded = FALSE; +void CRender::ReleaseLevelCache() +{ + WaitLevelPrepare(); + if (m_prepared_level_geometry) + dxRenderDeviceRender::Instance().Resources->ReleaseLevelShaderCache( + m_prepared_level_geometry->key.c_str(), m_prepared_level_geometry->identity); + xr_delete(m_prepared_level_geometry); + m_prepared_level_path = nullptr; + m_level_prepare_generation = 0; + xr_delete(m_level_prepared_portals_model); + m_level_prepared_portals.clear_and_free(); + m_level_prepared_sectors.clear_and_free(); + m_level_prepared_sectors_ready = false; + for (LevelStaticPackage*& package : m_level_cache) + { + dxRenderDeviceRender::Instance().Resources->ReleaseLevelShaderCache( + package->key.c_str(), package->identity); + xr_delete(package); + } + m_level_cache.clear(); +} + +void CRender::EvictLevelCacheUnderPressure() +{ + MEMORYSTATUSEX memory = {}; + memory.dwLength = sizeof(memory); + while (!m_level_cache.empty() && GlobalMemoryStatusEx(&memory)) + { + const u64 minimum_available = _max(2ull * 1024 * 1024 * 1024, memory.ullTotalPhys * 15 / 100); + if (memory.ullAvailPhys >= minimum_available) + break; + LevelStaticPackage* package = m_level_cache.front(); + Msg("* [LEVEL CACHE] R4 evicted under memory pressure: %s", package->key.c_str()); + m_level_cache.erase(m_level_cache.begin()); + dxRenderDeviceRender::Instance().Resources->ReleaseLevelShaderCache( + package->key.c_str(), package->identity); + xr_delete(package); + } } -void CRender::LoadBuffers(CStreamReader* base_fs, BOOL _alternative) +void CRender::DestroyActiveLevel() +{ + if (!b_loaded) + return; + dxRenderDeviceRender::Instance().Resources->WaitForTextureLoads(); + LevelStaticPackage* package = DetachLevelStaticPackage(); + dxRenderDeviceRender::Instance().Resources->ReleaseLevelShaderCache( + package->key.c_str(), package->identity); + xr_delete(package); +} + +void CRender::LoadBuffers(CStreamReader* base_fs, xr_vector& declarations, + xr_vector& vertex_buffers, xr_vector& index_buffers) { R_ASSERT2(base_fs, "Could not load geometry. File not found."); - dxRenderDeviceRender::Instance().Resources->Evict(); // u32 dwUsage = D3DUSAGE_WRITEONLY; - xr_vector& _DC = _alternative ? xDC : nDC; - xr_vector& _VB = _alternative ? xVB : nVB; - xr_vector& _IB = _alternative ? xIB : nIB; + xr_vector& _DC = declarations; + xr_vector& _VB = vertex_buffers; + xr_vector& _IB = index_buffers; + NativeLoadExecutor& executor = NativeLoadExecutor::Instance(); + NativeLoadExecutor::Batch buffer_batch = executor.BeginBatch(executor.CurrentGeneration()); + xr_task_group fallback_tasks; + auto submit = [&executor, &buffer_batch, &fallback_tasks](auto&& work) + { + if (buffer_batch.Valid()) + executor.Submit(buffer_batch, NativeLoadPriority::Geometry, std::forward(work)); + else + fallback_tasks.run(std::forward(work)); + }; + xr_vector> vertex_data; + xr_vector> index_data; // Vertex buffers { @@ -216,6 +1327,7 @@ void CRender::LoadBuffers(CStreamReader* base_fs, BOOL _alternative) u32 count = fs->r_u32(); _DC.resize(count); _VB.resize(count); + vertex_data.resize(count); u32 bufferSize = (MAXD3DDECLLENGTH + 1) * sizeof(D3DVERTEXELEMENT9); D3DVERTEXELEMENT9* dcl = (D3DVERTEXELEMENT9*)_alloca(bufferSize); for (u32 i = 0; i < count; i++) @@ -243,14 +1355,18 @@ void CRender::LoadBuffers(CStreamReader* base_fs, BOOL _alternative) //_VB[i]->Unlock (); // TODO: DX10: Check fragmentation. // Check if buffer is less then 2048 kb - BYTE* pData = xr_alloc(vCount * vSize); - fs->r(pData, vCount * vSize); - dx10BufferUtils::CreateVertexBuffer(&_VB[i], pData, vCount * vSize); - xr_free(pData); + vertex_data[i].resize(vCount * vSize); + fs->r(vertex_data[i].data(), vertex_data[i].size()); // fs->advance (vCount*vSize); } fs->close(); + + for (u32 i = 0; i < count; ++i) + submit([&, i]() + { + dx10BufferUtils::CreateVertexBuffer(&_VB[i], vertex_data[i].data(), static_cast(vertex_data[i].size())); + }); } // Index buffers @@ -258,6 +1374,7 @@ void CRender::LoadBuffers(CStreamReader* base_fs, BOOL _alternative) CStreamReader* fs = base_fs->open_chunk(fsL_IB); u32 count = fs->r_u32(); _IB.resize(count); + index_data.resize(count); for (u32 i = 0; i < count; i++) { u32 iCount = fs->r_u32(); @@ -273,34 +1390,154 @@ void CRender::LoadBuffers(CStreamReader* base_fs, BOOL _alternative) // TODO: DX10: Check fragmentation. // Check if buffer is less then 2048 kb - BYTE* pData = xr_alloc(iCount * 2); - fs->r(pData, iCount * 2); - dx10BufferUtils::CreateIndexBuffer(&_IB[i], pData, iCount * 2); - xr_free(pData); + index_data[i].resize(iCount * 2); + fs->r(index_data[i].data(), index_data[i].size()); // fs().advance (iCount*2); } fs->close(); + + for (u32 i = 0; i < count; ++i) + submit([&, i]() + { + dx10BufferUtils::CreateIndexBuffer(&_IB[i], index_data[i].data(), static_cast(index_data[i].size())); + }); } + + if (buffer_batch.Valid()) + executor.Wait(buffer_batch); + fallback_tasks.wait(); } -void CRender::LoadVisuals(IReader* fs) +void CRender::LoadVisualLeaf(dxRender_Visual* visual, IReader* chunk, const VisualGeometrySource* geometry) { - IReader* chunk = 0; - u32 index = 0; - dxRender_Visual* V = 0; - ogf_header H; + const bool previous_defer = g_defer_visual_shader_creation; + const VisualGeometrySource* previous_geometry = m_visual_geometry_source; + g_defer_visual_shader_creation = true; + m_visual_geometry_source = geometry; + try + { + visual->Load(nullptr, chunk, 0); + } + catch (...) + { + m_visual_geometry_source = previous_geometry; + g_defer_visual_shader_creation = previous_defer; + throw; + } + m_visual_geometry_source = previous_geometry; + g_defer_visual_shader_creation = previous_defer; +} + +void CRender::PrepareVisuals(IReader* fs, xr_vector& visuals, + const VisualGeometrySource* geometry) +{ + R_ASSERT(visuals.empty()); + xr_vector chunks; + xr_vector headers; + for (u32 index = 0;; ++index) + { + IReader* chunk = fs->open_chunk(index); + if (!chunk) + break; + ogf_header header; + chunk->r_chunk_safe(OGF_HEADER, &header, sizeof(header)); + chunks.push_back(chunk); + headers.push_back(header); + } - while ((chunk = fs->open_chunk(index)) != 0) + visuals.resize(chunks.size()); + for (u32 index = 0; index < chunks.size(); ++index) + visuals[index] = Models->Instance_Create(headers[index].type); + + // Every slot exists before loading begins. Leaf visuals own disjoint state + // and only read immutable shader/geometry registries, so they can load in + // parallel. Hierarchies link those slots and FLOD reads owner RCache state; + // keep both in the original index order after the leaf barrier. + auto load_leaf = [&](u32 index) + { + if (!IsWorkerSafeLevelVisual(headers[index].type)) + return; + LoadVisualLeaf(visuals[index], chunks[index], geometry); + chunks[index]->close(); + chunks[index] = nullptr; + }; + NativeLoadExecutor& executor = NativeLoadExecutor::Instance(); + NativeLoadExecutor::Batch visual_batch = executor.BeginBatch(executor.CurrentGeneration()); + try { - chunk->r_chunk_safe(OGF_HEADER, &H, sizeof(H)); - V = Models->Instance_Create(H.type); - V->Load(0, chunk, 0); - Visuals.push_back(V); + if (visual_batch.Valid()) + { + for (u32 index = 0; index < chunks.size(); ++index) + executor.Submit(visual_batch, NativeLoadPriority::Geometry, + [&load_leaf, index]() { load_leaf(index); }); + executor.Wait(visual_batch); + } + else + xr_parallel_for(0u, static_cast(chunks.size()), load_leaf); + } + catch (...) + { + for (IReader*& chunk : chunks) + if (chunk) + chunk->close(); + throw; + } + for (IReader*& chunk : chunks) + if (chunk) + chunk->close(); +} - chunk->close(); - index++; +void CRender::LinkPreparedVisuals(IReader* fs, xr_vector& visuals) +{ + const xr_vector* previous_visuals = m_visual_table_source; + m_visual_table_source = &visuals; + u32 index = 0; + try + { + for (;; ++index) + { + IReader* chunk = fs->open_chunk(index); + if (!chunk) + break; + try + { + R_ASSERT(index < visuals.size()); + ogf_header header; + R_ASSERT(chunk->r_chunk_safe(OGF_HEADER, &header, sizeof(header))); + R_ASSERT(visuals[index]->getType() == header.type); + if (header.type == MT_HIERRARHY || header.type == MT_LOD) + LoadVisualLeaf(visuals[index], chunk, nullptr); + else if (!IsWorkerSafeLevelVisual(header.type)) + visuals[index]->Load(nullptr, chunk, 0); + } + catch (...) + { + chunk->close(); + throw; + } + chunk->close(); + } + R_ASSERT(index == visuals.size()); } + catch (...) + { + m_visual_table_source = previous_visuals; + throw; + } + m_visual_table_source = previous_visuals; +} + +void CRender::DiscardPreparedVisuals() +{ + for (dxRender_Visual*& visual : m_level_prepared_visuals) + { + visual->Release(); + xr_delete(visual); + } + m_level_prepared_visuals.clear_and_free(); + m_level_prepared_visual_data.clear_and_free(); + m_level_prepared_visuals_ready = false; } void CRender::LoadLights(IReader* fs) @@ -310,13 +1547,6 @@ void CRender::LoadLights(IReader* fs) Lights.LoadHemi(); } -struct b_portal -{ - u16 sector_front; - u16 sector_back; - svector vertices; -}; - void CRender::LoadSectors(IReader* fs) { // allocate memory for portals @@ -403,7 +1633,55 @@ void CRender::LoadSectors(IReader* fs) pOutdoorSector = largest_sector; } -void CRender::LoadSWIs(CStreamReader* base_fs) +void CRender::LoadPreparedSectors() +{ + R_ASSERT(m_level_prepared_sectors_ready); + R_ASSERT(m_level_prepared_portals.size() % sizeof(b_portal) == 0); + const u32 portal_count = static_cast(m_level_prepared_portals.size() / sizeof(b_portal)); + Portals.resize(portal_count); + for (u32 index = 0; index < portal_count; ++index) + Portals[index] = xr_new(); + + for (xr_vector& bytes : m_level_prepared_sectors) + { + IReader reader(bytes.data(), static_cast(bytes.size())); + CSector* sector = xr_new(); + sector->load(reader); + Sectors.push_back(sector); + } + + for (u32 index = 0; index < portal_count; ++index) + { + b_portal portal; + CopyMemory(&portal, m_level_prepared_portals.data() + index * sizeof(b_portal), sizeof(portal)); + CPortal* target = static_cast(Portals[index]); + target->Setup(portal.vertices.begin(), portal.vertices.size(), + static_cast(getSector(portal.sector_front)), + static_cast(getSector(portal.sector_back))); + } + rmPortals = m_level_prepared_portals_model; + m_level_prepared_portals_model = nullptr; + m_level_prepared_portals.clear_and_free(); + m_level_prepared_sectors.clear_and_free(); + m_level_prepared_sectors_ready = false; + + pLastSector = nullptr; + CSector* largest_sector = nullptr; + float largest_sector_volume = 0.f; + for (IRender_Sector* item : Sectors) + { + CSector* sector = static_cast(item); + const float volume = sector->root()->vis.box.getvolume(); + if (volume > largest_sector_volume) + { + largest_sector_volume = volume; + largest_sector = sector; + } + } + pOutdoorSector = largest_sector; +} + +void CRender::LoadSWIs(CStreamReader* base_fs, xr_vector& swis) { // allocate memory for portals if (base_fs->find_chunk(fsL_SWIS)) @@ -411,18 +1689,18 @@ void CRender::LoadSWIs(CStreamReader* base_fs) CStreamReader* fs = base_fs->open_chunk(fsL_SWIS); u32 item_count = fs->r_u32(); - xr_vector::iterator it = SWIs.begin(); - xr_vector::iterator it_e = SWIs.end(); + xr_vector::iterator it = swis.begin(); + xr_vector::iterator it_e = swis.end(); for (; it != it_e; ++it) xr_free((*it).sw); - SWIs.clear_not_free(); + swis.clear_not_free(); - SWIs.resize(item_count); + swis.resize(item_count); for (u32 c = 0; c < item_count; c++) { - FSlideWindowItem& swi = SWIs[c]; + FSlideWindowItem& swi = swis[c]; swi.reserved[0] = fs->r_u32(); swi.reserved[1] = fs->r_u32(); swi.reserved[2] = fs->r_u32(); @@ -436,39 +1714,48 @@ void CRender::LoadSWIs(CStreamReader* base_fs) } } -void CRender::Load3DFluid() +void CRender::Remove3DFluid() { - if (!RImplementation.o.volumetricfog) - return; - - string_path fn_game; - if (FS.exist(fn_game, "$level$", "level.fog_vol")) + for (dxRender_Visual* visual : Visuals) { - IReader* F = FS.r_open(fn_game); - u16 version = F->r_u16(); - - if (version == 3) + if (visual->getType() != MT_HIERRARHY) + continue; + FHierrarhyVisual* hierarchy = static_cast(visual); + for (auto child = hierarchy->children.begin(); child != hierarchy->children.end();) { - u32 cnt = F->r_u32(); - for (u32 i = 0; i < cnt; ++i) + if ((*child)->getType() != MT_3DFLUIDVOLUME) { - dx103DFluidVolume* pVolume = xr_new(); - pVolume->Load("", F, 0); + ++child; + continue; + } + dxRender_Visual* fluid = static_cast(*child); + fluid->Release(); + xr_delete(fluid); + child = hierarchy->children.erase(child); + } + } +} + +void CRender::Commit3DFluid() +{ + if (!RImplementation.o.volumetricfog) + return; - // Attach to sector's static geometry - CSector* pSector = (CSector*)detectSector(pVolume->getVisData().sphere.P); - // 3DFluid volume must be in render sector - VERIFY(pSector); + for (const dx103DFluidData::PreparedData& prepared : m_level_fluid_descriptors) + { + dx103DFluidVolume* pVolume = xr_new(); + pVolume->LoadPrepared(prepared); - dxRender_Visual* pRoot = pSector->root(); - // Sector must have root - VERIFY(pRoot); - VERIFY(pRoot->getType() == MT_HIERRARHY); + // Attach to sector's static geometry + CSector* pSector = (CSector*)detectSector(pVolume->getVisData().sphere.P); + // 3DFluid volume must be in render sector + VERIFY(pSector); - ((FHierrarhyVisual*)pRoot)->children.push_back(pVolume); - } - } + dxRender_Visual* pRoot = pSector->root(); + // Sector must have root + VERIFY(pRoot); + VERIFY(pRoot->getType() == MT_HIERRARHY); - FS.r_close(F); + ((FHierrarhyVisual*)pRoot)->children.push_back(pVolume); } } diff --git a/src/Layers/xrRenderPC_R4/r4_rendertarget.cpp b/src/Layers/xrRenderPC_R4/r4_rendertarget.cpp index 03db2f91b8..9a23a0441b 100644 --- a/src/Layers/xrRenderPC_R4/r4_rendertarget.cpp +++ b/src/Layers/xrRenderPC_R4/r4_rendertarget.cpp @@ -349,6 +349,8 @@ void generate_jitter(DWORD* dest, u32 elem_count) CRenderTarget::CRenderTarget() { + CTimer startupTimer; + startupTimer.Start(); u32 SampleCount = 1; if (ps_r_ssao_mode != 2/*hdao*/) @@ -652,50 +654,61 @@ CRenderTarget::CRenderTarget() rt_Generic_2.create(r2_RT_generic2, w, h, D3DFMT_A16B16G16R16F, SampleCount); } - s_hdr10_bloom_downsample.create(b_hdr10_bloom_downsample, "hdr10_bloom_downsample"); - s_hdr10_bloom_blur.create(b_hdr10_bloom_blur, "hdr10_bloom_blur"); - s_hdr10_bloom_upsample.create(b_hdr10_bloom_upsample, "hdr10_bloom_upsample"); - - s_hdr10_lens_flare_downsample.create(b_hdr10_lens_flare_downsample, "hdr10_lens_flare_downsample"); - s_hdr10_lens_flare_fgen.create(b_hdr10_lens_flare_fgen, "hdr10_lens_flare_fgen"); - s_hdr10_lens_flare_blur.create(b_hdr10_lens_flare_blur, "hdr10_lens_flare_blur"); - s_hdr10_lens_flare_upsample.create(b_hdr10_lens_flare_upsample, "hdr10_lens_flare_upsample"); - - s_sunshafts.create(b_sunshafts, "r2\\sunshafts"); - s_blur.create(b_blur, "r2\\blur"); - s_pp_bloom.create(b_pp_bloom, "r2\\pp_bloom"); - s_dof.create(b_dof, "r2\\dof"); - s_gasmask_drops.create(b_gasmask_drops, "r2\\gasmask_drops"); - s_gasmask_dudv.create(b_gasmask_dudv, "r2\\gasmask_dudv"); - s_nightvision.create(b_nightvision, "r2\\nightvision"); - - s_fakescope.create(b_fakescope, "r2\\fakescope"); //crookr - - s_heatvision.create(b_heatvision, "r2\\heatvision"); //--DSR-- HeatVision - s_lut.create(b_lut, "r2\\lut"); - // OCCLUSION - s_occq.create(b_occq, "r2\\occq"); - - // Screen Space Shaders Stuff - s_ssfx_fog_scattering.create(b_ssfx_fog_scattering, "ssfx_fog_scattering"); // SSS Fog Scattering - s_ssfx_motion_blur.create(b_ssfx_motion_blur, "ssfx_motion_blur"); // SSS Motion Blur - s_ssfx_taa.create(b_ssfx_taa, "ssfx_taa"); // SSS TAA - s_ssfx_rain.create(b_ssfx_rain, "ssfx_rain"); // SSS Rain - s_ssfx_bloom.create(b_ssfx_bloom, "ssfx_bloom"); // SSS Bloom - s_ssfx_bloom_lens.create(b_ssfx_bloom_lens, "ssfx_bloom_flares"); // SSS Bloom Lens flare - s_ssfx_bloom_downsample.create(b_ssfx_bloom_downsample, "ssfx_bloom_downsample"); // SSS Bloom - s_ssfx_bloom_upsample.create(b_ssfx_bloom_upsample, "ssfx_bloom_upsample"); // SSS Bloom - s_ssfx_sss_ext.create(b_ssfx_sss_ext, "ssfx_sss_ext"); // SSS Extended - s_ssfx_sss.create(b_ssfx_sss, "ssfx_sss"); // SSS - s_ssfx_ssr.create(b_ssfx_ssr, "ssfx_ssr"); // SSR - s_ssfx_volumetric_blur.create(b_ssfx_volumetric_blur, "ssfx_volumetric_blur"); // Volumetric Blur - - s_ssfx_water_ssr.create("ssfx_water_ssr"); // Water SSR - s_ssfx_water.create("ssfx_water"); // Water - s_ssfx_water_blur.create(b_ssfx_water_blur, "ssfx_water_blur"); // Water - - s_ssfx_ao.create(b_ssfx_ao, "ssfx_ao"); // SSR - + const u32 initialTargetsMs = startupTimer.GetElapsed_ms(); + xr_task_group startup_shader_tasks; + startup_shader_tasks.run([this]() + { s_hdr10_bloom_downsample.create_parallel(b_hdr10_bloom_downsample, "hdr10_bloom_downsample"); }); + startup_shader_tasks.run([this]() + { s_hdr10_bloom_blur.create_parallel(b_hdr10_bloom_blur, "hdr10_bloom_blur"); }); + startup_shader_tasks.run([this]() + { s_hdr10_bloom_upsample.create_parallel(b_hdr10_bloom_upsample, "hdr10_bloom_upsample"); }); + startup_shader_tasks.run([this]() + { s_hdr10_lens_flare_downsample.create_parallel(b_hdr10_lens_flare_downsample, "hdr10_lens_flare_downsample"); }); + startup_shader_tasks.run([this]() + { s_hdr10_lens_flare_fgen.create_parallel(b_hdr10_lens_flare_fgen, "hdr10_lens_flare_fgen"); }); + startup_shader_tasks.run([this]() + { s_hdr10_lens_flare_blur.create_parallel(b_hdr10_lens_flare_blur, "hdr10_lens_flare_blur"); }); + startup_shader_tasks.run([this]() + { s_hdr10_lens_flare_upsample.create_parallel(b_hdr10_lens_flare_upsample, "hdr10_lens_flare_upsample"); }); + + startup_shader_tasks.run([this]() { s_sunshafts.create_parallel(b_sunshafts, "r2\\sunshafts"); }); + startup_shader_tasks.run([this]() { s_blur.create_parallel(b_blur, "r2\\blur"); }); + startup_shader_tasks.run([this]() { s_pp_bloom.create_parallel(b_pp_bloom, "r2\\pp_bloom"); }); + startup_shader_tasks.run([this]() { s_dof.create_parallel(b_dof, "r2\\dof"); }); + startup_shader_tasks.run([this]() + { s_gasmask_drops.create_parallel(b_gasmask_drops, "r2\\gasmask_drops"); }); + startup_shader_tasks.run([this]() + { s_gasmask_dudv.create_parallel(b_gasmask_dudv, "r2\\gasmask_dudv"); }); + startup_shader_tasks.run([this]() { s_nightvision.create_parallel(b_nightvision, "r2\\nightvision"); }); + startup_shader_tasks.run([this]() { s_fakescope.create_parallel(b_fakescope, "r2\\fakescope"); }); + startup_shader_tasks.run([this]() { s_heatvision.create_parallel(b_heatvision, "r2\\heatvision"); }); + startup_shader_tasks.run([this]() { s_lut.create_parallel(b_lut, "r2\\lut"); }); + startup_shader_tasks.run([this]() { s_occq.create_parallel(b_occq, "r2\\occq"); }); + + startup_shader_tasks.run([this]() + { s_ssfx_fog_scattering.create_parallel(b_ssfx_fog_scattering, "ssfx_fog_scattering"); }); + startup_shader_tasks.run([this]() + { s_ssfx_motion_blur.create_parallel(b_ssfx_motion_blur, "ssfx_motion_blur"); }); + startup_shader_tasks.run([this]() { s_ssfx_taa.create_parallel(b_ssfx_taa, "ssfx_taa"); }); + startup_shader_tasks.run([this]() { s_ssfx_rain.create_parallel(b_ssfx_rain, "ssfx_rain"); }); + startup_shader_tasks.run([this]() { s_ssfx_bloom.create_parallel(b_ssfx_bloom, "ssfx_bloom"); }); + startup_shader_tasks.run([this]() + { s_ssfx_bloom_lens.create_parallel(b_ssfx_bloom_lens, "ssfx_bloom_flares"); }); + startup_shader_tasks.run([this]() + { s_ssfx_bloom_downsample.create_parallel(b_ssfx_bloom_downsample, "ssfx_bloom_downsample"); }); + startup_shader_tasks.run([this]() + { s_ssfx_bloom_upsample.create_parallel(b_ssfx_bloom_upsample, "ssfx_bloom_upsample"); }); + startup_shader_tasks.run([this]() { s_ssfx_sss_ext.create_parallel(b_ssfx_sss_ext, "ssfx_sss_ext"); }); + startup_shader_tasks.run([this]() { s_ssfx_sss.create_parallel(b_ssfx_sss, "ssfx_sss"); }); + startup_shader_tasks.run([this]() { s_ssfx_ssr.create_parallel(b_ssfx_ssr, "ssfx_ssr"); }); + startup_shader_tasks.run([this]() + { s_ssfx_volumetric_blur.create_parallel(b_ssfx_volumetric_blur, "ssfx_volumetric_blur"); }); + + s_ssfx_water_ssr.create("ssfx_water_ssr"); + s_ssfx_water.create("ssfx_water"); + startup_shader_tasks.run([this]() + { s_ssfx_water_blur.create_parallel(b_ssfx_water_blur, "ssfx_water_blur"); }); + startup_shader_tasks.run([this]() { s_ssfx_ao.create_parallel(b_ssfx_ao, "ssfx_ao"); }); // SSS 23: Deprecated /*string32 cskin_buffer; for (int skin_num = 0; skin_num < 5; skin_num++) @@ -718,57 +731,43 @@ CRenderTarget::CRenderTarget() if (RImplementation.o.dx10_minmax_sm) { rt_smap_depth_minmax.create(r2_RT_smap_depth_minmax, size / 4, size / 4, D3DFMT_R32F); - CBlender_createminmax TempBlender; - s_create_minmax_sm.create(&TempBlender, "null"); + startup_shader_tasks.run([this]() + { + CBlender_createminmax blender; + s_create_minmax_sm.create_parallel(&blender, "null"); + }); } //rt_smap_surf.create (r2_RT_smap_surf, size,size,nullrt ); //rt_smap_ZB = NULL; - s_accum_mask.create(b_accum_mask, "r3\\accum_mask"); - s_accum_direct.create(b_accum_direct, "r3\\accum_direct"); - - + startup_shader_tasks.run([this]() { s_accum_mask.create_parallel(b_accum_mask, "r3\\accum_mask"); }); + startup_shader_tasks.run([this]() { s_accum_direct.create_parallel(b_accum_direct, "r3\\accum_direct"); }); if (RImplementation.o.dx10_msaa) { - int bound = RImplementation.o.dx10_msaa_samples; - - if (RImplementation.o.dx10_msaa_opt) - bound = 1; - + const int bound = RImplementation.o.dx10_msaa_opt ? 1 : RImplementation.o.dx10_msaa_samples; for (int i = 0; i < bound; ++i) { - s_accum_direct_msaa[i].create(b_accum_direct_msaa[i], "r3\\accum_direct"); - s_accum_mask_msaa[i].create(b_accum_mask_msaa[i], "r3\\accum_direct"); + startup_shader_tasks.run([this, i]() + { s_accum_direct_msaa[i].create_parallel(b_accum_direct_msaa[i], "r3\\accum_direct"); }); + startup_shader_tasks.run([this, i]() + { s_accum_mask_msaa[i].create_parallel(b_accum_mask_msaa[i], "r3\\accum_direct"); }); } } if (RImplementation.o.advancedpp) { s_accum_direct_volumetric.create("accum_volumetric_sun_nomsaa"); - if (RImplementation.o.dx10_minmax_sm) s_accum_direct_volumetric_minmax.create("accum_volumetric_sun_nomsaa_minmax"); - if (RImplementation.o.dx10_msaa) { - static LPCSTR snames[] = { - "accum_volumetric_sun_msaa0", - "accum_volumetric_sun_msaa1", - "accum_volumetric_sun_msaa2", - "accum_volumetric_sun_msaa3", - "accum_volumetric_sun_msaa4", - "accum_volumetric_sun_msaa5", - "accum_volumetric_sun_msaa6", - "accum_volumetric_sun_msaa7" - }; - int bound = RImplementation.o.dx10_msaa_samples; - - if (RImplementation.o.dx10_msaa_opt) - bound = 1; - + const int bound = RImplementation.o.dx10_msaa_opt ? 1 : RImplementation.o.dx10_msaa_samples; for (int i = 0; i < bound; ++i) { - //s_accum_direct_volumetric_msaa[i].create (b_accum_direct_volumetric_sun_msaa[i], "r3\\accum_direct"); - s_accum_direct_volumetric_msaa[i].create(snames[i]); + static LPCSTR names[] = {"accum_volumetric_sun_msaa0", "accum_volumetric_sun_msaa1", + "accum_volumetric_sun_msaa2", "accum_volumetric_sun_msaa3", + "accum_volumetric_sun_msaa4", "accum_volumetric_sun_msaa5", + "accum_volumetric_sun_msaa6", "accum_volumetric_sun_msaa7"}; + s_accum_direct_volumetric_msaa[i].create(names[i]); } } } @@ -790,43 +789,43 @@ CRenderTarget::CRenderTarget() // RAIN // TODO: DX10: Create resources only when DX10 rain is enabled. // Or make DX10 rain switch dynamic? + startup_shader_tasks.run([this]() { - CBlender_rain TempBlender; - s_rain.create(&TempBlender, "null"); - - if (RImplementation.o.dx10_msaa) + CBlender_rain blender; + s_rain.create_parallel(&blender, "null"); + }); + if (RImplementation.o.dx10_msaa) + { + const int bound = RImplementation.o.dx10_msaa_opt ? 1 : RImplementation.o.dx10_msaa_samples; + for (int i = 0; i < bound; ++i) { - static LPCSTR SampleDefs[] = {"0", "1", "2", "3", "4", "5", "6", "7"}; - CBlender_rain_msaa TempBlender[8]; - - int bound = RImplementation.o.dx10_msaa_samples; - - if (RImplementation.o.dx10_msaa_opt) - bound = 1; - - for (int i = 0; i < bound; ++i) + startup_shader_tasks.run([this, i]() { - TempBlender[i].SetDefine("ISAMPLE", SampleDefs[i]); - s_rain_msaa[i].create(&TempBlender[i], "null"); - s_accum_spot_msaa[i].create(b_accum_spot_msaa[i], "r2\\accum_spot_s", "lights\\lights_spot01"); - s_accum_point_msaa[i].create(b_accum_point_msaa[i], "r2\\accum_point_s"); - //s_accum_volume_msaa[i].create(b_accum_direct_volumetric_msaa[i], "lights\\lights_spot01"); - s_accum_volume_msaa[i].create(b_accum_volumetric_msaa[i], "lights\\lights_spot01"); - s_combine_msaa[i].create(b_combine_msaa[i], "r2\\combine"); - } + static LPCSTR sampleDefinitions[] = {"0", "1", "2", "3", "4", "5", "6", "7"}; + CBlender_rain_msaa blender; + blender.SetDefine("ISAMPLE", sampleDefinitions[i]); + s_rain_msaa[i].create_parallel(&blender, "null"); + s_accum_spot_msaa[i].create_parallel( + b_accum_spot_msaa[i], "r2\\accum_spot_s", "lights\\lights_spot01"); + s_accum_point_msaa[i].create_parallel(b_accum_point_msaa[i], "r2\\accum_point_s"); + s_accum_volume_msaa[i].create_parallel(b_accum_volumetric_msaa[i], "lights\\lights_spot01"); + s_combine_msaa[i].create_parallel(b_combine_msaa[i], "r2\\combine"); + }); } } if (RImplementation.o.dx10_msaa) { - CBlender_msaa TempBlender; - - s_mark_msaa_edges.create(&TempBlender, "null"); + startup_shader_tasks.run([this]() + { + CBlender_msaa blender; + s_mark_msaa_edges.create_parallel(&blender, "null"); + }); } // POINT { - s_accum_point.create(b_accum_point, "r2\\accum_point_s"); + startup_shader_tasks.run([this]() { s_accum_point.create_parallel(b_accum_point, "r2\\accum_point_s"); }); accum_point_geom_create(); g_accum_point.create(D3DFVF_XYZ, g_accum_point_vb, g_accum_point_ib); accum_omnip_geom_create(); @@ -835,7 +834,10 @@ CRenderTarget::CRenderTarget() // SPOT { - s_accum_spot.create(b_accum_spot, "r2\\accum_spot_s", "lights\\lights_spot01"); + startup_shader_tasks.run([this]() + { + s_accum_spot.create_parallel(b_accum_spot, "r2\\accum_spot_s", "lights\\lights_spot01"); + }); accum_spot_geom_create(); g_accum_spot.create(D3DFVF_XYZ, g_accum_spot_vb, g_accum_spot_ib); } @@ -849,18 +851,14 @@ CRenderTarget::CRenderTarget() // REFLECTED { - s_accum_reflected.create(b_accum_reflected, "r2\\accum_refl"); + startup_shader_tasks.run([this]() + { s_accum_reflected.create_parallel(b_accum_reflected, "r2\\accum_refl"); }); if (RImplementation.o.dx10_msaa) { - int bound = RImplementation.o.dx10_msaa_samples; - - if (RImplementation.o.dx10_msaa_opt) - bound = 1; - + const int bound = RImplementation.o.dx10_msaa_opt ? 1 : RImplementation.o.dx10_msaa_samples; for (int i = 0; i < bound; ++i) - { - s_accum_reflected_msaa[i].create(b_accum_reflected_msaa[i], "null"); - } + startup_shader_tasks.run([this, i]() + { s_accum_reflected_msaa[i].create_parallel(b_accum_reflected_msaa[i], "null"); }); } } @@ -879,11 +877,12 @@ CRenderTarget::CRenderTarget() g_bloom_filter.create(fvf_filter, RCache.Vertex.Buffer(), RCache.QuadIB); s_bloom_dbg_1.create("effects\\screen_set", r2_RT_bloom1); s_bloom_dbg_2.create("effects\\screen_set", r2_RT_bloom2); - s_bloom.create(b_bloom, "r2\\bloom"); + startup_shader_tasks.run([this]() { s_bloom.create_parallel(b_bloom, "r2\\bloom"); }); if (RImplementation.o.dx10_msaa) { - s_bloom_msaa.create(b_bloom_msaa, "r2\\bloom"); - s_postprocess_msaa.create(b_postprocess_msaa, "r2\\post"); + startup_shader_tasks.run([this]() { s_bloom_msaa.create_parallel(b_bloom_msaa, "r2\\bloom"); }); + startup_shader_tasks.run([this]() + { s_postprocess_msaa.create_parallel(b_postprocess_msaa, "r2\\post"); }); } f_bloom_factor = 0.5f; } @@ -896,14 +895,14 @@ CRenderTarget::CRenderTarget() rt_smaa_edgetex.create(r2_RT_smaa_edgetex, w, h, D3DFMT_A8R8G8B8); rt_smaa_blendtex.create(r2_RT_smaa_blendtex, w, h, D3DFMT_A8R8G8B8); - s_smaa.create(b_smaa, "r3\\smaa"); + startup_shader_tasks.run([this]() { s_smaa.create_parallel(b_smaa, "r3\\smaa"); }); } // TONEMAP { rt_LUM_64.create(r2_RT_luminance_t64, 64, 64, D3DFMT_A16B16G16R16F); rt_LUM_8.create(r2_RT_luminance_t8, 8, 8, D3DFMT_A16B16G16R16F); - s_luminance.create(b_luminance, "r2\\luminance"); + startup_shader_tasks.run([this]() { s_luminance.create_parallel(b_luminance, "r2\\luminance"); }); f_luminance_adapt = 0.5f; t_LUM_src.create(r2_RT_luminance_src); @@ -940,7 +939,7 @@ CRenderTarget::CRenderTarget() D3DFORMAT fmt = HW.Caps.id_vendor == 0x10DE ? D3DFMT_R32F : D3DFMT_R16F; rt_half_depth.create(r2_RT_half_depth, w, h, fmt); - s_ssao.create(b_ssao, "r2\\ssao"); + startup_shader_tasks.run([this]() { s_ssao.create_parallel(b_ssao, "r2\\ssao"); }); } //if (RImplementation.o.ssao_blur_on) @@ -965,11 +964,12 @@ CRenderTarget::CRenderTarget() { u32 w = Device.dwWidth, h = Device.dwHeight; rt_ssao_temp.create(r2_RT_ssao_temp, w, h, D3DFMT_R16F, 1, true); - s_hdao_cs.create(b_hdao_cs, "r2\\ssao"); - if (RImplementation.o.dx10_msaa) + startup_shader_tasks.run([this]() { - s_hdao_cs_msaa.create(b_hdao_msaa_cs, "r2\\ssao"); - } + s_hdao_cs.create_parallel(b_hdao_cs, "r2\\ssao"); + if (RImplementation.o.dx10_msaa) + s_hdao_cs_msaa.create_parallel(b_hdao_msaa_cs, "r2\\ssao"); + }); } // COMBINE @@ -979,7 +979,7 @@ CRenderTarget::CRenderTarget() {0, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, // pos+uv D3DDECL_END() }; - s_combine.create(b_combine, "r2\\combine"); + startup_shader_tasks.run([this]() { s_combine.create_parallel(b_combine, "r2\\combine"); }); s_combine_volumetric.create("combine_volumetric"); s_combine_dbg_0.create("effects\\screen_set", r2_RT_smap_surf); s_combine_dbg_1.create("effects\\screen_set", r2_RT_luminance_t8); @@ -1294,10 +1294,15 @@ CRenderTarget::CRenderTarget() // Menu s_menu.create("distort"); g_menu.create(FVF::F_TL, RCache.Vertex.Buffer(), RCache.QuadIB); + const u32 overlappedWorkMs = startupTimer.GetElapsed_ms() - initialTargetsMs; + startup_shader_tasks.wait(); + const u32 shaderWaitMs = startupTimer.GetElapsed_ms() - initialTargetsMs - overlappedWorkMs; // dwWidth = Device.dwWidth; dwHeight = Device.dwHeight; + Msg("* [STARTUP/RENDER TARGET] initial=%u overlap=%u shader-wait=%u total=%u ms", + initialTargetsMs, overlappedWorkMs, shaderWaitMs, startupTimer.GetElapsed_ms()); } CRenderTarget::~CRenderTarget() diff --git a/src/xrCDB/xrCDB.cpp b/src/xrCDB/xrCDB.cpp index 7164fc2008..7d4c8e8d05 100644 --- a/src/xrCDB/xrCDB.cpp +++ b/src/xrCDB/xrCDB.cpp @@ -63,6 +63,21 @@ MODEL::~MODEL() verts_count = 0; } +void MODEL::swap(MODEL& other) +{ + syncronize(); + other.syncronize(); + std::swap(tree, other.tree); + const bool this_status = status.load(std::memory_order_acquire); + const bool other_status = other.status.load(std::memory_order_acquire); + status.store(other_status, std::memory_order_release); + other.status.store(this_status, std::memory_order_release); + std::swap(tris, other.tris); + std::swap(tris_count, other.tris_count); + std::swap(verts, other.verts); + std::swap(verts_count, other.verts_count); +} + void MODEL::build(Fvector* V, int Vcnt, TRI* T, int Tcnt, build_callback* bc, void* bcp) { R_ASSERT(S_INIT == status); diff --git a/src/xrCDB/xrCDB.h b/src/xrCDB/xrCDB.h index 90999aa7ee..b5b58edc34 100644 --- a/src/xrCDB/xrCDB.h +++ b/src/xrCDB/xrCDB.h @@ -102,6 +102,7 @@ namespace CDB static void build_thread(void*); void build_internal(Fvector* V, int Vcnt, TRI* T, int Tcnt, build_callback* bc = NULL, void* bcp = NULL); void build(Fvector* V, int Vcnt, TRI* T, int Tcnt, build_callback* bc = NULL, void* bcp = NULL); + void swap(MODEL& other); u32 memory(); }; diff --git a/src/xrCDB/xr_area.cpp b/src/xrCDB/xr_area.cpp index 6b84d8d8bf..ed7a2d2db4 100644 --- a/src/xrCDB/xr_area.cpp +++ b/src/xrCDB/xr_area.cpp @@ -7,6 +7,64 @@ using namespace collide; +namespace +{ +struct StaticCformPackage +{ + shared_str key; + u64 identity = 0; + Fbox bounds; + CDB::MODEL model; + bool materials_remapped = false; +}; + +xr_vector& StaticCformCache() +{ + // Keep the cache alive until process termination. xrCore's allocators can be + // destroyed before function-local static containers during engine shutdown. + static xr_vector* cache = xr_new>(); + return *cache; +} + +xrCriticalSection& StaticCformCacheLock() +{ + static xrCriticalSection* lock = xr_new(); + return *lock; +} + +u64 CformIdentity(const CLocatorAPI::file& file) +{ + u64 identity = 1469598103934665603ull; + auto mix = [&identity](u32 value) + { + identity ^= value; + identity *= 1099511628211ull; + }; + mix(file.crc); + mix(file.size_real); + mix(file.size_compressed); + mix(file.modif); + return identity; +} + +void EvictStaticCformCacheUnderPressure() +{ + auto& cache = StaticCformCache(); + MEMORYSTATUSEX memory = {}; + memory.dwLength = sizeof(memory); + while (!cache.empty() && GlobalMemoryStatusEx(&memory)) + { + const u64 minimum_available = _max(2ull * 1024 * 1024 * 1024, memory.ullTotalPhys * 15 / 100); + if (memory.ullAvailPhys >= minimum_available) + break; + StaticCformPackage* package = cache.front(); + Msg("* [LEVEL CACHE] CFORM evicted under memory pressure: %s", package->key.c_str()); + cache.erase(cache.begin()); + xr_delete(package); + } +} +} + //---------------------------------------------------------------------- // Class : CObjectSpace // Purpose : stores space slots @@ -28,6 +86,33 @@ CObjectSpace::CObjectSpace() //---------------------------------------------------------------------- CObjectSpace::~CObjectSpace() { + Static.syncronize(); + if (m_static_cache_key.size()) + { + StaticCformPackage* package = xr_new(); + package->key = m_static_cache_key; + package->identity = m_static_cache_identity; + package->bounds = m_BoundingVolume; + package->materials_remapped = m_static_materials_remapped; + package->model.swap(Static); + + xrCriticalSectionGuard guard(StaticCformCacheLock()); + auto& cache = StaticCformCache(); + for (auto it = cache.begin(); it != cache.end();) + { + if ((*it)->key == package->key) + { + StaticCformPackage* stale = *it; + it = cache.erase(it); + xr_delete(stale); + } + else + ++it; + } + cache.push_back(package); + EvictStaticCformCacheUnderPressure(); + } + //moved to ~IGameLevel // Sound->set_geometry_occ (NULL); // Sound->set_handler (NULL); @@ -87,23 +172,114 @@ void CObjectSpace::Load(CDB::build_callback build_callback) Load("$level$", "level.cform", build_callback); } +void CObjectSpace::PrepareStatic(LPCSTR level_path) +{ + if (!level_path || !level_path[0]) + return; + xr_string full_path = level_path; + if (full_path.back() != '\\' && full_path.back() != '/') + full_path += '\\'; + full_path += "level.cform"; + const CLocatorAPI::file* file = FS.exist(full_path.c_str()); + if (!file) + return; + const shared_str key = file->name; + const u64 identity = CformIdentity(*file); + + // This lock is also the future for an in-flight prepare: Load blocks here + // only if the early ALife-overlapped build has not finished yet. + xrCriticalSectionGuard guard(StaticCformCacheLock()); + auto& cache = StaticCformCache(); + for (auto it = cache.begin(); it != cache.end(); ++it) + { + StaticCformPackage* package = *it; + if (package->key != key) + continue; + if (package->identity == identity) + return; + cache.erase(it); + xr_delete(package); + break; + } + + IReader* reader = FS.r_open(full_path.c_str()); + R_ASSERT(reader); + hdrCFORM header; + reader->r(&header, sizeof(header)); + R_ASSERT(CFORM_CURRENT_VERSION == header.version); + Fvector* vertices = static_cast(reader->pointer()); + CDB::TRI* triangles = reinterpret_cast(vertices + header.vertcount); + StaticCformPackage* package = xr_new(); + package->key = key; + package->identity = identity; + package->bounds = header.aabb; + package->model.build(vertices, header.vertcount, triangles, header.facecount); + FS.r_close(reader); + cache.push_back(package); + EvictStaticCformCacheUnderPressure(); + Msg("* [LEVEL PREPARE] CFORM ready: %s", key.c_str()); +} + void CObjectSpace::Load(LPCSTR path, LPCSTR fname, CDB::build_callback build_callback) { #ifdef USE_ARENA_ALLOCATOR Msg( "CObjectSpace::Load, g_collision_allocator.get_allocated_size() - %d", int(g_collision_allocator.get_allocated_size()/1024.0/1024) ); #endif // #ifdef USE_ARENA_ALLOCATOR - IReader* F = FS.r_open(path, fname); + string_path resolved; + const CLocatorAPI::file* file = FS.exist(resolved, path, fname); + R_ASSERT(file); + const shared_str key = file->name; + const u64 identity = CformIdentity(*file); + { + xrCriticalSectionGuard guard(StaticCformCacheLock()); + auto& cache = StaticCformCache(); + for (auto it = cache.begin(); it != cache.end(); ++it) + { + StaticCformPackage* package = *it; + if (package->key != key) + continue; + if (package->identity != identity) + { + cache.erase(it); + xr_delete(package); + break; + } + + Static.swap(package->model); + if (!package->materials_remapped && build_callback) + { + build_callback(Static.get_verts(), Static.get_verts_count(), Static.get_tris(), + Static.get_tris_count(), nullptr); + package->materials_remapped = true; + } + m_BoundingVolume = package->bounds; + m_static_cache_key = key; + m_static_cache_identity = identity; + m_static_materials_remapped = package->materials_remapped; + cache.erase(it); + xr_delete(package); + g_SpatialSpace->initialize(m_BoundingVolume); + g_SpatialSpacePhysic->initialize(m_BoundingVolume); + g_SpatialSpaceLights->initialize(m_BoundingVolume); + Msg("* [LEVEL CACHE] CFORM restored: %s", key.c_str()); + return; + } + } + + m_static_cache_key = key; + m_static_cache_identity = identity; + m_static_materials_remapped = build_callback != nullptr; + IReader* F = FS.r_open(resolved); R_ASSERT(F); Load(F, build_callback); } void CObjectSpace::Load(IReader* F, CDB::build_callback build_callback) { - static IReader* pReader = nullptr; - pReader = F; + Static.async_cform_load.wait(); hdrCFORM H; - pReader->r(&H, sizeof(hdrCFORM)); + F->r(&H, sizeof(hdrCFORM)); R_ASSERT(CFORM_CURRENT_VERSION == H.version); m_BoundingVolume.set(H.aabb); @@ -120,7 +296,8 @@ void CObjectSpace::Load(IReader* F, CDB::build_callback build_callback) Fvector* verts = (Fvector*)F->pointer(); CDB::TRI* tris = (CDB::TRI*)(verts + H.vertcount); Create(verts, tris, H, build_callback, false); - FS.r_close(pReader); + IReader* reader = F; + FS.r_close(reader); }); } diff --git a/src/xrCDB/xr_area.h b/src/xrCDB/xr_area.h index 02fbebda17..ed17ae2ca5 100644 --- a/src/xrCDB/xr_area.h +++ b/src/xrCDB/xr_area.h @@ -28,7 +28,11 @@ class XRCDB_API CObjectSpace private: CDB::MODEL Static; Fbox m_BoundingVolume; + shared_str m_static_cache_key; + u64 m_static_cache_identity = 0; + bool m_static_materials_remapped = false; public: + static void PrepareStatic(LPCSTR level_path); #ifdef DEBUG FactoryPtr *m_pRender; #endif diff --git a/src/xrCore/LocatorAPI.cpp b/src/xrCore/LocatorAPI.cpp index e4f83a381a..a8cf57294c 100644 --- a/src/xrCore/LocatorAPI.cpp +++ b/src/xrCore/LocatorAPI.cpp @@ -29,6 +29,207 @@ const u32 BIG_FILE_READER_WINDOW_SIZE = 1024 * 1024; CLocatorAPI* xr_FS = NULL; +struct CLocatorAPI::ArchiveDataView +{ + void* base; + u32 size; + + ArchiveDataView(void* mapped_base, u32 mapped_size, LPCSTR name) : base(mapped_base), size(mapped_size) + { +#ifdef FS_DEBUG + register_file_mapping(base, size, name); +#endif + } + + ~ArchiveDataView() + { +#ifdef FS_DEBUG + unregister_file_mapping(base, size); +#endif + UnmapViewOfFile(base); + } +}; + +namespace +{ +class CArchiveReader final : public IReader +{ + xr_shared_ptr owner; + +public: + CArchiveReader(xr_shared_ptr view, void* data, int size) + : IReader(data, size), owner(std::move(view)) + { + } +}; + +class CStartupLooseReader final : public IReader +{ + xr_shared_ptr> owner; + +public: + explicit CStartupLooseReader(xr_shared_ptr> data) + : IReader(const_cast(data->data()), static_cast(data->size())), owner(std::move(data)) + { + } +}; + +struct InitialFileData +{ + u32 vfs; + u32 crc; + u32 ptr; + u32 size_real; + u32 size_compressed; + u32 modif; +}; + +struct InitialNameHash +{ + size_t operator()(const xr_string& value) const + { + size_t result = sizeof(size_t) == 8 ? size_t(14695981039346656037ull) : size_t(2166136261u); + const size_t prime = sizeof(size_t) == 8 ? size_t(1099511628211ull) : size_t(16777619u); + for (const unsigned char character : value) + { + result ^= character; + result *= prime; + } + return result; + } +}; +} + +struct CLocatorAPI::StartupLooseCache +{ + enum class State : u8 + { + Queued, + Loading, + Ready, + Failed + }; + + struct Entry + { + xr_string name; + u32 size = 0; + u32 modif = 0; + State state = State::Queued; + bool valid = true; + xr_shared_ptr> data; + }; + + using EntryPtr = xr_shared_ptr; + + std::mutex mutex; + std::condition_variable changed; + xr_map entries; + xr_vector queue; + size_t next = 0; + bool stopping = false; + xr_task_group workers; + std::atomic workers_remaining{0}; + std::atomic prepared{0}; + std::atomic failed{0}; + std::atomic promoted{0}; + std::atomic demand_wait_ms{0}; + + static xr_shared_ptr> Read(const Entry& entry) + { + HANDLE file = CreateFileA(entry.name.c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr); + if (file == INVALID_HANDLE_VALUE) + return {}; + BY_HANDLE_FILE_INFORMATION before; + if (!GetFileInformationByHandle(file, &before) || before.nFileSizeHigh || before.nFileSizeLow != entry.size) + { + CloseHandle(file); + return {}; + } + + auto buffer = xr_make_shared>(); + buffer->resize(entry.size); + u32 total_read = 0; + while (total_read < entry.size) + { + DWORD bytes_read = 0; + if (!ReadFile(file, buffer->data() + total_read, entry.size - total_read, &bytes_read, nullptr) || !bytes_read) + break; + total_read += bytes_read; + } + BY_HANDLE_FILE_INFORMATION after; + const bool unchanged = GetFileInformationByHandle(file, &after) && !after.nFileSizeHigh && + after.nFileSizeLow == entry.size && + CompareFileTime(&before.ftLastWriteTime, &after.ftLastWriteTime) == 0; + CloseHandle(file); + + if (total_read != entry.size || !unchanged) + return {}; + + return buffer; + } + + void Load(const EntryPtr& entry) + { + xr_shared_ptr> data; + try + { + data = Read(*entry); + } + catch (...) + { + } + + { + std::lock_guard guard(mutex); + if (entry->valid && data) + { + entry->data = std::move(data); + entry->state = State::Ready; + ++prepared; + } + else + { + entry->state = State::Failed; + ++failed; + } + } + changed.notify_all(); + } + + void Worker() + { + for (;;) + { + EntryPtr entry; + { + std::lock_guard guard(mutex); + while (!stopping && next < queue.size()) + { + entry = queue[next++]; + if (entry->valid && entry->state == State::Queued) + { + entry->state = State::Loading; + break; + } + entry.reset(); + } + if (stopping || !entry) + break; + } + Load(entry); + } + + if (workers_remaining.fetch_sub(1) == 1) + { + Msg("* [STARTUP/VFS] loose-cache prepared=%u failed=%u promoted=%u demand-wait=%llu ms", + prepared.load(), failed.load(), promoted.load(), demand_wait_ms.load()); + } + } +}; + #ifdef _EDITOR # define FSLTX "fs.ltx" #else @@ -203,10 +404,14 @@ XRCORE_API void _dump_open_files(int mode) CLocatorAPI::CLocatorAPI() #ifdef PROFILE_CRITICAL_SECTIONS - :m_auth_lock(MUTEX_PROFILE_ID(CLocatorAPI::m_auth_lock)) + : m_scan_lock(MUTEX_PROFILE_ID(CLocatorAPI::m_scan_lock)), + m_auth_lock(MUTEX_PROFILE_ID(CLocatorAPI::m_auth_lock)) #endif // PROFILE_CRITICAL_SECTIONS { m_Flags.zero(); + m_initial_build = false; + m_initial_archive_index_ms = 0; + m_startup_loose_cache = nullptr; // get page size SYSTEM_INFO sys_inf; GetSystemInfo(&sys_inf); @@ -223,11 +428,28 @@ CLocatorAPI::~CLocatorAPI() void CLocatorAPI::Register(LPCSTR name, u32 vfs, u32 crc, u32 ptr, u32 size_real, u32 size_compressed, u32 modif) { - //Msg("Register[%d] [%s]",vfs,name); string256 temp_file_name; xr_strcpy(temp_file_name, sizeof(temp_file_name), name); xr_strlwr(temp_file_name); + if (m_initial_build) + { + InitialFileRecord record; + record.name = temp_file_name; + record.vfs = vfs; + record.crc = crc; + record.ptr = ptr; + record.size_real = size_real; + record.size_compressed = size_compressed; + record.modif = modif & (~u32(0x3)); + m_initial_files.push_back(std::move(record)); + return; + } + + xrCriticalSectionGuard guard(m_scan_lock); + //Msg("Register[%d] [%s]",vfs,name); + InvalidateStartupLooseCache(temp_file_name); + // Register file file desc; // desc.name = xr_strlwr(xr_strdup(name)); @@ -287,6 +509,252 @@ void CLocatorAPI::Register(LPCSTR name, u32 vfs, u32 crc, u32 ptr, u32 size_real } } +void CLocatorAPI::CommitInitialFiles(u64 scan_started_at) +{ + using InitialFileMap = xr_unordered_map; + using InitialFileMapEntry = InitialFileMap::value_type; + + m_initial_build = false; + InitialFileMap merged; + merged.reserve(m_initial_files.size()); + u32 replacements = 0; + + const u64 replay_started_at = GetTickCount64(); + for (const InitialFileRecord& record : m_initial_files) + { + InitialFileData data = { + record.vfs, record.crc, record.ptr, record.size_real, record.size_compressed, record.modif + }; + auto existing = merged.find(record.name); + if (existing != merged.end()) + { + existing->second = data; + ++replacements; + continue; + } + + merged.emplace(record.name, data); + + string_path temp; + xr_strcpy(temp, sizeof(temp), record.name.c_str()); + string_path path; + string_path folder; + u32 vfs_id = record.vfs; + while (temp[0] && temp[1]) + { + _splitpath(temp, path, folder, nullptr, nullptr); + xr_strcat(path, folder); + if (merged.find(path) == merged.end()) + { + InitialFileData folder_data = data; + folder_data.vfs = vfs_id; + folder_data.ptr = 0; + folder_data.size_real = 0; + folder_data.size_compressed = 0; + folder_data.modif = u32(-1); + merged.emplace(path, folder_data); + } + xr_strcpy(temp, sizeof(temp), path); + if (xr_strlen(temp)) + temp[xr_strlen(temp) - 1] = 0; + vfs_id = u32(-1); + } + } + const u64 replay_ms = GetTickCount64() - replay_started_at; + + const u64 sort_started_at = GetTickCount64(); + xr_vector sorted; + sorted.reserve(merged.size()); + for (const InitialFileMapEntry& entry : merged) + sorted.push_back(&entry); + std::sort(sorted.begin(), sorted.end(), [](const InitialFileMapEntry* left, const InitialFileMapEntry* right) + { + return xr_strcmp(left->first.c_str(), right->first.c_str()) < 0; + }); + const u64 sort_ms = GetTickCount64() - sort_started_at; + + const u64 commit_started_at = GetTickCount64(); + R_ASSERT(m_files.empty()); + files_set committed; + auto hint = committed.end(); + for (const InitialFileMapEntry* entry : sorted) + { + const InitialFileData& source = entry->second; + file desc = { + xr_strdup(entry->first.c_str()), source.vfs, source.crc, source.ptr, source.size_real, + source.size_compressed, source.modif + }; + hint = committed.emplace_hint(hint, desc); + } + m_files.swap(committed); + const u64 commit_ms = GetTickCount64() - commit_started_at; + + u32 catalog_hash = 0; + for (const file& entry : m_files) + { + catalog_hash = crc32(entry.name, xr_strlen(entry.name) + 1, catalog_hash); + catalog_hash = crc32(&entry.vfs, sizeof(entry.vfs), catalog_hash); + catalog_hash = crc32(&entry.crc, sizeof(entry.crc), catalog_hash); + catalog_hash = crc32(&entry.ptr, sizeof(entry.ptr), catalog_hash); + catalog_hash = crc32(&entry.size_real, sizeof(entry.size_real), catalog_hash); + catalog_hash = crc32(&entry.size_compressed, sizeof(entry.size_compressed), catalog_hash); + catalog_hash = crc32(&entry.modif, sizeof(entry.modif), catalog_hash); + } + + const u64 scan_total_ms = replay_started_at - scan_started_at; + const u64 discovery_ms = scan_total_ms > m_initial_archive_index_ms + ? scan_total_ms - m_initial_archive_index_ms + : 0; + Msg("* [STARTUP/VFS] discovery=%llu ms index=%llu ms replay=%llu ms sort=%llu ms commit=%llu ms files=%u replacements=%u catalog=%08x", + discovery_ms, m_initial_archive_index_ms, replay_ms, sort_ms, commit_ms, m_files.size(), replacements, catalog_hash); + + m_initial_files.clear_and_free(); +} + +void CLocatorAPI::StartStartupLooseCache() +{ + R_ASSERT(!m_startup_loose_cache); + auto* cache = xr_new(); + u64 total_bytes = 0; + + for (LPCSTR alias : {"$game_config$", "$game_scripts$", "$game_textures$"}) + { + const PathPairIt path = pathes.find(alias); + if (path == pathes.end()) + continue; + + const bool texture_thm = xr_strcmp(alias, "$game_textures$") == 0; + xr_string prefix = path->second->m_Path; + std::transform(prefix.begin(), prefix.end(), prefix.begin(), + [](unsigned char character) { return static_cast(tolower(character)); }); + + for (const file& desc : m_files) + { + if (desc.vfs != u32(-1) || !desc.size_real || strncmp(desc.name, prefix.c_str(), prefix.size()) != 0) + continue; + if (texture_thm) + { + LPCSTR extension = strext(desc.name); + if (!extension || xr_strcmp(extension, ".thm") != 0) + continue; + } + + auto entry = xr_make_shared(); + entry->name = desc.name; + entry->size = desc.size_real; + entry->modif = desc.modif; + if (cache->entries.emplace(entry->name, entry).second) + { + cache->queue.push_back(std::move(entry)); + total_bytes += desc.size_real; + } + } + } + + if (cache->queue.empty()) + { + xr_delete(cache); + return; + } + + m_startup_loose_cache = cache; + const u32 worker_count = std::min( + std::max(2u, std::thread::hardware_concurrency() / 2), + std::min(8, static_cast(cache->queue.size()))); + cache->workers_remaining = worker_count; + Msg("* [STARTUP/VFS] loose-cache queued=%u bytes=%llu workers=%u", + static_cast(cache->queue.size()), total_bytes, worker_count); + for (u32 worker = 0; worker < worker_count; ++worker) + cache->workers.run([cache] { cache->Worker(); }); +} + +void CLocatorAPI::StopStartupLooseCache() +{ + StartupLooseCache* cache = m_startup_loose_cache; + if (!cache) + return; + + { + std::lock_guard guard(cache->mutex); + cache->stopping = true; + } + cache->changed.notify_all(); + cache->workers.wait(); + m_startup_loose_cache = nullptr; + xr_delete(cache); +} + +void CLocatorAPI::InvalidateStartupLooseCache(LPCSTR name) +{ + StartupLooseCache* cache = m_startup_loose_cache; + if (!cache) + return; + string_path normalized; + xr_strcpy(normalized, sizeof(normalized), name); + xr_strlwr(normalized); + + { + std::lock_guard guard(cache->mutex); + const auto entry = cache->entries.find(normalized); + if (entry == cache->entries.end()) + return; + entry->second->valid = false; + entry->second->state = StartupLooseCache::State::Failed; + entry->second->data.reset(); + cache->entries.erase(entry); + } + cache->changed.notify_all(); +} + +bool CLocatorAPI::OpenStartupLooseCache(IReader*& reader, LPCSTR name, const file& desc) +{ + StartupLooseCache* cache = m_startup_loose_cache; + if (!cache) + return false; + + StartupLooseCache::EntryPtr entry; + bool load = false; + { + std::unique_lock guard(cache->mutex); + const auto found = cache->entries.find(name); + if (cache->stopping || found == cache->entries.end()) + return false; + entry = found->second; + if (!entry->valid || entry->size != desc.size_real || entry->modif != desc.modif) + return false; + + if (entry->state == StartupLooseCache::State::Queued) + { + entry->state = StartupLooseCache::State::Loading; + ++cache->promoted; + load = true; + } + else if (entry->state == StartupLooseCache::State::Loading) + { + const u64 started_at = GetTickCount64(); + cache->changed.wait(guard, [&entry] + { + return !entry->valid || entry->state != StartupLooseCache::State::Loading; + }); + cache->demand_wait_ms += GetTickCount64() - started_at; + } + } + + if (load) + cache->Load(entry); + + xr_shared_ptr> data; + { + std::lock_guard guard(cache->mutex); + if (!entry->valid || entry->state != StartupLooseCache::State::Ready) + return false; + data = entry->data; + } + + reader = xr_new(std::move(data)); + return true; +} + IReader* open_chunk(void* ptr, u32 ID) { BOOL res; @@ -338,6 +806,8 @@ IReader* open_chunk(void* ptr, u32 ID) void CLocatorAPI::LoadArchive(archive& A, LPCSTR entrypoint) { + const u64 initial_index_started_at = m_initial_build ? GetTickCount64() : 0; + // Create base path string_path fs_entry_point; fs_entry_point[0] = 0; @@ -432,6 +902,8 @@ void CLocatorAPI::LoadArchive(archive& A, LPCSTR entrypoint) Register(full, A.vfs_idx, crc, ptr, size_real, size_compr, 0); } hdr->close(); + if (m_initial_build) + m_initial_archive_index_ms += GetTickCount64() - initial_index_started_at; // if(g_temporary_stuff_subst) // g_temporary_stuff = g_temporary_stuff_subst; @@ -453,14 +925,48 @@ void CLocatorAPI::archive::open() void CLocatorAPI::archive::close() { - CloseHandle(hSrcMap); - hSrcMap = NULL; - CloseHandle(hSrcFile); - hSrcFile = NULL; + std::atomic_store(&data_view, xr_shared_ptr()); + if (hSrcMap) + { + CloseHandle(hSrcMap); + hSrcMap = NULL; + } + if (hSrcFile) + { + CloseHandle(hSrcFile); + hSrcFile = NULL; + } +} + +xr_shared_ptr CLocatorAPI::GetArchiveDataView(archive& A) +{ + if (xr_shared_ptr view = std::atomic_load(&A.data_view)) + return view; + + xrCriticalSectionGuard guard(m_scan_lock); + if (xr_shared_ptr view = std::atomic_load(&A.data_view)) + return view; + if (A.data_view_failed) + return {}; + + A.open(); + void* base = MapViewOfFile(A.hSrcMap, FILE_MAP_READ, 0, 0, 0); + if (!base) + { + A.data_view_failed = true; + Msg("! [STARTUP/VFS] full archive mapping failed, using per-file views: %s (%s)", + A.path.c_str(), Debug.error2string(GetLastError())); + return {}; + } + + xr_shared_ptr view = xr_make_shared(base, A.size, A.path.c_str()); + std::atomic_store(&A.data_view, view); + return view; } void CLocatorAPI::ProcessArchive(LPCSTR _path) { + xrCriticalSectionGuard guard(m_scan_lock); // find existing archive shared_str path = _path; @@ -499,6 +1005,7 @@ void CLocatorAPI::ProcessArchive(LPCSTR _path) void CLocatorAPI::unload_archive(CLocatorAPI::archive& A) { + xrCriticalSectionGuard guard(m_scan_lock); files_it I = m_files.begin(); for (; I != m_files.end(); ++I) { @@ -535,7 +1042,7 @@ bool CLocatorAPI::load_all_unloaded_archives() } -void CLocatorAPI::ProcessOne(LPCSTR path, const _finddata_t& entry) +void CLocatorAPI::ProcessOne(LPCSTR path, const _finddata_t& entry, u32 parallel_depth) { string_path N; xr_strcpy(N, sizeof(N), path); @@ -551,7 +1058,7 @@ void CLocatorAPI::ProcessOne(LPCSTR path, const _finddata_t& entry) if (0 == xr_strcmp(entry.name, "..")) return; xr_strcat(N, "\\"); Register(N, 0xffffffff, 0, 0, entry.size, entry.size, (u32)entry.time_write); - Recurse(N); + Recurse(N, parallel_depth ? parallel_depth - 1 : 0); } else { @@ -596,7 +1103,7 @@ bool ignore_path(const char* _path) return true; } -bool CLocatorAPI::Recurse(const char* path) +bool CLocatorAPI::Recurse(const char* path, u32 parallel_depth) { string_path scanPath; xr_strcpy(scanPath, sizeof(scanPath), path); @@ -610,8 +1117,8 @@ bool CLocatorAPI::Recurse(const char* path) intptr_t handle = _findfirst(scanPath, &findData); if (handle == -1) return false; - rec_files.reserve(256); - size_t oldSize = rec_files.size(); + FFVec files; + files.reserve(256); intptr_t done = handle; while (done != -1) { @@ -628,17 +1135,21 @@ bool CLocatorAPI::Recurse(const char* path) ignore = ignore_name(findData.name); } if (!ignore) - rec_files.push_back(findData); + files.push_back(findData); done = _findnext(handle, &findData); } _findclose(handle); - size_t newSize = rec_files.size(); - if (newSize > oldSize) + if (!files.empty()) { - std::sort(rec_files.begin() + oldSize, rec_files.end(), pred_str_ff); - for (size_t i = oldSize; i < newSize; i++) - ProcessOne(path, rec_files[i]); - rec_files.erase(rec_files.begin() + oldSize, rec_files.end()); + std::sort(files.begin(), files.end(), pred_str_ff); + if (parallel_depth && !m_initial_build) + xr_parallel_foreach(files.begin(), files.end(), [this, path, parallel_depth](const _finddata_t& entry) + { + ProcessOne(path, entry, parallel_depth); + }); + else + for (const _finddata_t& entry : files) + ProcessOne(path, entry); } // insert self if (path && path[0] != 0) @@ -755,7 +1266,10 @@ void CLocatorAPI::_initialize(u32 flags, LPCSTR target_folder, LPCSTR fs_name) size_t M1 = Memory.mem_usage(); m_Flags.set(flags, TRUE); - + m_initial_build = true; + m_initial_archive_index_ms = 0; + m_initial_files.clear_not_free(); + const u64 initial_scan_started_at = GetTickCount64(); // scan root directory bNoRecurse = TRUE; string4096 buf; @@ -780,6 +1294,8 @@ void CLocatorAPI::_initialize(u32 flags, LPCSTR target_folder, LPCSTR fs_name) const char *lp_add, *lp_def, *lp_capt; string16 b_v; string4096 temp; + xr_vector recursive_roots; + u32 skipped_scans = 0; while (!pFSltx->eof()) { @@ -829,7 +1345,23 @@ void CLocatorAPI::_initialize(u32 flags, LPCSTR target_folder, LPCSTR fs_name) FS_Path* P = new FS_Path((p_it != pathes.end()) ? p_it->second->m_Path : root, lp_add, lp_def, lp_capt, fl); bNoRecurse = !(fl & FS_Path::flRecurse); - Recurse(P->m_Path); + bool already_scanned = false; + for (const xr_string& scanned_root : recursive_roots) + { + if (!_strnicmp(P->m_Path, scanned_root.c_str(), scanned_root.size())) + { + already_scanned = true; + break; + } + } + if (already_scanned) + ++skipped_scans; + else + { + Recurse(P->m_Path, bNoRecurse ? 0 : 1); + if (fl & FS_Path::flRecurse) + recursive_roots.emplace_back(P->m_Path); + } auto I = pathes.insert(std::make_pair(xr_strdup(id), P)); #ifndef DEBUG m_Flags.set(flCacheFiles, FALSE); @@ -837,11 +1369,12 @@ void CLocatorAPI::_initialize(u32 flags, LPCSTR target_folder, LPCSTR fs_name) //CHECK_OR_EXIT (I.second,"The file 'fsgame.ltx' is corrupted (it contains duplicated lines).\nPlease reinstall the game or fix the problem manually."); } + Msg("FS: skipped %u duplicate alias scans", skipped_scans); r_close(pFSltx); R_ASSERT(path_exist("$app_data_root$")); }; - + CommitInitialFiles(initial_scan_started_at); Msg("File System Ready..."); size_t M2 = Memory.mem_usage(); Msg("FS: %d files cached %d archives, %lldKb memory used.", m_files.size(), m_archives.size(), (M2 - M1) / 1024); @@ -873,10 +1406,12 @@ void CLocatorAPI::_initialize(u32 flags, LPCSTR target_folder, LPCSTR fs_name) { xrLogger::OpenLogFile(); } + StartStartupLooseCache(); } void CLocatorAPI::_destroy() { + StopStartupLooseCache(); xrLogger::CloseLog(); for (files_it I = m_files.begin(); I != m_files.end(); I++) @@ -902,6 +1437,7 @@ void CLocatorAPI::_destroy() const CLocatorAPI::file* CLocatorAPI::exist(const char* fn) { + xrCriticalSectionGuard guard(m_scan_lock); files_it it = file_find_it(fn); return (it != m_files.end()) ? &(*it) : 0; } @@ -1140,6 +1676,9 @@ void CLocatorAPI::check_cached_files(LPSTR fname, const u32& fname_size, const f void CLocatorAPI::file_from_cache_impl(IReader*& R, LPSTR fname, const file& desc) { + if (OpenStartupLooseCache(R, fname, desc)) + return; + if (desc.size_real < 16 * 1024) { R = xr_new(fname); @@ -1171,6 +1710,22 @@ void CLocatorAPI::file_from_archive(IReader*& R, LPCSTR fname, const file& desc) { // Archived one archive& A = m_archives[desc.vfs]; + if (const xr_shared_ptr view = GetArchiveDataView(A)) + { + R_ASSERT3(u64(desc.ptr) + desc.size_compressed <= view->size, "archive entry is outside mapping", fname); + u8* source = static_cast(view->base) + desc.ptr; + if (desc.size_real == desc.size_compressed) + { + R = xr_new(view, source, desc.size_real); + return; + } + + u8* dest = xr_alloc(desc.size_real); + rtc_decompress(dest, desc.size_real, source, desc.size_compressed); + R = xr_new(dest, desc.size_real, 0); + return; + } + u32 start = (desc.ptr / dwAllocGranularity) * dwAllocGranularity; u32 end = (desc.ptr + desc.size_compressed) / dwAllocGranularity; if ((desc.ptr + desc.size_compressed) % dwAllocGranularity) end += 1; @@ -1320,6 +1875,7 @@ void CLocatorAPI::copy_file_to_build(T*& r, LPCSTR source_name) bool CLocatorAPI::check_for_file(LPCSTR path, LPCSTR _fname, string_path& fname, const file*& desc) { + xrCriticalSectionGuard guard(m_scan_lock); // проверить нужно ли пересканировать пути check_pathes(); @@ -1489,6 +2045,7 @@ BOOL CLocatorAPI::dir_delete(LPCSTR path, LPCSTR nm, BOOL remove_files) { // const char* entry_begin = entry.name+base_len; if (!remove_files) return FALSE; + InvalidateStartupLooseCache(entry.name); unlink(entry.name); m_files.erase(cur_item); } @@ -1522,6 +2079,7 @@ void CLocatorAPI::file_delete(LPCSTR path, LPCSTR nm) if (I != m_files.end()) { // remove file + InvalidateStartupLooseCache(I->name); unlink(I->name); char* str = LPSTR(I->name); xr_free(str); @@ -1556,6 +2114,7 @@ void CLocatorAPI::file_rename(LPCSTR src, LPCSTR dest, bool bOwerwrite) if (D != m_files.end()) { if (!bOwerwrite) return; + InvalidateStartupLooseCache(D->name); unlink(D->name); char* str = LPSTR(D->name); xr_free(str); @@ -1563,6 +2122,7 @@ void CLocatorAPI::file_rename(LPCSTR src, LPCSTR dest, bool bOwerwrite) } file new_desc = *S; + InvalidateStartupLooseCache(S->name); // remove existing item char* str = LPSTR(S->name); xr_free(str); diff --git a/src/xrCore/LocatorAPI.h b/src/xrCore/LocatorAPI.h index d1e8362806..a813d8b6e0 100644 --- a/src/xrCore/LocatorAPI.h +++ b/src/xrCore/LocatorAPI.h @@ -19,6 +19,8 @@ class XRCORE_API CLocatorAPI { friend class FS_Path; public: + struct ArchiveDataView; + struct file { LPCSTR name; // low-case name @@ -37,8 +39,10 @@ class XRCORE_API CLocatorAPI u32 size; CInifile* header; u32 vfs_idx; + xr_shared_ptr data_view; + bool data_view_failed; - archive() : hSrcFile(NULL), hSrcMap(NULL), header(NULL), size(0), vfs_idx(u32(-1)) + archive() : hSrcFile(NULL), hSrcMap(NULL), header(NULL), size(0), vfs_idx(u32(-1)), data_view_failed(false) { } @@ -51,6 +55,19 @@ class XRCORE_API CLocatorAPI void LoadArchive(archive& A, LPCSTR entrypoint = NULL); private: + struct InitialFileRecord + { + xr_string name; + u32 vfs; + u32 crc; + u32 ptr; + u32 size_real; + u32 size_compressed; + u32 modif; + }; + + struct StartupLooseCache; + struct file_pred { IC bool operator()(const file& x, const file& y) const @@ -73,13 +90,24 @@ class XRCORE_API CLocatorAPI files_set m_files; BOOL bNoRecurse; + xrCriticalSection m_scan_lock; xrCriticalSection m_auth_lock; u64 m_auth_code; + bool m_initial_build; + u64 m_initial_archive_index_ms; + xr_vector m_initial_files; + StartupLooseCache* m_startup_loose_cache; void Register(LPCSTR name, u32 vfs, u32 crc, u32 ptr, u32 size_real, u32 size_compressed, u32 modif); + void CommitInitialFiles(u64 scan_started_at); + xr_shared_ptr GetArchiveDataView(archive& archive); + void StartStartupLooseCache(); + void StopStartupLooseCache(); + void InvalidateStartupLooseCache(LPCSTR name); + bool OpenStartupLooseCache(IReader*& reader, LPCSTR name, const file& desc); void ProcessArchive(LPCSTR path); - void ProcessOne(LPCSTR path, const _finddata_t& entry); - bool Recurse(LPCSTR path); + void ProcessOne(LPCSTR path, const _finddata_t& entry, u32 parallel_depth = 0); + bool Recurse(LPCSTR path, u32 parallel_depth = 0); files_it file_find_it(LPCSTR n); public: diff --git a/src/xrCore/ShaderSourceCRC.cpp b/src/xrCore/ShaderSourceCRC.cpp index 9fa56b8a59..23078e8d69 100644 --- a/src/xrCore/ShaderSourceCRC.cpp +++ b/src/xrCore/ShaderSourceCRC.cpp @@ -3,18 +3,48 @@ namespace { -void addShaderSourceCrc32(const void* sourceData, u32 sourceSize, LPCSTR shaderPath, u32& crc); +struct ShaderSourceDependency +{ + xr_string path; + u32 crc; + u32 size_real; + u32 size_compressed; + u32 modif; +}; + +struct ShaderSourceCrcCacheEntry +{ + u32 crc; + xr_vector dependencies; +}; + +xrCriticalSection& ShaderSourceCrcCacheLock() +{ + static xrCriticalSection* lock = xr_new(); + return *lock; +} + +xr_map& ShaderSourceCrcCache() +{ + static xr_map* cache = + xr_new>(); + return *cache; +} + +void addShaderSourceCrc32(const void* sourceData, u32 sourceSize, LPCSTR shaderPath, u32& crc, + xr_vector* dependencies); -IReader* openShaderInclude(LPCSTR shaderPath, LPCSTR includeName) +IReader* openShaderInclude(LPCSTR shaderPath, LPCSTR includeName, string_path& resolved) { string_path includePath; strconcat(sizeof(includePath), includePath, shaderPath ? shaderPath : "", includeName); - IReader* reader = FS.r_open("$game_shaders$", includePath); - if (reader) - return reader; + if (FS.exist(resolved, "$game_shaders$", includePath)) + return FS.r_open(resolved); - return FS.r_open("$game_shaders$", includeName); + if (FS.exist(resolved, "$game_shaders$", includeName)) + return FS.r_open(resolved); + return nullptr; } bool getIncludeName(LPCSTR sourceLine, string_path& includeName) @@ -33,20 +63,29 @@ bool getIncludeName(LPCSTR sourceLine, string_path& includeName) return true; } -void addIncludedShaderCrc32(LPCSTR shaderPath, LPCSTR includeName, u32& crc) +void addIncludedShaderCrc32(LPCSTR shaderPath, LPCSTR includeName, u32& crc, + xr_vector* dependencies) { - IReader* includeReader = openShaderInclude(shaderPath, includeName); + string_path resolved; + IReader* includeReader = openShaderInclude(shaderPath, includeName, resolved); if (!includeReader) { Msg("! Shader source CRC: can't find include '%s', skipping it for cache validation", includeName); return; } - addShaderSourceCrc32(includeReader->pointer(), includeReader->length(), shaderPath, crc); + if (dependencies) + { + const CLocatorAPI::file* file = FS.exist(resolved); + if (file) + dependencies->push_back({resolved, file->crc, file->size_real, file->size_compressed, file->modif}); + } + addShaderSourceCrc32(includeReader->pointer(), includeReader->length(), shaderPath, crc, dependencies); FS.r_close(includeReader); } -void parseShaderIncludes(const char* sourceData, u32 sourceSize, LPCSTR shaderPath, u32& crc) +void parseShaderIncludes(const char* sourceData, u32 sourceSize, LPCSTR shaderPath, u32& crc, + xr_vector* dependencies) { const char* cursor = sourceData; const char* const sourceEnd = sourceData + sourceSize; @@ -71,20 +110,94 @@ void parseShaderIncludes(const char* sourceData, u32 sourceSize, LPCSTR shaderPa string_path includeName; if (getIncludeName(line, includeName)) - addIncludedShaderCrc32(shaderPath, includeName, crc); + addIncludedShaderCrc32(shaderPath, includeName, crc, dependencies); } } -void addShaderSourceCrc32(const void* sourceData, u32 sourceSize, LPCSTR shaderPath, u32& crc) +void addShaderSourceCrc32(const void* sourceData, u32 sourceSize, LPCSTR shaderPath, u32& crc, + xr_vector* dependencies) { crc = crc32(sourceData, sourceSize, crc); - parseShaderIncludes(static_cast(sourceData), sourceSize, shaderPath, crc); + parseShaderIncludes(static_cast(sourceData), sourceSize, shaderPath, crc, dependencies); +} + +bool dependenciesUnchanged(const ShaderSourceCrcCacheEntry& entry) +{ + for (const ShaderSourceDependency& dependency : entry.dependencies) + { + const CLocatorAPI::file* file = FS.exist(dependency.path.c_str()); + if (!file || file->crc != dependency.crc || file->size_real != dependency.size_real || + file->size_compressed != dependency.size_compressed || file->modif != dependency.modif) + { + return false; + } + } + return true; } } // namespace u32 getShaderSourceCrc32(const void* sourceData, u32 sourceSize, LPCSTR shaderPath) { u32 crc = 0; - addShaderSourceCrc32(sourceData, sourceSize, shaderPath, crc); + addShaderSourceCrc32(sourceData, sourceSize, shaderPath, crc, nullptr); return crc; } + +u32 getShaderSourceCrc32Cached(const void* sourceData, u32 sourceSize, LPCSTR shaderPath, LPCSTR sourceName, + LPCSTR target) +{ + xr_string relative = shaderPath ? shaderPath : ""; + relative += sourceName ? sourceName : ""; + relative += '.'; + if (target && target[0]) + { + relative += target[0]; + if (target[1]) + relative += target[1]; + } + + xr_string key = relative; + string_path resolved; + if (const CLocatorAPI::file* source = FS.exist(resolved, "$game_shaders$", relative.c_str())) + { + string128 identity; + xr_sprintf(identity, "|%08x:%08x:%08x:%08x", source->crc, source->size_real, + source->size_compressed, source->modif); + key = resolved; + key += identity; + } + else + { + string32 size; + xr_sprintf(size, "|%08x", sourceSize); + key += size; + } + + ShaderSourceCrcCacheEntry cached_entry = {}; + bool cached = false; + { + xrCriticalSectionGuard guard(ShaderSourceCrcCacheLock()); + auto found = ShaderSourceCrcCache().find(key); + if (found != ShaderSourceCrcCache().end()) + { + cached_entry = found->second; + cached = true; + } + } + if (cached && dependenciesUnchanged(cached_entry)) + return cached_entry.crc; + + ShaderSourceCrcCacheEntry entry = {}; + addShaderSourceCrc32(sourceData, sourceSize, shaderPath, entry.crc, &entry.dependencies); + { + xrCriticalSectionGuard guard(ShaderSourceCrcCacheLock()); + ShaderSourceCrcCache()[key] = entry; + } + return entry.crc; +} + +void clearShaderSourceCrcCache() +{ + xrCriticalSectionGuard guard(ShaderSourceCrcCacheLock()); + ShaderSourceCrcCache().clear(); +} diff --git a/src/xrCore/ShaderSourceCRC.h b/src/xrCore/ShaderSourceCRC.h index 95ea98d94c..36c6c1444a 100644 --- a/src/xrCore/ShaderSourceCRC.h +++ b/src/xrCore/ShaderSourceCRC.h @@ -1,3 +1,6 @@ #pragma once XRCORE_API u32 getShaderSourceCrc32(const void* sourceData, u32 sourceSize, LPCSTR shaderPath); +XRCORE_API u32 getShaderSourceCrc32Cached(const void* sourceData, u32 sourceSize, LPCSTR shaderPath, + LPCSTR sourceName, LPCSTR target); +XRCORE_API void clearShaderSourceCrcCache(); diff --git a/src/xrCore/Xr_ini.cpp b/src/xrCore/Xr_ini.cpp index 371d807fa5..cbab006d54 100644 --- a/src/xrCore/Xr_ini.cpp +++ b/src/xrCore/Xr_ini.cpp @@ -270,7 +270,9 @@ IC BOOL is_empty_line_now(IReader* F) // Regex pattern cache (added before Load function) static const std::regex& GetCachedRegex(const xr_string& pattern) { - static xr_unordered_flat_map g_RegexCache; + static xr_map g_RegexCache; + static xrCriticalSection g_RegexCacheGuard; + xrCriticalSectionGuard guard(g_RegexCacheGuard); auto it = g_RegexCache.find(pattern); if (it == g_RegexCache.end()) { diff --git a/src/xrCore/_thread_types.h b/src/xrCore/_thread_types.h index 74b8203b5b..c45d71c3f8 100644 --- a/src/xrCore/_thread_types.h +++ b/src/xrCore/_thread_types.h @@ -4,10 +4,18 @@ #include #include #include +#include +#include +#include +#include #include +#include +#include +#include #include #include #include +#include // Atomic types using xr_atomic_u32 = std::atomic_uint32_t; @@ -51,3 +59,505 @@ IC void xr_parallel_sort(RandomIt first, RandomIt last, P pred = {}) { concurrency::parallel_sort(first, last, pred); } + +enum class NativeLoadPriority : u8 +{ + Spawn, + ShaderTexture, + Geometry, + Environment, + Speculative, + Count +}; + +// One process-wide load queue over the existing PPL scheduler. Pump tasks take +// the highest-priority available work, so idle scheduler workers automatically +// help whichever native subsystem still has work left. +class NativeLoadExecutor : xray::noncopyable +{ + struct GenerationState; + struct BatchState; + +public: + using GenerationId = u64; + + class Batch + { + friend class NativeLoadExecutor; + + std::shared_ptr state; + + explicit Batch(std::shared_ptr value) : state(std::move(value)) {} + + public: + Batch() = default; + bool Valid() const { return !!state; } + }; + + static NativeLoadExecutor& Instance() + { + static NativeLoadExecutor executor; + return executor; + } + + GenerationId BeginGeneration() + { + std::lock_guard lifecycle_guard(lifecycle_mutex); + + std::shared_ptr previous; + { + std::lock_guard guard(mutex); + previous = current_generation; + } + if (previous) + FinishGeneration(previous, true); + + const auto state = std::make_shared(); + { + std::lock_guard guard(mutex); + state->id = ++next_generation; + current_generation = state; + } + return state->id; + } + + void CancelGeneration(GenerationId id) + { + std::lock_guard lifecycle_guard(lifecycle_mutex); + const auto state = FindGeneration(id); + if (state) + FinishGeneration(state, true); + } + + void FinalizeGeneration(GenerationId id) + { + std::lock_guard lifecycle_guard(lifecycle_mutex); + const auto state = FindGeneration(id); + if (state) + FinishGeneration(state, false); + } + + Batch BeginBatch(GenerationId id) + { + std::lock_guard guard(mutex); + if (!current_generation || current_generation->id != id || !current_generation->accepting || + current_generation->cancelled) + return {}; + + const auto batch = std::make_shared(); + batch->generation = current_generation; + return Batch(batch); + } + + template + bool Submit(const Batch& batch, NativeLoadPriority priority, Function&& function) + { + return SubmitImpl(batch, priority, std::function(std::forward(function)), {}); + } + + template + bool Submit(const Batch& batch, NativeLoadPriority priority, Function&& function, CancelFunction&& cancel) + { + return SubmitImpl(batch, priority, std::function(std::forward(function)), + std::function(std::forward(cancel))); + } + + void Wait(const Batch& batch, bool help = true) + { + const auto batch_state = batch.state; + if (!batch_state) + return; + + { + std::lock_guard guard(mutex); + batch_state->closed = true; + batch_state->completed.notify_all(); + } + + for (;;) + { + { + std::lock_guard guard(mutex); + if (!batch_state->pending) + break; + } + + if (help && TryExecuteOne(batch_state)) + continue; + + std::unique_lock guard(mutex); + batch_state->completed.wait(guard, [&batch_state] { return !batch_state->pending; }); + } + + std::exception_ptr failure; + { + std::lock_guard guard(mutex); + // A batch owns the lifetime of the data captured by its tasks. Do + // not unwind it because an unrelated batch in the same generation + // failed; cancelled work below inherits the generation failure into + // its own batch before completion. + failure = batch_state->failure; + } + if (failure) + std::rethrow_exception(failure); + } + + bool IsCurrent(GenerationId id) const + { + std::lock_guard guard(mutex); + return current_generation && current_generation->id == id && !current_generation->cancelled; + } + + GenerationId CurrentGeneration() const + { + std::lock_guard guard(mutex); + return current_generation ? current_generation->id : 0; + } + + // Device reset must not invalidate resources while load jobs still own or + // create them. Drain the current generation without closing it: later + // stages of the same load may still enqueue more work after reset. + void WaitCurrentGenerationIdle() + { + std::lock_guard lifecycle_guard(lifecycle_mutex); + + std::shared_ptr generation; + { + std::lock_guard guard(mutex); + generation = current_generation; + } + if (!generation) + return; + + WaitForGeneration(generation); + + std::exception_ptr failure; + { + std::lock_guard guard(mutex); + failure = generation->failure; + } + if (failure) + std::rethrow_exception(failure); + } + + bool HelpGeneration(GenerationId id) + { + const auto state = FindGeneration(id); + return state && TryExecuteOne(state); + } + + u32 WorkerLimit() const { return worker_limit; } + +private: + struct GenerationState + { + GenerationId id = 0; + bool accepting = true; + bool cancelled = false; + u32 pending = 0; + u32 queued = 0; + u32 active_pumps = 0; + std::exception_ptr failure; + std::condition_variable completed; + std::condition_variable pumps_idle; + std::shared_ptr pumps = std::make_shared(); + }; + + struct BatchState + { + std::shared_ptr generation; + u32 pending = 0; + bool closed = false; + std::exception_ptr failure; + std::condition_variable completed; + }; + + struct WorkItem + { + std::shared_ptr generation; + std::shared_ptr batch; + std::function function; + std::function cancel; + }; + + static constexpr size_t PriorityCount = static_cast(NativeLoadPriority::Count); + + mutable std::mutex mutex; + std::mutex lifecycle_mutex; + std::array, PriorityCount> queues; + std::shared_ptr current_generation; + GenerationId next_generation = 0; + const u32 worker_limit; + + NativeLoadExecutor() + : worker_limit(std::max(1u, std::thread::hardware_concurrency() > 1 ? + std::thread::hardware_concurrency() - 1 : 1u)) + { + } + + ~NativeLoadExecutor() + { + try + { + const GenerationId id = CurrentGeneration(); + if (id) + CancelGeneration(id); + } + catch (...) + { + } + } + + std::shared_ptr FindGeneration(GenerationId id) const + { + std::lock_guard guard(mutex); + return current_generation && current_generation->id == id ? current_generation : nullptr; + } + + bool PopWorkLocked(const std::shared_ptr& generation, WorkItem& result) + { + for (auto& queue : queues) + { + if (queue.empty() || queue.front().generation != generation) + continue; + + result = std::move(queue.front()); + queue.pop_front(); + --generation->queued; + return true; + } + return false; + } + + bool PopWorkLocked(const std::shared_ptr& batch, WorkItem& result) + { + for (auto& queue : queues) + { + // Nested work can sit behind its parent in the same priority lane. + const auto item = std::find_if(queue.begin(), queue.end(), [&batch](const WorkItem& work) + { + return work.batch == batch; + }); + if (item == queue.end()) + continue; + + result = std::move(*item); + queue.erase(item); + --batch->generation->queued; + return true; + } + return false; + } + + void RecordFailureLocked(const std::shared_ptr& generation, + const std::shared_ptr& batch, std::exception_ptr failure) + { + if (!failure) + return; + if (batch && !batch->failure) + batch->failure = failure; + if (!generation->failure) + generation->failure = failure; + generation->accepting = false; + generation->cancelled = true; + } + + void SchedulePumpsLocked(const std::shared_ptr& generation) + { + u32 pumps_to_start = std::min(worker_limit - generation->active_pumps, generation->queued); + while (pumps_to_start--) + { + ++generation->active_pumps; + try + { + generation->pumps->run([this, generation] { Pump(generation); }); + } + catch (...) + { + --generation->active_pumps; + RecordFailureLocked(generation, nullptr, std::current_exception()); + generation->completed.notify_all(); + generation->pumps_idle.notify_all(); + break; + } + } + } + + bool SubmitImpl(const Batch& batch, NativeLoadPriority priority, std::function function, + std::function cancel) + { + const auto batch_state = batch.state; + if (!batch_state || !function || priority >= NativeLoadPriority::Count) + return false; + + std::lock_guard guard(mutex); + const auto& generation = batch_state->generation; + if (current_generation != generation || !generation->accepting || generation->cancelled || batch_state->closed) + return false; + + WorkItem work{generation, batch_state, std::move(function), std::move(cancel)}; + queues[static_cast(priority)].push_back(std::move(work)); + ++batch_state->pending; + ++generation->pending; + ++generation->queued; + SchedulePumpsLocked(generation); + return true; + } + + void CompleteWork(const WorkItem& work, std::exception_ptr failure) + { + std::lock_guard guard(mutex); + RecordFailureLocked(work.generation, work.batch, failure); + if (work.batch->pending) + --work.batch->pending; + if (work.generation->pending) + --work.generation->pending; + work.batch->completed.notify_all(); + work.generation->completed.notify_all(); + } + + void Execute(WorkItem& work) + { + bool execute = false; + { + std::lock_guard guard(mutex); + execute = current_generation == work.generation && !work.generation->cancelled; + } + + std::exception_ptr failure; + if (execute) + { + try + { + work.function(); + } + catch (...) + { + failure = std::current_exception(); + } + } + else if (work.cancel) + { + try + { + work.cancel(); + } + catch (...) + { + failure = std::current_exception(); + } + } + if (!execute && !failure) + { + std::lock_guard guard(mutex); + failure = work.generation->failure; + } + CompleteWork(work, failure); + } + + void Pump(const std::shared_ptr& generation) + { + for (;;) + { + WorkItem work; + { + std::lock_guard guard(mutex); + if (!PopWorkLocked(generation, work)) + { + --generation->active_pumps; + generation->pumps_idle.notify_all(); + return; + } + } + Execute(work); + } + } + + bool TryExecuteOne(const std::shared_ptr& generation) + { + WorkItem work; + { + std::lock_guard guard(mutex); + if (!PopWorkLocked(generation, work)) + return false; + } + Execute(work); + return true; + } + + bool TryExecuteOne(const std::shared_ptr& batch) + { + WorkItem work; + { + std::lock_guard guard(mutex); + if (!PopWorkLocked(batch, work)) + return false; + } + Execute(work); + return true; + } + + void WaitForGeneration(const std::shared_ptr& generation) + { + for (;;) + { + { + std::lock_guard guard(mutex); + if (!generation->pending) + break; + } + + if (TryExecuteOne(generation)) + continue; + + std::unique_lock guard(mutex); + generation->completed.wait(guard, [&generation] { return !generation->pending; }); + } + + std::unique_lock guard(mutex); + generation->pumps_idle.wait(guard, [&generation] { return !generation->active_pumps; }); + } + + void FinishGeneration(const std::shared_ptr& generation, bool cancel) + { + if (!cancel) + { + // Let every active producer finish while nested submissions are + // still legal. Closing first makes a late texture child look like a + // cancelled load even though normal finalization was requested. + WaitForGeneration(generation); + } + + { + std::lock_guard guard(mutex); + generation->accepting = false; + generation->cancelled = generation->cancelled || cancel; + SchedulePumpsLocked(generation); + } + + // A non-executor producer may have raced the quiescent check but + // submitted before accepting was cleared. Drain that final work too. + WaitForGeneration(generation); + + std::exception_ptr failure; + try + { + generation->pumps->wait(); + } + catch (...) + { + failure = std::current_exception(); + } + + { + std::lock_guard guard(mutex); + if (!failure) + failure = generation->failure; + if (current_generation == generation) + current_generation.reset(); + } + + if (!cancel && failure) + std::rethrow_exception(failure); + } +}; diff --git a/src/xrEngine/Environment.cpp b/src/xrEngine/Environment.cpp index eb440da27d..0a90676845 100644 --- a/src/xrEngine/Environment.cpp +++ b/src/xrEngine/Environment.cpp @@ -102,96 +102,49 @@ CEnvironment::CEnvironment() : // tsky0 = Device.Resources->_CreateTexture("$user$sky0"); // tsky1 = Device.Resources->_CreateTexture("$user$sky1"); - string_path file_name; - m_ambients_config = - xr_new( - FS.update_path( - file_name, - "$game_config$", - "environment\\ambients.ltx" - ), - TRUE, - TRUE, - FALSE - ); - m_sound_channels_config = - xr_new( - FS.update_path( - file_name, - "$game_config$", - "environment\\sound_channels.ltx" - ), - TRUE, - TRUE, - FALSE - ); - m_effects_config = - xr_new( - FS.update_path( - file_name, - "$game_config$", - "environment\\effects.ltx" - ), - TRUE, - TRUE, - FALSE - ); - m_suns_config = - xr_new( - FS.update_path( - file_name, - "$game_config$", - "environment\\suns.ltx" - ), - TRUE, - TRUE, - FALSE - ); - m_sun_pos_config = - xr_new( - FS.update_path( - file_name, - "$game_config$", - "environment\\sun_positions.ltx" - ), - TRUE, - TRUE, - FALSE - ); - m_thunderbolt_collections_config = - xr_new( - FS.update_path( - file_name, - "$game_config$", - "environment\\thunderbolt_collections.ltx" - ), - TRUE, - TRUE, - FALSE - ); - m_thunderbolts_config = - xr_new( - FS.update_path( - file_name, - "$game_config$", - "environment\\thunderbolts.ltx" - ), - TRUE, - TRUE, - FALSE - ); - - CInifile* config = - xr_new( - FS.update_path( - file_name, - "$game_config$", - "environment\\environment.ltx" - ), - TRUE, - TRUE, - FALSE - ); + CTimer configTimer; + configTimer.Start(); + static LPCSTR configNames[] = { + "environment\\ambients.ltx", + "environment\\sound_channels.ltx", + "environment\\effects.ltx", + "environment\\suns.ltx", + "environment\\sun_positions.ltx", + "environment\\thunderbolt_collections.ltx", + "environment\\thunderbolts.ltx", + "environment\\environment.ltx" + }; + CInifile* configs[std::size(configNames)] = {}; + xr_task_group configTasks; + for (u32 index = 0; index < std::size(configNames); ++index) + { + configTasks.run([&, index]() + { + string_path fileName; + configs[index] = xr_new( + FS.update_path(fileName, "$game_config$", configNames[index]), TRUE, TRUE, FALSE); + }); + } + try + { + configTasks.wait(); + } + catch (...) + { + for (CInifile*& config : configs) + xr_delete(config); + throw; + } + + m_ambients_config = configs[0]; + m_sound_channels_config = configs[1]; + m_effects_config = configs[2]; + m_suns_config = configs[3]; + m_sun_pos_config = configs[4]; + m_thunderbolt_collections_config = configs[5]; + m_thunderbolts_config = configs[6]; + CInifile* config = configs[7]; + Msg("* [STARTUP/ENV] configs=%u ms", configTimer.GetElapsed_ms()); // params p_var_alt = deg2rad(config->r_float("environment", "altitude")); p_var_long = deg2rad(config->r_float("environment", "delta_longitude")); diff --git a/src/xrEngine/Environment.h b/src/xrEngine/Environment.h index 18acf37254..5f80f6ae8b 100644 --- a/src/xrEngine/Environment.h +++ b/src/xrEngine/Environment.h @@ -353,6 +353,8 @@ class ENGINE_API CEnvironment void mods_load(); void mods_unload(); + static void PrepareLevelModifiers(LPCSTR canonical_level_path, xr_vector& result); + void CommitLevelModifiers(xr_vector& prepared); void OnFrame(); void lerp(float& current_weight); diff --git a/src/xrEngine/Environment_misc.cpp b/src/xrEngine/Environment_misc.cpp index 5d5b47735b..b4ac0e1105 100644 --- a/src/xrEngine/Environment_misc.cpp +++ b/src/xrEngine/Environment_misc.cpp @@ -609,11 +609,22 @@ CEnvAmbient* CEnvironment::AppendEnvAmb(const shared_str& sect) void CEnvironment::mods_load() { - Modifiers.clear_and_free(); - string_path path; - if (FS.exist(path, "$level$", "level.env_mod")) + xr_vector prepared; + PrepareLevelModifiers(FS.get_path("$level$")->m_Path, prepared); + CommitLevelModifiers(prepared); +} + +void CEnvironment::PrepareLevelModifiers(LPCSTR canonical_level_path, xr_vector& result) +{ + result.clear_and_free(); + xr_string path = canonical_level_path ? canonical_level_path : ""; + if (!path.empty() && path.back() != '\\' && path.back() != '/') + path += '\\'; + path += "level.env_mod"; + if (FS.exist(path.c_str())) { - IReader* fs = FS.r_open(path); + IReader* fs = FS.r_open(path.c_str()); + R_ASSERT3(fs, "Cannot open level environment modifiers", path.c_str()); u32 id = 0; u32 ver = 0x0015; u32 sz; @@ -628,13 +639,18 @@ void CEnvironment::mods_load() { CEnvModifier E; E.load(fs, ver); - Modifiers.push_back(E); + result.push_back(E); } id++; } FS.r_close(fs); } +} +void CEnvironment::CommitLevelModifiers(xr_vector& prepared) +{ + Modifiers.swap(prepared); + prepared.clear_and_free(); load_level_specific_ambients(); } @@ -856,6 +872,12 @@ void CEnvironment::load_weather_effects() void CEnvironment::load() { + CTimer startupTimer; + startupTimer.Start(); + u32 soundPrepareMs = 0; + u32 weatherMs = 0; + u32 effectsMs = 0; + if (!CurrentEnv) create_mixer(); @@ -865,9 +887,45 @@ void CEnvironment::load() if (!eff_LensFlare) eff_LensFlare = xr_new(); if (!eff_Thunderbolt) eff_Thunderbolt = xr_new(); + if (Sound) + { + xr_vector sounds; + auto collect_list = [&sounds](CInifile* config, LPCSTR key) + { + string_path sound; + for (const auto& section : config->sections()) + { + if (!config->line_exist(section.Name, key)) + continue; + LPCSTR values = config->r_string(section.Name, key); + const u32 count = _GetItemCount(values); + for (u32 index = 0; index < count; ++index) + sounds.emplace_back(_GetItem(values, index, sound)); + } + }; + auto collect_values = [&sounds](CInifile* config, LPCSTR key) + { + for (const auto& section : config->sections()) + if (config->line_exist(section.Name, key)) + sounds.emplace_back(config->r_string(section.Name, key)); + }; + collect_list(m_sound_channels_config, "sounds"); + collect_values(m_effects_config, "sound"); + collect_values(m_thunderbolts_config, "sound"); + std::sort(sounds.begin(), sounds.end()); + sounds.erase(std::unique(sounds.begin(), sounds.end()), sounds.end()); + Sound->source_prefetch_prepare(sounds); + } + soundPrepareMs = startupTimer.GetElapsed_ms(); + load_weathers(); + weatherMs = startupTimer.GetElapsed_ms() - soundPrepareMs; load_weather_effects(); + effectsMs = startupTimer.GetElapsed_ms() - soundPrepareMs - weatherMs; load_sun(); + Msg("* [STARTUP/ENV LOAD] sounds=%u weather=%u effects=%u sun=%u total=%u ms", + soundPrepareMs, weatherMs, effectsMs, + startupTimer.GetElapsed_ms() - soundPrepareMs - weatherMs - effectsMs, startupTimer.GetElapsed_ms()); } void CEnvironment::unload() diff --git a/src/xrEngine/GameMtlLib.cpp b/src/xrEngine/GameMtlLib.cpp index d800d17a03..2f47cc827f 100644 --- a/src/xrEngine/GameMtlLib.cpp +++ b/src/xrEngine/GameMtlLib.cpp @@ -7,6 +7,22 @@ #include "../xrCore/mezz_stringbuffer.h" +namespace +{ +xr_string NormalizeMaterialName(LPCSTR name) +{ + xr_string result = name ? name : ""; + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char character) { return static_cast(tolower(character)); }); + return result; +} + +u64 MaterialPairKey(int first, int second) +{ + return (u64(static_cast(first)) << 32) | static_cast(second); +} +} + CGameMtlLibrary GMLib; //CSound_manager_interface* Sound = NULL; #ifdef _EDITOR @@ -69,6 +85,14 @@ void SGameMtl::Load(IReader& fs) void CGameMtlLibrary::Load() { + CTimer loadTimer; + loadTimer.Start(); + u32 baseMaterialsMs = 0; + u32 materialOverridesMs = 0; + u32 basePairsMs = 0; + u32 pairOverridesMs = 0; + u32 soundResourcesMs = 0; + string_path name; if (!FS.exist(name, _game_data_, GAMEMTL_FILENAME)) { @@ -110,41 +134,49 @@ void CGameMtlLibrary::Load() } OBJ->close(); } + baseMaterialsMs = loadTimer.GetElapsed_ms(); + + xr_unordered_map materialByName; + xr_unordered_map materialIndexById; + materialByName.reserve(materials.size()); + materialIndexById.reserve(materials.size()); + int biggestMaterialId = -1; + for (u16 index = 0; index < materials.size(); ++index) + { + SGameMtl* material = materials[index]; + materialByName.emplace(NormalizeMaterialName(material->m_Name.c_str()), material); + materialIndexById.emplace(material->ID, index); + biggestMaterialId = std::max(biggestMaterialId, material->ID); + } // demonized: loose gamemtl.xr loading string_path materialsLtxName; - const int biggestIdStart = -1; if (FS.exist(materialsLtxName, _game_data_, "materials\\materials", ".ltx")) { #ifdef DEBUG_PRINT_MATERIAL Msg("found materials.ltx file %s", materialsLtxName); #endif - int biggestId = biggestIdStart; + u32 addedMaterials = 0; + u32 changedMaterials = 0; auto materialsLtx = xr_new(materialsLtxName, TRUE); for (const auto& sec : materialsLtx->sections()) { SGameMtl* M; - auto material = std::find_if(materials.begin(), materials.end(), [&sec](const SGameMtl* m) { - return xr_strcmp(m->m_Name, sec.Name) == 0; + const auto material = std::find_if(materials.begin(), materials.end(), [&sec](const SGameMtl* item) + { + return xr_strcmp(item->m_Name, sec.Name) == 0; }); if (material == materials.end()) { M = xr_new(); - if (biggestId == biggestIdStart) { - for (const auto& m : materials) { - if (m->ID > biggestId) { - biggestId = m->ID; - } - } - } - M->ID = ++biggestId; + M->ID = ++biggestMaterialId; M->m_Name = sec.Name; + materialIndexById.emplace(M->ID, static_cast(materials.size())); materials.push_back(M); - - Msg("[materials.ltx] Adding new material %s, id %d", M->m_Name.c_str(), M->ID); + materialByName.emplace(NormalizeMaterialName(M->m_Name.c_str()), M); + ++addedMaterials; } else { M = *material; - - Msg("[materials.ltx] Changing existing material %s, id %d", M->m_Name.c_str(), M->ID); + ++changedMaterials; } if (materialsLtx->line_exist(M->m_Name, "desc")) M->m_Desc = materialsLtx->r_string(M->m_Name, "desc"); @@ -181,7 +213,9 @@ void CGameMtlLibrary::Load() if (materialsLtx->line_exist(M->m_Name, "density_factor")) M->fDensityFactor = materialsLtx->r_float(M->m_Name, "density_factor"); } xr_delete(materialsLtx); + Msg("* [materials.ltx] applied: added=%u changed=%u", addedMaterials, changedMaterials); } + materialOverridesMs = loadTimer.GetElapsed_ms() - baseMaterialsMs; #ifdef DEBUG_PRINT_MATERIAL for (const auto& mat : materials) { @@ -232,6 +266,16 @@ void CGameMtlLibrary::Load() } OBJ->close(); } + basePairsMs = loadTimer.GetElapsed_ms() - baseMaterialsMs - materialOverridesMs; + + xr_unordered_map pairByMaterials; + pairByMaterials.reserve(material_pairs.size()); + int biggestPairId = -1; + for (SGameMtlPair* pair : material_pairs) + { + pairByMaterials.emplace(MaterialPairKey(pair->GetMtl0(), pair->GetMtl1()), pair); + biggestPairId = std::max(biggestPairId, pair->ID); + } string_path materialPairsLtxName; if (FS.exist(materialPairsLtxName, _game_data_, "materials\\material_pairs", ".ltx")) @@ -239,72 +283,62 @@ void CGameMtlLibrary::Load() #ifdef DEBUG_PRINT_MATERIAL Msg("found material_pairs.ltx file %s", materialPairsLtxName); #endif - int biggestId = biggestIdStart; + u32 addedPairs = 0; + u32 changedPairs = 0; auto materialsLtx = xr_new(materialPairsLtxName, TRUE); for (const auto& sec : materialsLtx->sections()) { SGameMtlPair* M; xr_string secStr = sec.Name.c_str(); - auto materials = secStr.SplitStringMulti("@", false, true); - if (materials.size() < 2) { + auto pairNames = secStr.SplitStringMulti("@", false, true); + if (pairNames.size() < 2) { Msg("![material_pairs.ltx] encountered wrongly defined pair %s, two materials are required", secStr.c_str()); continue; } - int m1 = GetMaterialID(materials[0].c_str()); - int m2 = GetMaterialID(materials[1].c_str()); + const auto firstMaterial = materialByName.find(NormalizeMaterialName(pairNames[0].c_str())); + const auto secondMaterial = materialByName.find(NormalizeMaterialName(pairNames[1].c_str())); + const int m1 = firstMaterial == materialByName.end() ? GAMEMTL_NONE_ID : firstMaterial->second->ID; + const int m2 = secondMaterial == materialByName.end() ? GAMEMTL_NONE_ID : secondMaterial->second->ID; if (m1 == GAMEMTL_NONE_ID) { - Msg("![material_pairs.ltx] encountered unknown material %s in string %s, skip", materials[0].c_str(), secStr.c_str()); + Msg("![material_pairs.ltx] encountered unknown material %s in string %s, skip", pairNames[0].c_str(), secStr.c_str()); continue; } if (m2 == GAMEMTL_NONE_ID) { - Msg("![material_pairs.ltx] encountered unknown material %s in string %s, skip", materials[1].c_str(), secStr.c_str()); + Msg("![material_pairs.ltx] encountered unknown material %s in string %s, skip", pairNames[1].c_str(), secStr.c_str()); continue; } - auto material = std::find_if(material_pairs.begin(), material_pairs.end(), [&m1, &m2](SGameMtlPair* m) { - return m1 == m->GetMtl0() && m2 == m->GetMtl1(); - }); - - if (material == material_pairs.end()) { + const u64 pairKey = MaterialPairKey(m1, m2); + const auto material = pairByMaterials.find(pairKey); + if (material == pairByMaterials.end()) { M = xr_new(this); - if (biggestId == biggestIdStart) { - for (const auto& m : material_pairs) { - if (m->ID > biggestId) { - biggestId = m->ID; - } - } - } - M->ID = ++biggestId; + M->ID = ++biggestPairId; M->ID_parent = -1; M->SetPair(m1, m2); material_pairs.push_back(M); - - Msg("[material_pairs.ltx] Adding new material pair %s | %s, id %d", GetMaterialByID(M->GetMtl0())->m_Name.c_str(), GetMaterialByID(M->GetMtl1())->m_Name.c_str(), M->ID); + pairByMaterials.emplace(pairKey, M); + ++addedPairs; } else { - M = *material; - - Msg("[material_pairs.ltx] Changing existing material pair %s | %s, id %d", GetMaterialByID(M->GetMtl0())->m_Name.c_str(), GetMaterialByID(M->GetMtl1())->m_Name.c_str(), M->ID); + M = material->second; + ++changedPairs; } if (materialsLtx->line_exist(sec.Name, "breaking_sounds")) { auto s = materialsLtx->r_string(sec.Name, "breaking_sounds"); M->BreakingSoundsStr = s ? s : ""; M->OwnProps.set(SGameMtlPair::flBreakingSounds, 1); - M->CreateSoundsImpl(M->BreakingSounds, s); } if (materialsLtx->line_exist(sec.Name, "step_sounds")) { auto s = materialsLtx->r_string(sec.Name, "step_sounds"); M->StepSoundsStr = s ? s : ""; M->OwnProps.set(SGameMtlPair::flStepSounds, 1); - M->CreateSoundsImpl(M->StepSounds, s); } if (materialsLtx->line_exist(sec.Name, "collide_sounds")) { auto s = materialsLtx->r_string(sec.Name, "collide_sounds"); M->CollideSoundsStr = s ? s : ""; M->OwnProps.set(SGameMtlPair::flCollideSounds, 1); - M->CreateSoundsImpl(M->CollideSounds, s); } if (materialsLtx->line_exist(sec.Name, "collide_particles")) { auto s = materialsLtx->r_string(sec.Name, "collide_particles"); @@ -320,7 +354,10 @@ void CGameMtlLibrary::Load() } } xr_delete(materialsLtx); + Msg("* [material_pairs.ltx] applied: added=%u changed=%u", addedPairs, changedPairs); } + pairOverridesMs = loadTimer.GetElapsed_ms() - + baseMaterialsMs - materialOverridesMs - basePairsMs; #ifdef DEBUG_PRINT_MATERIAL for (const auto& mat : material_pairs) { @@ -344,13 +381,29 @@ void CGameMtlLibrary::Load() #endif // DEBUG_PRINT_MATERIAL #ifndef _EDITOR + xr_vector materialSoundNames; + for (const SGameMtlPair* pair : material_pairs) + pair->CollectSoundNames(materialSoundNames); + std::sort(materialSoundNames.begin(), materialSoundNames.end()); + materialSoundNames.erase(std::unique(materialSoundNames.begin(), materialSoundNames.end()), materialSoundNames.end()); + if (Sound) + Sound->source_prefetch_prepare(materialSoundNames); + for (SGameMtlPair* pair : material_pairs) + pair->CreateSoundResources(); + soundResourcesMs = loadTimer.GetElapsed_ms() - + baseMaterialsMs - materialOverridesMs - basePairsMs - pairOverridesMs; + material_count = (u32)materials.size(); material_pairs_rt.resize(material_count * material_count, 0); for (GameMtlPairIt p_it = material_pairs.begin(); material_pairs.end() != p_it; ++p_it) { SGameMtlPair* S = *p_it; - int idx0 = GetMaterialIdx(S->mtl0) * material_count + GetMaterialIdx(S->mtl1); - int idx1 = GetMaterialIdx(S->mtl1) * material_count + GetMaterialIdx(S->mtl0); + const auto first = materialIndexById.find(S->mtl0); + const auto second = materialIndexById.find(S->mtl1); + VERIFY(first != materialIndexById.end()); + VERIFY(second != materialIndexById.end()); + int idx0 = first->second * material_count + second->second; + int idx1 = second->second * material_count + first->second; material_pairs_rt[idx0] = S; material_pairs_rt[idx1] = S; } @@ -365,6 +418,8 @@ void CGameMtlLibrary::Load() } */ FS.r_close(F); + Msg("* [STARTUP/MATERIALS] base=%u overrides=%u pairs=%u pair-overrides=%u sounds=%u total=%u ms", + baseMaterialsMs, materialOverridesMs, basePairsMs, pairOverridesMs, soundResourcesMs, loadTimer.GetElapsed_ms()); } #ifdef GM_NON_GAME diff --git a/src/xrEngine/GameMtlLib.h b/src/xrEngine/GameMtlLib.h index d1a29f8058..33a4c6c0b6 100644 --- a/src/xrEngine/GameMtlLib.h +++ b/src/xrEngine/GameMtlLib.h @@ -193,6 +193,8 @@ struct MTL_EXPORT_API SGameMtlPair void CreateSoundsImpl(SoundVec& sounds, LPCSTR str); void CreateParticlesImpl(PSVec& particles, LPCSTR str); void CreateMarksImpl(IWallMarkArray* marks, LPCSTR str); + void CollectSoundNames(xr_vector& sounds) const; + void CreateSoundResources(); #ifdef GM_NON_GAME diff --git a/src/xrEngine/GameMtlLib_Engine.cpp b/src/xrEngine/GameMtlLib_Engine.cpp index 4a07b01760..68048f05a1 100644 --- a/src/xrEngine/GameMtlLib_Engine.cpp +++ b/src/xrEngine/GameMtlLib_Engine.cpp @@ -24,7 +24,7 @@ void DestroyPSs(PSVec& lst) // Device.Resources->Delete(*it); } -void CreateSounds(SoundVec& lst, LPCSTR buf) +void CollectSoundNames(xr_vector& sounds, LPCSTR buf) { string128 tmp; int cnt = _GetItemCount(buf); @@ -45,21 +45,32 @@ void CreateSounds(SoundVec& lst, LPCSTR buf) string128 name; xr_strcpy(name, sizeof(name), (*it).name.c_str()); *strext(name) = 0; - - ref_sound snd; - snd.create(name, st_Effect, sg_SourceType); - lst.push_back(snd); + xr_strlwr(name); + sounds.emplace_back(name); } } else { - ref_sound snd; - snd.create(tmp, st_Effect, sg_SourceType); - lst.push_back(snd); + if (strext(tmp)) + *strext(tmp) = 0; + xr_strlwr(tmp); + sounds.emplace_back(tmp); } } } +void CreateSounds(SoundVec& lst, LPCSTR buf) +{ + xr_vector names; + CollectSoundNames(names, buf); + for (const xr_string& name : names) + { + ref_sound snd; + snd.create(name.c_str(), st_Effect, sg_SourceType); + lst.push_back(snd); + } +} + /* void CreateMarks(ShaderVec& lst, LPCSTR buf) { @@ -118,17 +129,14 @@ void SGameMtlPair::Load(IReader& fs) R_ASSERT(fs.find_chunk(GAMEMTLPAIR_CHUNK_BREAKING)); fs.r_stringZ(buf); BreakingSoundsStr = buf.c_str(); - CreateSounds(BreakingSounds, *buf); R_ASSERT(fs.find_chunk(GAMEMTLPAIR_CHUNK_STEP)); fs.r_stringZ(buf); StepSoundsStr = buf.c_str(); - CreateSounds(StepSounds, *buf); R_ASSERT(fs.find_chunk(GAMEMTLPAIR_CHUNK_COLLIDE)); fs.r_stringZ(buf); CollideSoundsStr = buf.c_str(); - CreateSounds(CollideSounds, *buf); fs.r_stringZ(buf); CollideParticlesStr = buf.c_str(); CreatePSs(CollideParticles, *buf); @@ -152,3 +160,17 @@ void SGameMtlPair::CreateMarksImpl(IWallMarkArray* marks, LPCSTR str) { marks->clear(); CreateMarks(marks, str); } + +void SGameMtlPair::CollectSoundNames(xr_vector& sounds) const +{ + ::CollectSoundNames(sounds, BreakingSoundsStr.c_str()); + ::CollectSoundNames(sounds, StepSoundsStr.c_str()); + ::CollectSoundNames(sounds, CollideSoundsStr.c_str()); +} + +void SGameMtlPair::CreateSoundResources() +{ + CreateSoundsImpl(BreakingSounds, BreakingSoundsStr.c_str()); + CreateSoundsImpl(StepSounds, StepSoundsStr.c_str()); + CreateSoundsImpl(CollideSounds, CollideSoundsStr.c_str()); +} diff --git a/src/xrEngine/IGame_Level.cpp b/src/xrEngine/IGame_Level.cpp index 6747b357f0..282a279c53 100644 --- a/src/xrEngine/IGame_Level.cpp +++ b/src/xrEngine/IGame_Level.cpp @@ -65,6 +65,9 @@ void IGame_Level::net_Stop() { for (int i = 0; i < 6; i++) { + if (Objects.o_count() == 0 && Objects.destroy_queues_empty()) + break; + Objects.Update(false); Objects.ProcessDestroyQueue(); } @@ -92,10 +95,10 @@ xrCriticalSection lloadcs; bool IGame_Level::Load(u32 dwNum) { PROF_EVENT("IGame_Level::Load"); + CTimer level_timer; + level_timer.Start(); xrCriticalSectionGuard guard(&lloadcs); if (bReady) return TRUE; - extern xr_task_group prefetch_task; - prefetch_task.wait(); //SECUROM_MARKER_PERFORMANCE_ON(10) // Initialize level data @@ -116,29 +119,111 @@ bool IGame_Level::Load(u32 dwNum) fs.r_chunk_safe(fsL_HEADER, &H, sizeof(H)); R_ASSERT2(XRCL_PRODUCTION_VERSION == H.XRLC_version, "Incompatible level version."); - // CForms - // g_pGamePersistent->LoadTitle ("st_loading_cform"); + // HUD + Environment + if (!g_hud) + g_hud = (CCustomHUD*)NEW_INSTANCE(CLSID_HUDMANAGER); + + if (!Load_Prepared_Environment()) + g_pGamePersistent->Environment().mods_load(); g_pGamePersistent->LoadTitle(); - ObjectSpace.Load( [](Fvector* V, int Vcnt, CDB::TRI* T, int Tcnt, void* params){g_pGameLevel->Load_GameSpecific_CFORM(T, Tcnt);}); - //Sound->set_geometry_occ ( &Static ); - Sound->set_geometry_occ(ObjectSpace.GetStaticModel()); - Sound->set_handler(_sound_event); - pApp->LoadSwitch(); + // CFORM and game-specific navigation data use independent level files and + // publish to separate subsystems, so overlap them with renderer loading. + NativeLoadExecutor& load_executor = NativeLoadExecutor::Instance(); + NativeLoadExecutor::Batch level_load_batch = load_executor.BeginBatch(load_executor.CurrentGeneration()); + xr_task_group fallback_level_tasks; + auto submit_level_task = [&load_executor, &level_load_batch, &fallback_level_tasks]( + NativeLoadPriority priority, auto&& work) + { + if (level_load_batch.Valid()) + load_executor.Submit(level_load_batch, priority, std::forward(work)); + else + fallback_level_tasks.run(std::forward(work)); + }; + ObjectSpace.Load([](Fvector* V, int Vcnt, CDB::TRI* T, int Tcnt, void* params) + { + g_pGameLevel->Load_GameSpecific_CFORM(T, Tcnt); + }); + submit_level_task(NativeLoadPriority::Geometry, [this]() + { + CTimer timer; + timer.Start(); + ObjectSpace.GetStaticModel()->syncronize(); + Msg("* [LEVEL LOAD] CFORM: %d ms", timer.GetElapsed_ms()); + }); + bool level_tasks_drained = false; + auto drain_level_tasks = [&]() + { + if (level_tasks_drained) + return; + std::exception_ptr failure; + try + { + if (level_load_batch.Valid()) + load_executor.Wait(level_load_batch); + } + catch (...) + { + failure = std::current_exception(); + } + try + { + fallback_level_tasks.wait(); + } + catch (...) + { + if (!failure) + failure = std::current_exception(); + } + level_tasks_drained = true; + if (failure) + std::rethrow_exception(failure); + }; + struct level_task_drain_guard + { + std::function drain; + ~level_task_drain_guard() + { + try { drain(); } catch (...) {} + } + } task_drain_guard{drain_level_tasks}; + CTimer game_specific_before_timer; + game_specific_before_timer.Start(); + R_ASSERT(Load_GameSpecific_Before()); + Msg("* [LEVEL LOAD] game-specific before: %d ms", game_specific_before_timer.GetElapsed_ms()); + pApp->LoadSwitch(); - // HUD + Environment - if (!g_hud) - g_hud = (CCustomHUD*)NEW_INSTANCE(CLSID_HUDMANAGER); + // R4 internally submits immutable prepare work to NativeLoadExecutor. Keep + // the orchestration itself on the render owner because LoadTitle and the + // prepared registry commits touch the loading screen/immediate context. + CTimer render_timer; + render_timer.Start(); + Render->level_BeginAsyncLoad(); + try + { + Render->level_Load(LL_Stream); + } + catch (...) + { + const std::exception_ptr failure = std::current_exception(); + Render->level_AbortAsyncLoad(); + try { drain_level_tasks(); } catch (...) {} + FS.r_close(LL_Stream); + std::rethrow_exception(failure); + } + Msg("* [LEVEL LOAD] renderer: %d ms", render_timer.GetElapsed_ms()); + CTimer barrier_timer; + barrier_timer.Start(); + drain_level_tasks(); + Msg("* [LEVEL LOAD] CFORM/AI barrier: %d ms", barrier_timer.GetElapsed_ms()); - // Render-level Load - Render->level_Load(LL_Stream); + Sound->set_geometry_occ(ObjectSpace.GetStaticModel()); + Sound->set_handler(_sound_event); // tscreate.FrameEnd (); // Msg ("* S-CREATE: %f ms, %d times",tscreate.result,tscreate.count); // Objects - g_pGamePersistent->Environment().mods_load(); - R_ASSERT(Load_GameSpecific_Before()); Objects.Load(); //. ANDY R_ASSERT (Load_GameSpecific_After ()); @@ -151,6 +236,7 @@ bool IGame_Level::Load(u32 dwNum) #endif Device.seqFrame.Add(this); + Msg("* [LEVEL LOAD] total: %d ms", level_timer.GetElapsed_ms()); //SECUROM_MARKER_PERFORMANCE_OFF(10) @@ -179,14 +265,25 @@ void IGame_Level::OnRender() // Level render, only when no client output required if (!g_dedicated_server) { + const bool measure_precache = pApp && pApp->LoadSessionMeasurePrecache(); + u64 calculate_ticks = 0; + u64 render_ticks = 0; { PROF_EVENT("IGame_Level::OnRender: Calculate"); + const u64 started_at = measure_precache ? CPU::QPC() : 0; Render->Calculate(); + if (measure_precache) + calculate_ticks = CPU::QPC() - started_at; } { PROF_EVENT("IGame_Level::OnRender: Render"); + const u64 started_at = measure_precache ? CPU::QPC() : 0; Render->Render(); + if (measure_precache) + render_ticks = CPU::QPC() - started_at; } + if (measure_precache) + pApp->LoadSessionRecordPrecacheLevel(calculate_ticks, render_ticks); } else { diff --git a/src/xrEngine/IGame_Level.h b/src/xrEngine/IGame_Level.h index cc8bcbae3f..9b6dfe16af 100644 --- a/src/xrEngine/IGame_Level.h +++ b/src/xrEngine/IGame_Level.h @@ -110,6 +110,7 @@ class ENGINE_API IGame_Level : virtual bool Load(u32 dwNum); virtual bool Load_GameSpecific_Before() { return TRUE; }; // before object loading virtual bool Load_GameSpecific_After() { return TRUE; }; // after object loading + virtual bool Load_Prepared_Environment() { return false; } virtual void Load_GameSpecific_CFORM(CDB::TRI* T, u32 count) = 0; virtual void _BCL OnFrame(void); diff --git a/src/xrEngine/IGame_Persistent.cpp b/src/xrEngine/IGame_Persistent.cpp index 1ead707504..95ae66e4e9 100644 --- a/src/xrEngine/IGame_Persistent.cpp +++ b/src/xrEngine/IGame_Persistent.cpp @@ -60,6 +60,7 @@ IGame_Persistent::IGame_Persistent() IGame_Persistent::~IGame_Persistent() { + WaitGamePrefetch(); xr_delete(PerlinNoise1D); RDEVICE.seqFrame.Remove(this); RDEVICE.seqAppStart.Remove(this); @@ -86,10 +87,6 @@ void IGame_Persistent::OnAppDeactivate() void IGame_Persistent::OnAppStart() { -#ifndef _EDITOR - Environment().load(); -#endif - // Texture Prefetch Config string_path file_name; m_textures_prefetch_config = @@ -174,24 +171,71 @@ void IGame_Persistent::OnGameStart() } xr_task_group prefetch_task; +NativeLoadExecutor::Batch prefetch_batch; + +void WaitGamePrefetch() +{ + NativeLoadExecutor::Batch batch = prefetch_batch; + prefetch_batch = {}; + if (batch.Valid()) + NativeLoadExecutor::Instance().Wait(batch); + prefetch_task.wait(); +} + #ifndef _EDITOR void IGame_Persistent::Prefetch() { + WaitGamePrefetch(); Msg("* [x-ray]: Prefetching Data"); // prefetch game objects & models float p_time = 1000.f * Device.GetTimerGlobal()->GetElapsed_sec(); size_t mem_0 = Memory.mem_usage(); PROF_EVENT("Prefetch"); - static DWORD this_thread_id = 0; - this_thread_id = GetCurrentThreadId(); - prefetch_task.run([this]() + struct texture_folder + { + shared_str name; + bool recursive; + }; + xr_vector texture_folders; + xr_vector texture_names; + xr_vector object_visuals; + if (m_textures_prefetch_config->section_exist("prefetch_folders")) + { + const CInifile::Sect& section = m_textures_prefetch_config->r_section("prefetch_folders"); + texture_folders.reserve(section.Data.size()); + for (CInifile::SectCIt item = section.Data.begin(); item != section.Data.end(); ++item) + texture_folders.push_back({item->first, item->second.size() && !xr_strcmp(*item->second, "*")}); + } + if (m_textures_prefetch_config->section_exist("prefetch_textures")) + { + const CInifile::Sect& section = m_textures_prefetch_config->r_section("prefetch_textures"); + texture_names.reserve(section.Data.size()); + for (CInifile::SectCIt item = section.Data.begin(); item != section.Data.end(); ++item) + texture_names.push_back(item->first); + } + string256 object_section; + strconcat(sizeof(object_section), object_section, "prefetch_objects_", m_game_params.m_game_type); + if (pSettings->section_exist(object_section)) + { + const CInifile::Sect& section = pSettings->r_section(object_section); + for (CInifile::SectCIt item = section.Data.begin(); item != section.Data.end(); ++item) + if (pSettings->section_exist(item->first.c_str()) && + pSettings->line_exist(item->first.c_str(), "visual")) + object_visuals.push_back(pSettings->r_string(item->first.c_str(), "visual")); + } + + NativeLoadExecutor& executor = NativeLoadExecutor::Instance(); + prefetch_batch = executor.BeginBatch(executor.CurrentGeneration()); + auto submit = [&executor](NativeLoadPriority priority, auto&& work) + { + if (prefetch_batch.Valid()) + executor.Submit(prefetch_batch, priority, std::forward(work)); + else + prefetch_task.run(std::forward(work)); + }; + submit(NativeLoadPriority::ShaderTexture, [texture_folders, texture_names]() { - { - PROF_EVENT("Prefetch Loading models"); - Log("Loading models..."); - Render->models_Prefetch(); - } { PROF_EVENT("Loading textures"); Log("Loading textures..."); @@ -208,57 +252,46 @@ void IGame_Persistent::Prefetch() Device.m_pRender->ResourcesPrefetchCreateTexture(it->name.c_str()); }; - if (m_textures_prefetch_config->section_exist("prefetch_folders")) + for (const texture_folder& item : texture_folders) { - CInifile::Sect const& sect_f = m_textures_prefetch_config->r_section("prefetch_folders"); - for (CInifile::SectCIt I = sect_f.Data.begin(); I != sect_f.Data.end(); I++) + if (item.recursive) { - if (I->second.size() && !xr_strcmp(*I->second, "*")) + string_path folder; + FS.update_path(folder, "$game_textures$", item.name.c_str()); + xr_strcat(folder, sizeof(folder), "\\"); + xr_vector* subfolders = FS.file_list_open(folder, FS_ListFolders); + if (subfolders) { - string_path folder; - FS.update_path(folder, "$game_textures$", *I->first); - xr_strcat(folder, sizeof(folder), "\\"); - - xr_vector* subfolders = FS.file_list_open(folder, FS_ListFolders); - - if (subfolders == nullptr) - { - FS.file_list_close(subfolders); - continue; - } - for (LPSTR subfolder : *subfolders) { string_path path; strconcat(sizeof(path), path, folder, subfolder); - loadFileFolder(path); } - - FS.file_list_close(subfolders); } - - loadFileFolder(*I->first); + FS.file_list_close(subfolders); } + loadFileFolder(item.name.c_str()); } - if (m_textures_prefetch_config->section_exist("prefetch_textures")) - { - CInifile::Sect const& sect = m_textures_prefetch_config->r_section("prefetch_textures"); - for (CInifile::SectCIt I = sect.Data.begin(); I != sect.Data.end(); I++) - Device.m_pRender->ResourcesPrefetchCreateTexture(I->first.c_str()); - } - - Device.m_pRender->ResourcesDeferredUpload(); + for (const shared_str& texture : texture_names) + Device.m_pRender->ResourcesPrefetchCreateTexture(texture.c_str()); } }); - { - // prefetch game objects & models - PROF_EVENT("Loading objects"); - Log("Loading objects..."); - ObjectPool.prefetch(); - } - + for (const shared_str& visual : object_visuals) + submit(NativeLoadPriority::Spawn, [visual]() + { + xr_vector textures; + Render->model_CollectTextures(visual.c_str(), nullptr, textures); + for (const xr_string& texture : textures) + Device.m_pRender->ResourcesPrefetchCreateTexture(texture.c_str()); + }); + + // Model creation can enter renderer Lua shader lookup. Keep it on the + // original owner thread while pure texture discovery/loading overlaps it. + PROF_EVENT("Prefetch Loading models"); + Log("Loading models..."); + Render->models_Prefetch(); Msg("* [x-ray]: Prefetched Data"); p_time = 1000.f * Device.GetTimerGlobal()->GetElapsed_sec() - p_time; size_t p_mem = Memory.mem_usage() - mem_0; @@ -272,6 +305,7 @@ void IGame_Persistent::Prefetch() void IGame_Persistent::OnGameEnd() { #ifndef _EDITOR + WaitGamePrefetch(); ObjectPool.clear(); Render->models_Clear(TRUE); #endif @@ -338,6 +372,8 @@ void IGame_Persistent::destroy_particles(bool all_particles) void IGame_Persistent::OnAssetsChanged() { #ifndef _EDITOR + if (pApp && pApp->LoadSessionActive()) + pApp->LoadSessionCancel("assets changed"); Device.m_pRender->OnAssetsChanged(); //Resources->m_textures_description.Load(); #endif } diff --git a/src/xrEngine/IGame_Persistent.h b/src/xrEngine/IGame_Persistent.h index 6ea7422837..73cc860adc 100644 --- a/src/xrEngine/IGame_Persistent.h +++ b/src/xrEngine/IGame_Persistent.h @@ -16,6 +16,8 @@ class ScriptWallmarksManager; class ENGINE_API CPS_Instance; class script_attachment; +ENGINE_API void WaitGamePrefetch(); + //----------------------------------------------------------------------------------------------------------- class ENGINE_API IGame_Persistent : #ifndef _EDITOR diff --git a/src/xrEngine/Render.h b/src/xrEngine/Render.h index 5c09ba4062..9dfdae48b9 100644 --- a/src/xrEngine/Render.h +++ b/src/xrEngine/Render.h @@ -295,7 +295,21 @@ class ENGINE_API IRender_interface public: // options bool hud_loading; - s32 m_MSAASample; + class MSAASampleThreadLocal + { + static s32& value() + { + static thread_local s32 sample = -1; + return sample; + } + public: + operator s32() const { return value(); } + MSAASampleThreadLocal& operator=(s32 sample) + { + value() = sample; + return *this; + } + } m_MSAASample; BENCH_SEC_SCRAMBLEMEMBER1 @@ -319,6 +333,11 @@ class ENGINE_API IRender_interface virtual void level_Load(IReader*) = 0; virtual void level_Unload() = 0; + virtual bool level_StaticCacheReady(LPCSTR canonical_level_path) { return false; } + virtual void level_Prepare(LPCSTR canonical_level_path) {} + virtual void level_InvalidateStaticCache() {} + virtual void level_BeginAsyncLoad() {} + virtual void level_AbortAsyncLoad() {} virtual size_t SectorsCount() { return size_t(0); } @@ -398,6 +417,14 @@ class ENGINE_API IRender_interface virtual void model_Logging(BOOL bEnable) = 0; virtual void models_Prefetch() = 0; virtual void models_PrefetchOne(LPCSTR name, bool assert = true) = 0; + virtual void model_CollectTextures(LPCSTR name, LPCSTR canonical_level_path, + xr_vector& textures) {} + virtual bool models_PrefetchPrepared(LPCSTR name, LPCSTR canonical_level_path, bool assert = true) + { + models_PrefetchOne(name, assert); + return true; + } + virtual void models_InvalidatePrepared() {} virtual void models_Clear(BOOL b_complete) = 0; virtual bool models_Exists(LPCSTR name) = 0; diff --git a/src/xrEngine/Xr_input.cpp b/src/xrEngine/Xr_input.cpp index 261309bdbf..328d157845 100644 --- a/src/xrEngine/Xr_input.cpp +++ b/src/xrEngine/Xr_input.cpp @@ -3,6 +3,7 @@ #include "xr_input.h" #include "IInputReceiver.h" +#include "x_ray.h" //#include "../include/editor/ide.hpp" #ifndef _EDITOR @@ -242,7 +243,8 @@ void CInput::KeyUpdate() if (b_altF4) return; #ifndef _EDITOR - if (Device.dwPrecacheFrame == 0) + const bool dispatch_input = !Device.dwPrecacheFrame && !(pApp && pApp->LoadSessionActive()); + if (dispatch_input) #endif { for (u32 i = 0; i < dwElements; i++) @@ -449,8 +451,9 @@ void CInput::MouseUpdate() }; #ifndef _EDITOR - if (Device.dwPrecacheFrame) - return; + const bool dispatch_input = !Device.dwPrecacheFrame && !(pApp && pApp->LoadSessionActive()); +#else + const bool dispatch_input = true; #endif BOOL mouse_prev[COUNT_MOUSE_BUTTONS]; @@ -486,100 +489,105 @@ void CInput::MouseUpdate() if (od[i].dwData & 0x80) { mouseState[0] = TRUE; - cbStack.back()->IR_OnMousePress(bSwitched ? 1 : 0); + if (dispatch_input) cbStack.back()->IR_OnMousePress(bSwitched ? 1 : 0); } if (!(od[i].dwData & 0x80)) { mouseState[0] = FALSE; - cbStack.back()->IR_OnMouseRelease(bSwitched ? 1 : 0); + if (dispatch_input) cbStack.back()->IR_OnMouseRelease(bSwitched ? 1 : 0); } break; case DIMOFS_BUTTON1: if (od[i].dwData & 0x80) { mouseState[1] = TRUE; - cbStack.back()->IR_OnMousePress(bSwitched ? 0 : 1); + if (dispatch_input) cbStack.back()->IR_OnMousePress(bSwitched ? 0 : 1); } if (!(od[i].dwData & 0x80)) { mouseState[1] = FALSE; - cbStack.back()->IR_OnMouseRelease(bSwitched ? 0 : 1); + if (dispatch_input) cbStack.back()->IR_OnMouseRelease(bSwitched ? 0 : 1); } break; case DIMOFS_BUTTON2: if (od[i].dwData & 0x80) { mouseState[2] = TRUE; - cbStack.back()->IR_OnMousePress(2); + if (dispatch_input) cbStack.back()->IR_OnMousePress(2); } if (!(od[i].dwData & 0x80)) { mouseState[2] = FALSE; - cbStack.back()->IR_OnMouseRelease(2); + if (dispatch_input) cbStack.back()->IR_OnMouseRelease(2); } break; case DIMOFS_BUTTON3: if (od[i].dwData & 0x80) { mouseState[3] = TRUE; - cbStack.back()->IR_OnMousePress(3); + if (dispatch_input) cbStack.back()->IR_OnMousePress(3); } if (!(od[i].dwData & 0x80)) { mouseState[3] = FALSE; - cbStack.back()->IR_OnMouseRelease(3); + if (dispatch_input) cbStack.back()->IR_OnMouseRelease(3); } break; case DIMOFS_BUTTON4: if (od[i].dwData & 0x80) { mouseState[4] = TRUE; - cbStack.back()->IR_OnMousePress(4); + if (dispatch_input) cbStack.back()->IR_OnMousePress(4); } if (!(od[i].dwData & 0x80)) { mouseState[4] = FALSE; - cbStack.back()->IR_OnMouseRelease(4); + if (dispatch_input) cbStack.back()->IR_OnMouseRelease(4); } break; case DIMOFS_BUTTON5: if (od[i].dwData & 0x80) { mouseState[5] = TRUE; - cbStack.back()->IR_OnMousePress(5); + if (dispatch_input) cbStack.back()->IR_OnMousePress(5); } if (!(od[i].dwData & 0x80)) { mouseState[5] = FALSE; - cbStack.back()->IR_OnMouseRelease(5); + if (dispatch_input) cbStack.back()->IR_OnMouseRelease(5); } break; case DIMOFS_BUTTON6: if (od[i].dwData & 0x80) { mouseState[6] = TRUE; - cbStack.back()->IR_OnMousePress(6); + if (dispatch_input) cbStack.back()->IR_OnMousePress(6); } if (!(od[i].dwData & 0x80)) { mouseState[6] = FALSE; - cbStack.back()->IR_OnMouseRelease(6); + if (dispatch_input) cbStack.back()->IR_OnMouseRelease(6); } break; case DIMOFS_BUTTON7: if (od[i].dwData & 0x80) { mouseState[7] = TRUE; - cbStack.back()->IR_OnMousePress(7); + if (dispatch_input) cbStack.back()->IR_OnMousePress(7); } if (!(od[i].dwData & 0x80)) { mouseState[7] = FALSE; - cbStack.back()->IR_OnMouseRelease(7); + if (dispatch_input) cbStack.back()->IR_OnMouseRelease(7); } break; } } + if (!dispatch_input) + { + ZeroMemory(timeStamp, sizeof(timeStamp)); + return; + } if (mouseState[0] && mouse_prev[0]) { diff --git a/src/xrEngine/device.cpp b/src/xrEngine/device.cpp index b161e00a9e..62d969476a 100644 --- a/src/xrEngine/device.cpp +++ b/src/xrEngine/device.cpp @@ -114,7 +114,7 @@ void CRenderDevice::End(void) PROF_EVENT("Render: End"); #ifndef DEDICATED_SERVER - + const bool measure_precache = pApp && pApp->LoadSessionMeasurePrecache(); #ifdef INGAME_EDITOR bool load_finished = false; @@ -139,9 +139,11 @@ void CRenderDevice::End(void) } ::Sound->set_master_volume(1.f); - m_pRender->ResourcesDestroyNecessaryTextures(); - - Msg("* [x-ray]: Handled Necessary Textures Destruction"); + if (!pApp || !pApp->LoadSessionActive()) + { + m_pRender->ResourcesDestroyNecessaryTextures(); + Msg("* [x-ray]: Handled Necessary Textures Destruction"); + } Memory.mem_compact(); //Msg("* MEMORY USAGE: %lld K", Memory.mem_usage() / 1024); //Msg("* End of synchronization A[%d] R[%d]", b_is_Active, b_is_Ready); @@ -167,7 +169,10 @@ void CRenderDevice::End(void) // Present goes here, so call OA Frame end. if (g_SASH.IsBenchmarkRunning()) g_SASH.DisplayFrame(Device.fTimeGlobal); + const u64 present_started_at = measure_precache ? CPU::QPC() : 0; m_pRender->End(); + if (measure_precache) + pApp->LoadSessionRecordPrecachePresent(CPU::QPC() - present_started_at); # ifdef INGAME_EDITOR if (load_finished && m_editor) @@ -184,6 +189,8 @@ void CRenderDevice::PreCache(u32 amount, bool b_draw_loadscreen, bool b_wait_use if (m_pRender->GetForceGPU_REF()) amount = 0; #endif + if (pApp) + pApp->LoadSessionPrecacheBegin(); dwPrecacheFrame = dwPrecacheTotal = amount; if (amount && !precache_light && g_pGameLevel && g_loading_events.empty()) @@ -390,7 +397,15 @@ void CRenderDevice::on_idle() Device.seqParallelBeforRender.clear(); } + const bool precache_before_frame = pApp && pApp->LoadSessionMeasurePrecache(); + const u64 frame_started_at = precache_before_frame ? CPU::QPC() : 0; FrameMove(); + const bool measure_precache_frame = pApp && pApp->LoadSessionMeasurePrecache(); + const u64 frame_move_finished_at = measure_precache_frame ? CPU::QPC() : 0; + const u64 measured_frame_started_at = precache_before_frame ? frame_started_at : frame_move_finished_at; + const u64 frame_move_ticks = precache_before_frame ? frame_move_finished_at - frame_started_at : 0; + u64 seq_render_ticks = 0; + u64 end_ticks = 0; if (g_pGamePersistent != nullptr) { @@ -514,7 +529,10 @@ void CRenderDevice::on_idle() if (b_is_Active && Begin()) { START_PROFILE("Process seqRender"); + const u64 seq_render_started_at = measure_precache_frame ? CPU::QPC() : 0; seqRender.Process(rp_Render); + if (measure_precache_frame) + seq_render_ticks = CPU::QPC() - seq_render_started_at; STOP_PROFILE; if (psDeviceFlags.test(rsCameraPos) || psDeviceFlags.test(rsStatistic) || Statistic->errors.size()) @@ -523,7 +541,10 @@ void CRenderDevice::on_idle() Statistic->Show(); } + const u64 end_started_at = measure_precache_frame ? CPU::QPC() : 0; End(); + if (measure_precache_frame) + end_ticks = CPU::QPC() - end_started_at; } Statistic->RenderTOTAL_Real.End(); Statistic->RenderTOTAL_Real.FrameEnd(); @@ -531,7 +552,14 @@ void CRenderDevice::on_idle() #endif Device.isRendering = false; + const u64 secondary_wait_started_at = measure_precache_frame ? CPU::QPC() : 0; secondary_tasks.wait(); + if (measure_precache_frame) + { + const u64 frame_finished_at = CPU::QPC(); + pApp->LoadSessionRecordPrecacheFrame(frame_finished_at - measured_frame_started_at, + frame_move_ticks, seq_render_ticks, end_ticks, frame_finished_at - secondary_wait_started_at); + } if (psLua_ParallelGC_debug && psLua_ParallelGC && Device.LuaGCDebug) { @@ -635,7 +663,10 @@ void CRenderDevice::Run() thread_spawn(mt_DiscordThread, "X-RAY Discord thread", 0, 0); // Message cycle + CTimer app_start_timer; + app_start_timer.Start(); seqAppStart.Process(rp_AppStart); + Msg("* [STARTUP] app start callbacks: %d ms", app_start_timer.GetElapsed_ms()); //m_pRender->ClearTarget(); SetForegroundWindow(m_hWnd); @@ -902,7 +933,11 @@ void CLoadScreenRenderer::OnRender() { PROF_EVENT(); + const bool measure_precache = pApp && pApp->LoadSessionMeasurePrecache(); + const u64 started_at = measure_precache ? CPU::QPC() : 0; pApp->load_draw_internal(); + if (measure_precache) + pApp->LoadSessionRecordPrecacheLoadscreen(CPU::QPC() - started_at); } void CRenderDevice::CSecondVPParams::SetSVPActive(bool bState) //--#SM+#-- +SecondVP+ diff --git a/src/xrEngine/x_ray.cpp b/src/xrEngine/x_ray.cpp index 28b6491b4c..24cb9fb4a9 100644 --- a/src/xrEngine/x_ray.cpp +++ b/src/xrEngine/x_ray.cpp @@ -8,6 +8,7 @@ #include "stdafx.h" #include "igame_level.h" #include "igame_persistent.h" +#include "Render.h" #include "dedicated_server_only.h" #include "no_single.h" @@ -66,6 +67,20 @@ rpc_info discord_gameinfo; rpc_strings discord_strings; float discord_update_rate = .5f; +static ULONGLONG startup_begin_time; + +void LogStartupMenuReady() +{ + static bool logged = false; + if (!logged) + { + logged = true; + Msg("* [STARTUP] total to main menu: %llu ms", GetTickCount64() - startup_begin_time); + } + if (Sound && (!pApp || !pApp->LoadSessionActive())) + Sound->source_prefetch_start(); +} + //UTF-8 (ICU) #pragma comment(lib, "icuuc.lib") //#pragma comment(lib, "sicuuc.lib") @@ -202,14 +217,35 @@ extern float g_fTimeFactor; PROTECT_API void InitSettings() { PROF_EVENT("InitSettings"); - string_path fname; - FS.update_path(fname, "$game_config$", "system.ltx"); + string_path systemPath; + string_path gamePath; + FS.update_path(systemPath, "$game_config$", "system.ltx"); + FS.update_path(gamePath, "$game_config$", "game.ltx"); #ifdef DEBUG - Msg("Updated path to system.ltx is %s", fname); + Msg("Updated path to system.ltx is %s", systemPath); #endif // #ifdef DEBUG - pSettings = xr_new(fname, TRUE); + + CInifile* systemSettings = nullptr; + CInifile* gameSettings = nullptr; + xr_task_group settingsTasks; + settingsTasks.run([&]() { systemSettings = xr_new(systemPath, TRUE); }); + settingsTasks.run([&]() { gameSettings = xr_new(gamePath, TRUE); }); + try + { + settingsTasks.wait(); + } + catch (...) + { + xr_delete(systemSettings); + xr_delete(gameSettings); + throw; + } + pSettings = systemSettings; + pGameIni = gameSettings; CHECK_OR_EXIT(0 != pSettings->section_count(), - make_string("Cannot find file %s.\nReinstalling application may fix this problem.", fname)); + make_string("Cannot find file %s.\nReinstalling application may fix this problem.", systemPath)); + CHECK_OR_EXIT(0 != pGameIni->section_count(), + make_string("Cannot find file %s.\nReinstalling application may fix this problem.", gamePath)); xr_auth_strings_t tmp_ignore_pathes; xr_auth_strings_t tmp_check_pathes; @@ -219,7 +255,7 @@ PROTECT_API void InitSettings() CInifile::allow_include_func_t tmp_functor; tmp_functor.bind(&tmp_excluder, &path_excluder_predicate::is_allow_include); pSettingsAuth = xr_new( - fname, + systemPath, TRUE, TRUE, FALSE, @@ -227,11 +263,6 @@ PROTECT_API void InitSettings() tmp_functor ); - FS.update_path(fname, "$game_config$", "game.ltx"); - pGameIni = xr_new(fname, TRUE); - CHECK_OR_EXIT(0 != pGameIni->section_count(), - make_string("Cannot find file %s.\nReinstalling application may fix this problem.", fname)); - g_fTimeFactor = pSettings->r_float("alife", "time_factor"); } @@ -586,6 +617,10 @@ void clearDiscordPresence() void Startup() { + CTimer startup_timer; + CTimer phase_timer; + startup_timer.Start(); + phase_timer.Start(); #ifndef DEDICATED_SERVER fill_vid_monitor_list(); #endif @@ -593,6 +628,7 @@ void Startup() InitSound1(); execUserScript(); InitSound2(); + Msg("* [STARTUP] sound and user config: %d ms", phase_timer.GetElapsed_ms()); #ifndef DEDICATED_SERVER { @@ -643,14 +679,20 @@ void Startup() } // Initialize APP + if (Sound) + Sound->source_prefetch_pause(); + phase_timer.Start(); Device.Create(); + Msg("* [STARTUP] render device: %d ms", phase_timer.GetElapsed_ms()); + phase_timer.Start(); LALib.OnCreate(); pApp = xr_new(); g_pGamePersistent = (IGame_Persistent*)NEW_INSTANCE(CLSID_GAME_PERSISTANT); g_SpatialSpace = xr_new(); g_SpatialSpacePhysic = xr_new(); g_SpatialSpaceLights = xr_new(); + Msg("* [STARTUP] application and game persistent: %d ms", phase_timer.GetElapsed_ms()); // Destroy LOGO DestroyWindow(logoWindow); @@ -665,12 +707,20 @@ void Startup() Msg("[ReShade]: Loaded compatibility addon"); else Msg("[ReShade]: ReShade not installed or version too old - didn't load compatibility addon"); + Msg("* [STARTUP] before main loop: %d ms", startup_timer.GetElapsed_ms()); // Main cycle Msg("* [x-ray]: Starting Main Loop"); //Memory.mem_usage(); Device.Run(); + if (Sound) + Sound->source_prefetch_stop(); + if (pApp && pApp->LoadSessionActive()) + { + try { pApp->LoadSessionCancel("main loop stopped"); } + catch (...) { Msg("! [load-session] cleanup failed after main loop stopped"); } + } // Discord clearDiscordPresence(); @@ -1095,12 +1145,18 @@ int APIENTRY WinMain_impl(HINSTANCE hInstance, // g_temporary_stuff = &trivial_encryptor::decode; compute_build_id(); + ULONGLONG early_phase_time = GetTickCount64(); Core._initialize("xray", NULL, TRUE, fsgame[0] ? fsgame : NULL); + Msg("* [STARTUP] core and filesystem: %llu ms", GetTickCount64() - early_phase_time); + CTimer startup_phase_timer; + startup_phase_timer.Start(); InitSettings(); + Msg("* [STARTUP] settings: %d ms", startup_phase_timer.GetElapsed_ms()); Msg(XRAY_MONOLITH_VERSION); { + startup_phase_timer.Start(); FS_FileSet fset; FS.file_list(fset, "$game_data$", FS_ListFiles, "*"); @@ -1123,6 +1179,7 @@ int APIENTRY WinMain_impl(HINSTANCE hInstance, break; } } + Msg("* [STARTUP] gamedata listing: %d ms", startup_phase_timer.GetElapsed_ms()); } // Adjust player & computer name for Asian @@ -1139,6 +1196,7 @@ int APIENTRY WinMain_impl(HINSTANCE hInstance, #endif // DEDICATED_SERVER FPU::m24r(); + startup_phase_timer.Start(); InitEngine(); InitInput(); @@ -1146,6 +1204,7 @@ int APIENTRY WinMain_impl(HINSTANCE hInstance, InitConsole(); Engine.External.CreateRendererList(); + Msg("* [STARTUP] engine/input/console: %d ms", startup_phase_timer.GetElapsed_ms()); LPCSTR benchName = "-batch_benchmark "; if (strstr(lpCmdLine, benchName)) @@ -1187,6 +1246,7 @@ int APIENTRY WinMain_impl(HINSTANCE hInstance, }; #ifndef DEDICATED_SERVER + startup_phase_timer.Start(); if (Core.ParamsData.test(ECoreParams::r2a)) Console->Execute("renderer renderer_r2a"); else if (Core.ParamsData.test(ECoreParams::r2)) @@ -1197,11 +1257,14 @@ int APIENTRY WinMain_impl(HINSTANCE hInstance, pTmp->Execute(Console->ConfigFile); xr_delete(pTmp); } + Msg("* [STARTUP] renderer config: %d ms", startup_phase_timer.GetElapsed_ms()); #else Console->Execute("renderer renderer_r1"); #endif //. InitInput ( ); + startup_phase_timer.Start(); Engine.External.Initialize(); + Msg("* [STARTUP] renderer DLL: %d ms", startup_phase_timer.GetElapsed_ms()); Console->Execute("stat_memory_async"); Startup(); @@ -1263,6 +1326,7 @@ int APIENTRY WinMain(HINSTANCE hInstance, char* lpCmdLine, int nCmdShow) { + startup_begin_time = GetTickCount64(); // Initialize LuaJIT low-memory pool FIRST, before any DLLs load and fragment // the lower 2GB address space. XR_EARLY_INIT(); @@ -1375,6 +1439,7 @@ void _InitializeFont(CGameFont*& F, LPCSTR section, u32 flags) CApplication::CApplication() { ll_dwReference = 0; + ZeroMemory(&m_load_session, sizeof(m_load_session)); max_load_stage = 0; @@ -1435,6 +1500,9 @@ void CApplication::OnEvent(EVENT E, u64 P1, u64 P2) { if (E == eQuit) { + if (Sound) + Sound->source_prefetch_stop(); + LoadSessionCancel("quit"); g_SASH.EndBenchmark(); PostQuitMessage(0); @@ -1450,6 +1518,10 @@ void CApplication::OnEvent(EVENT E, u64 P1, u64 P2) PROF_EVENT("CApplication::OnEvent: eStart"); LPSTR op_server = LPSTR(P1); LPSTR op_client = LPSTR(P2); + IGame_Persistent::params game_params; + game_params.parse_cmd_line(op_server ? op_server : ""); + LoadSessionStartEvent(!xr_strcmp(game_params.m_new_or_load, "new") ? "new-game" : + !xr_strcmp(game_params.m_new_or_load, "load") ? "menu-save" : "start"); Level_Current = u32(-1); R_ASSERT(0 == g_pGameLevel); R_ASSERT(0 != g_pGamePersistent); @@ -1488,6 +1560,7 @@ void CApplication::OnEvent(EVENT E, u64 P1, u64 P2) } else if (E == eDisconnect) { + LoadSessionPhaseBegin(LoadSessionTeardown); ls_header[0] = '\0'; ls_tip_number[0] = '\0'; ls_tip[0] = '\0'; @@ -1507,6 +1580,13 @@ void CApplication::OnEvent(EVENT E, u64 P1, u64 P2) } R_ASSERT(0 != g_pGamePersistent); g_pGamePersistent->Disconnect(); + LoadSessionPhaseEnd(LoadSessionTeardown); + if (!Engine.Event.Peek("KERNEL:start")) + { + LoadSessionCancel("disconnect"); + if (Sound) + Sound->source_prefetch_start(); + } } else if (E == eConsole) { @@ -1517,6 +1597,7 @@ void CApplication::OnEvent(EVENT E, u64 P1, u64 P2) else if (E == eStartMPDemo) { LPSTR demo_file = LPSTR(P1); + LoadSessionStartEvent("mp-demo"); R_ASSERT(0 == g_pGameLevel); R_ASSERT(0 != g_pGamePersistent); @@ -1542,16 +1623,325 @@ void CApplication::OnEvent(EVENT E, u64 P1, u64 P2) } static CTimer phase_timer; +static CTimer total_load_timer; extern ENGINE_API BOOL g_appLoaded = FALSE; //AVO: used by SPAWN_ANTIFREEZE (by alpet) extern ENGINE_API BOOL g_bootComplete = FALSE; //-AVO +void CApplication::LoadSessionBegin(LPCSTR scenario) +{ + if (m_load_session.active) + LoadSessionCancel("superseded"); + + ZeroMemory(&m_load_session, sizeof(m_load_session)); + m_load_session.started_at = Device.TimerAsync(); + m_load_session.client_event_hash = 14695981039346656037ULL; + xr_strcpy(m_load_session.scenario, scenario ? scenario : "unknown"); + try + { + m_load_session.native_generation = NativeLoadExecutor::Instance().BeginGeneration(); + if (Device.m_pRender) + m_load_session.resource_generation = Device.m_pRender->ResourcesBeginLoadGeneration(); + } + catch (...) + { + NativeLoadExecutor::Instance().CancelGeneration(m_load_session.native_generation); + ZeroMemory(&m_load_session, sizeof(m_load_session)); + throw; + } + m_load_session.active = true; + Msg("* [load-session] begin scenario=%s", m_load_session.scenario); + if (Sound) + Sound->source_prefetch_pause(); +} + +void CApplication::LoadSessionContinue(LPCSTR scenario) +{ + if (!m_load_session.active) + LoadSessionBegin(scenario); +} + +void CApplication::LoadSessionExpectReconnect() +{ + if (m_load_session.active) + m_load_session.reconnect_pending = true; +} + +void CApplication::LoadSessionStartEvent(LPCSTR scenario) +{ + if (m_load_session.active && m_load_session.reconnect_pending) + { + m_load_session.reconnect_pending = false; + return; + } + LoadSessionBegin(scenario); +} + +void CApplication::LoadSessionCancel(LPCSTR reason) +{ + if (!m_load_session.active) + return; + + std::exception_ptr failure; + try + { + if (m_load_session.native_generation) + NativeLoadExecutor::Instance().CancelGeneration(m_load_session.native_generation); + } + catch (...) + { + failure = std::current_exception(); + } + if (::Render) + ::Render->level_AbortAsyncLoad(); + try + { + if (Device.m_pRender && m_load_session.resource_generation) + Device.m_pRender->ResourcesAbortLoadGeneration(m_load_session.resource_generation); + } + catch (...) + { + if (!failure) + failure = std::current_exception(); + } + try + { + if (Device.m_pRender) + Device.m_pRender->ResourcesDestroyNecessaryTextures(); + } + catch (...) + { + if (!failure) + failure = std::current_exception(); + } + Msg("* [load-session] cancelled scenario=%s after %u ms (%s)", m_load_session.scenario, + Device.TimerAsync() - m_load_session.started_at, reason ? reason : "unknown"); + ZeroMemory(&m_load_session, sizeof(m_load_session)); + if (failure) + std::rethrow_exception(failure); +} + +void CApplication::LoadSessionSetScenario(LPCSTR scenario) +{ + if (m_load_session.active && scenario) + xr_strcpy(m_load_session.scenario, scenario); +} + +void CApplication::LoadSessionPhaseBegin(ELoadSessionPhase phase) +{ + if (!m_load_session.active || phase >= LoadSessionPhaseCount || m_load_session.phase_running[phase]) + return; + + m_load_session.phase_running[phase] = true; + m_load_session.phase_started_at[phase] = Device.TimerAsync(); +} + +void CApplication::LoadSessionPhaseEnd(ELoadSessionPhase phase) +{ + if (!m_load_session.active || phase >= LoadSessionPhaseCount || !m_load_session.phase_running[phase]) + return; + + m_load_session.phase_elapsed[phase] += Device.TimerAsync() - m_load_session.phase_started_at[phase]; + m_load_session.phase_running[phase] = false; +} + +void CApplication::LoadSessionPrecacheBegin() +{ + if (!m_load_session.active) + return; + + m_load_session.precache_started = true; + m_load_session.precache_started_at = Device.TimerAsync(); + m_load_session.precache_frames = 0; + m_load_session.precache_level_calls = 0; + m_load_session.precache_loadscreen_calls = 0; + m_load_session.precache_present_calls = 0; + m_load_session.precache_wall_ticks = 0; + m_load_session.precache_frame_move_ticks = 0; + m_load_session.precache_seq_render_ticks = 0; + m_load_session.precache_end_ticks = 0; + m_load_session.precache_present_ticks = 0; + m_load_session.precache_secondary_wait_ticks = 0; + m_load_session.precache_level_calculate_ticks = 0; + m_load_session.precache_level_render_ticks = 0; + m_load_session.precache_loadscreen_ticks = 0; +} + +bool CApplication::LoadSessionMeasurePrecache() const +{ + return m_load_session.active && m_load_session.precache_started && + Device.dwPrecacheFrame && Device.dwPrecacheTotal == 60; +} + +void CApplication::LoadSessionRecordPrecacheFrame(u64 wall_ticks, u64 frame_move_ticks, + u64 seq_render_ticks, u64 end_ticks, u64 secondary_wait_ticks) +{ + if (!m_load_session.active || !m_load_session.precache_started) + return; + + ++m_load_session.precache_frames; + m_load_session.precache_wall_ticks += wall_ticks; + m_load_session.precache_frame_move_ticks += frame_move_ticks; + m_load_session.precache_seq_render_ticks += seq_render_ticks; + m_load_session.precache_end_ticks += end_ticks; + m_load_session.precache_secondary_wait_ticks += secondary_wait_ticks; +} + +void CApplication::LoadSessionRecordPrecacheLevel(u64 calculate_ticks, u64 render_ticks) +{ + if (!m_load_session.active || !m_load_session.precache_started) + return; + + ++m_load_session.precache_level_calls; + m_load_session.precache_level_calculate_ticks += calculate_ticks; + m_load_session.precache_level_render_ticks += render_ticks; +} + +void CApplication::LoadSessionRecordPrecacheLoadscreen(u64 ticks) +{ + if (!m_load_session.active || !m_load_session.precache_started) + return; + + ++m_load_session.precache_loadscreen_calls; + m_load_session.precache_loadscreen_ticks += ticks; +} + +void CApplication::LoadSessionRecordPrecachePresent(u64 ticks) +{ + if (!m_load_session.active || !m_load_session.precache_started) + return; + + ++m_load_session.precache_present_calls; + m_load_session.precache_present_ticks += ticks; +} + +void CApplication::LoadSessionRecordClientEvent( + bool spawn, u16 destination, u16 type, const void* packet_data, u32 packet_size) +{ + if (!m_load_session.active) + return; + + auto append = [this](const void* data, u32 size) + { + const u8* bytes = static_cast(data); + for (u32 i = 0; i < size; ++i) + { + m_load_session.client_event_hash ^= bytes[i]; + m_load_session.client_event_hash *= 1099511628211ULL; + } + }; + + const u8 kind = spawn ? 1 : 2; + append(&kind, sizeof(kind)); + if (spawn) + { + R_ASSERT(packet_data && packet_size <= NET_PacketSizeLimit); + NET_Packet packet; + packet.B.count = packet_size; + CopyMemory(packet.B.data, packet_data, packet_size); + u16 message; + packet.r_begin(message); + shared_str section; + packet.r_stringZ(section); + string256 replacement; + packet.r_stringZ(replacement); + packet.r_u8(); + packet.r_u8(); + Fvector position; + Fvector angle; + packet.r_vec3(position); + packet.r_vec3(angle); + packet.r_u16(); + const u16 object_id = packet.r_u16(); + const u16 parent_id = packet.r_u16(); + append(&object_id, sizeof(object_id)); + append(&parent_id, sizeof(parent_id)); + append(section.c_str(), xr_strlen(section.c_str())); + ++m_load_session.client_spawn_count; + } + else + { + append(&destination, sizeof(destination)); + append(&type, sizeof(type)); + ++m_load_session.client_event_count; + } +} + +void CApplication::LoadSessionTryFinish(bool level_ready, bool control_ready, bool queues_drained) +{ + if (!m_load_session.active || !m_load_session.precache_started || Device.dwPrecacheFrame || + !g_loading_events.empty() || !level_ready || !control_ready || !queues_drained) + return; + + LoadSessionPhaseBegin(LoadSessionResourceWait); + try + { + if (m_load_session.native_generation) + NativeLoadExecutor::Instance().FinalizeGeneration(m_load_session.native_generation); + } + catch (...) + { + if (Device.m_pRender && m_load_session.resource_generation) + Device.m_pRender->ResourcesAbortLoadGeneration(m_load_session.resource_generation); + throw; + } + if (Device.m_pRender && m_load_session.resource_generation) + Device.m_pRender->ResourcesFinalizeLoadGeneration(m_load_session.resource_generation); + if (Device.m_pRender) + Device.m_pRender->ResourcesDestroyNecessaryTextures(); + LoadSessionPhaseEnd(LoadSessionResourceWait); + + const u32 now = Device.TimerAsync(); + Msg("* [load-session] engine ready scenario=%s: %u ms", m_load_session.scenario, + now - m_load_session.started_at); + Msg("* [load-session] client order: hash=%016llx, spawns=%u, events=%u", + m_load_session.client_event_hash, m_load_session.client_spawn_count, m_load_session.client_event_count); + Msg("* [load-session] phases: teardown=%u ms, server/lua=%u ms, native level=%u ms, " + "resource wait=%u ms, client spawn=%u ms, final precache=%u ms", + m_load_session.phase_elapsed[LoadSessionTeardown], + m_load_session.phase_elapsed[LoadSessionServerLua], + m_load_session.phase_elapsed[LoadSessionNativeLevel], + m_load_session.phase_elapsed[LoadSessionResourceWait], + m_load_session.phase_elapsed[LoadSessionClientSpawn], + now - m_load_session.precache_started_at); + const auto to_ms = [](u64 ticks) + { + return double(ticks) * 1000.0 / double(CPU::qpc_freq); + }; + const u64 serial_ticks = m_load_session.precache_frame_move_ticks + + m_load_session.precache_seq_render_ticks + m_load_session.precache_end_ticks + + m_load_session.precache_secondary_wait_ticks; + const u64 serial_other_ticks = m_load_session.precache_wall_ticks > serial_ticks ? + m_load_session.precache_wall_ticks - serial_ticks : 0; + const u64 measured_render_ticks = m_load_session.precache_level_calculate_ticks + + m_load_session.precache_level_render_ticks + m_load_session.precache_loadscreen_ticks; + const u64 render_other_ticks = m_load_session.precache_seq_render_ticks > measured_render_ticks ? + m_load_session.precache_seq_render_ticks - measured_render_ticks : 0; + Msg("* [load-session] precache perf: frames=%u, calls(level/loadscreen/present)=%u/%u/%u, " + "wall=%.2f ms, frame move=%.2f ms, seq render=%.2f ms, level calculate/render=%.2f/%.2f ms, " + "loadscreen=%.2f ms, render other=%.2f ms, end/present=%.2f/%.2f ms, " + "secondary wait=%.2f ms, serial other=%.2f ms", + m_load_session.precache_frames, m_load_session.precache_level_calls, + m_load_session.precache_loadscreen_calls, m_load_session.precache_present_calls, + to_ms(m_load_session.precache_wall_ticks), to_ms(m_load_session.precache_frame_move_ticks), + to_ms(m_load_session.precache_seq_render_ticks), + to_ms(m_load_session.precache_level_calculate_ticks), + to_ms(m_load_session.precache_level_render_ticks), to_ms(m_load_session.precache_loadscreen_ticks), + to_ms(render_other_ticks), to_ms(m_load_session.precache_end_ticks), + to_ms(m_load_session.precache_present_ticks), + to_ms(m_load_session.precache_secondary_wait_ticks), to_ms(serial_other_ticks)); + m_load_session.active = false; + if (Sound) + Sound->source_prefetch_start(); +} + void CApplication::LoadBegin() { ll_dwReference++; if (1 == ll_dwReference) { + total_load_timer.Start(); g_appLoaded = FALSE; //AVO: @@ -1573,6 +1963,7 @@ void CApplication::LoadEnd() ll_dwReference--; if (0 == ll_dwReference) { + Msg("* total loading time: %d ms", total_load_timer.GetElapsed_ms()); Msg("* phase time: %d ms", phase_timer.GetElapsed_ms()); Msg("* phase cmem: %lld K", Memory.mem_usage() / 1024); Console->Execute("stat_memory"); @@ -1647,6 +2038,8 @@ void CApplication::OnFrame() PROF_EVENT(); Engine.Event.OnFrame(); + if (Sound) + Sound->source_prefetch_poll(); g_SpatialSpace->update(); g_SpatialSpacePhysic->update(); } diff --git a/src/xrEngine/x_ray.h b/src/xrEngine/x_ray.h index 4d9e580e40..e4e54a030d 100644 --- a/src/xrEngine/x_ray.h +++ b/src/xrEngine/x_ray.h @@ -4,6 +4,16 @@ // refs class ENGINE_API CGameFont; +enum ELoadSessionPhase +{ + LoadSessionTeardown, + LoadSessionServerLua, + LoadSessionNativeLevel, + LoadSessionResourceWait, + LoadSessionClientSpawn, + LoadSessionPhaseCount +}; + #include "../Include/xrRender/FactoryPtr.h" #include "../Include/xrRender/ApplicationRender.h" @@ -33,6 +43,37 @@ class ENGINE_API CApplication : int load_stage; u32 ll_dwReference; + + struct SLoadSession + { + bool active; + bool precache_started; + bool reconnect_pending; + string32 scenario; + u32 started_at; + u32 precache_started_at; + u64 native_generation; + u64 resource_generation; + u64 client_event_hash; + u32 client_spawn_count; + u32 client_event_count; + u32 precache_frames; + u32 precache_level_calls; + u32 precache_loadscreen_calls; + u32 precache_present_calls; + u64 precache_wall_ticks; + u64 precache_frame_move_ticks; + u64 precache_seq_render_ticks; + u64 precache_end_ticks; + u64 precache_present_ticks; + u64 precache_secondary_wait_ticks; + u64 precache_level_calculate_ticks; + u64 precache_level_render_ticks; + u64 precache_loadscreen_ticks; + u32 phase_started_at[LoadSessionPhaseCount]; + u32 phase_elapsed[LoadSessionPhaseCount]; + bool phase_running[LoadSessionPhaseCount]; + } m_load_session; private: EVENT eQuit; EVENT eStart; @@ -61,6 +102,25 @@ class ENGINE_API CApplication : void LoadStage(); void LoadSwitch(); void LoadDraw(); + void LoadSessionBegin(LPCSTR scenario); + void LoadSessionContinue(LPCSTR scenario); + void LoadSessionExpectReconnect(); + void LoadSessionStartEvent(LPCSTR scenario); + void LoadSessionCancel(LPCSTR reason); + void LoadSessionSetScenario(LPCSTR scenario); + void LoadSessionPhaseBegin(ELoadSessionPhase phase); + void LoadSessionPhaseEnd(ELoadSessionPhase phase); + void LoadSessionPrecacheBegin(); + bool LoadSessionMeasurePrecache() const; + void LoadSessionRecordPrecacheFrame(u64 wall_ticks, u64 frame_move_ticks, u64 seq_render_ticks, + u64 end_ticks, u64 secondary_wait_ticks); + void LoadSessionRecordPrecacheLevel(u64 calculate_ticks, u64 render_ticks); + void LoadSessionRecordPrecacheLoadscreen(u64 ticks); + void LoadSessionRecordPrecachePresent(u64 ticks); + void LoadSessionRecordClientEvent(bool spawn, u16 destination, u16 type, const void* packet_data, u32 packet_size); + void LoadSessionTryFinish(bool level_ready, bool control_ready, bool queues_drained); + bool LoadSessionActive() const { return m_load_session.active; } + bool LoadSessionPrecacheStarted() const { return m_load_session.precache_started; } virtual void OnEvent(EVENT E, u64 P1, u64 P2); @@ -74,6 +134,7 @@ class ENGINE_API CApplication : }; extern ENGINE_API CApplication* pApp; +extern ENGINE_API void LogStartupMenuReady(); //Discord struct rpc_info diff --git a/src/xrEngine/xr_object.h b/src/xrEngine/xr_object.h index 2bc358e761..618c9a86bf 100644 --- a/src/xrEngine/xr_object.h +++ b/src/xrEngine/xr_object.h @@ -314,6 +314,7 @@ class ENGINE_API CObject : virtual void net_Relcase(CObject* O) { }; // destroy all links to another objects + virtual bool net_RelcaseNeeded() const { return true; } // Position stack IC u32 ps_Size() const { return PositionStack.size(); } diff --git a/src/xrEngine/xr_object_list.cpp b/src/xrEngine/xr_object_list.cpp index 4931a1e051..9590b79ffb 100644 --- a/src/xrEngine/xr_object_list.cpp +++ b/src/xrEngine/xr_object_list.cpp @@ -34,6 +34,8 @@ CObjectList::~CObjectList() { R_ASSERT(objects_active.empty()); R_ASSERT(objects_sleeping.empty()); + R_ASSERT(objects_relcase_active.empty()); + R_ASSERT(objects_relcase_sleeping.empty()); ProcessDestroyQueueImpl(force_destroy_queue); ProcessDestroyQueueImpl(destroy_queue); R_ASSERT(destroy_queue.empty()); @@ -84,11 +86,25 @@ void CObjectList::o_remove(Objects& v, CObject* O) //. Msg("---o_remove[%s][%d]", O->cName().c_str(), O->ID() ); } +void CObjectList::o_remove_relcase(Objects& v, CObject* O) +{ + VERIFY(O->net_RelcaseNeeded()); + if (!v.empty() && v.back() == O) + v.pop_back(); + else + o_remove(v, O); +} + void CObjectList::o_activate(CObject* O) { VERIFY(O && O->processing_enabled()); o_remove(objects_sleeping, O); objects_active.push_back(O); + if (O->net_RelcaseNeeded()) + { + o_remove_relcase(objects_relcase_sleeping, O); + objects_relcase_active.push_back(O); + } O->MakeMeCrow(); } @@ -97,6 +113,11 @@ void CObjectList::o_sleep(CObject* O) VERIFY(O && !O->processing_enabled()); o_remove(objects_active, O); objects_sleeping.push_back(O); + if (O->net_RelcaseNeeded()) + { + o_remove_relcase(objects_relcase_active, O); + objects_relcase_sleeping.push_back(O); + } O->MakeMeCrow(); } @@ -284,10 +305,10 @@ void CObjectList::ProcessDestroyQueueImpl(Objects& queue) for (int it = queue.size() - 1; it >= 0; it--) { auto obj = queue[it]; - for (const auto oit : objects_active) + for (const auto oit : objects_relcase_active) oit->net_Relcase(obj); - for (const auto oit : objects_sleeping) + for (const auto oit : objects_relcase_sleeping) oit->net_Relcase(obj); if (Sound) @@ -424,7 +445,8 @@ return (it==map_NETID.end())?0:it->second; */ void CObjectList::Load() { - R_ASSERT(/*map_NETID.empty() &&*/ objects_active.empty() && force_destroy_queue.empty() && destroy_queue.empty() && objects_sleeping.empty()); + R_ASSERT(/*map_NETID.empty() &&*/ objects_active.empty() && force_destroy_queue.empty() && destroy_queue.empty() && + objects_sleeping.empty() && objects_relcase_active.empty() && objects_relcase_sleeping.empty()); } void CObjectList::ClearProcessDestroyQueueFromDevice() @@ -479,6 +501,8 @@ void CObjectList::Unload() // Clear the destroy_queues from dangling pointers force_destroy_queue.clear(); destroy_queue.clear(); + R_ASSERT(objects_relcase_active.empty()); + R_ASSERT(objects_relcase_sleeping.empty()); } CObject* CObjectList::Create(LPCSTR name) @@ -487,7 +511,11 @@ CObject* CObjectList::Create(LPCSTR name) // Msg("CObjectList::Create [%x]%s", O, name); if (O) + { objects_sleeping.push_back(O); + if (O->net_RelcaseNeeded()) + objects_relcase_sleeping.push_back(O); + } return O; } @@ -512,11 +540,13 @@ void CObjectList::Destroy(CObject* O) VERIFY(std::find(crows.begin(), crows.end(), O) == crows.end()); } - // active/inactive - Objects::iterator _i = std::find(objects_active.begin(), objects_active.end(), O); - if (_i != objects_active.end()) + // Full teardown queues objects in active/sleeping order and destroys them in + // reverse order. Keep the stable-erase fallback for every other path. + if (!objects_active.empty() && objects_active.back() == O) { - objects_active.erase(_i); + objects_active.pop_back(); + if (O->net_RelcaseNeeded()) + o_remove_relcase(objects_relcase_active, O); VERIFY(std::find(objects_active.begin(), objects_active.end(), O) == objects_active.end()); VERIFY( std::find( @@ -526,16 +556,44 @@ void CObjectList::Destroy(CObject* O) ) == objects_sleeping.end() ); } + else if (!objects_sleeping.empty() && objects_sleeping.back() == O) + { + objects_sleeping.pop_back(); + if (O->net_RelcaseNeeded()) + o_remove_relcase(objects_relcase_sleeping, O); + VERIFY(std::find(objects_active.begin(), objects_active.end(), O) == objects_active.end()); + VERIFY(std::find(objects_sleeping.begin(), objects_sleeping.end(), O) == objects_sleeping.end()); + } else { - Objects::iterator _ii = std::find(objects_sleeping.begin(), objects_sleeping.end(), O); - if (_ii != objects_sleeping.end()) + Objects::iterator _i = std::find(objects_active.begin(), objects_active.end(), O); + if (_i != objects_active.end()) { - objects_sleeping.erase(_ii); - VERIFY(std::find(objects_sleeping.begin(), objects_sleeping.end(), O) == objects_sleeping.end()); + objects_active.erase(_i); + if (O->net_RelcaseNeeded()) + o_remove_relcase(objects_relcase_active, O); + VERIFY(std::find(objects_active.begin(), objects_active.end(), O) == objects_active.end()); + VERIFY( + std::find( + objects_sleeping.begin(), + objects_sleeping.end(), + O + ) == objects_sleeping.end() + ); } else - FATAL("! Unregistered object being destroyed"); + { + Objects::iterator _ii = std::find(objects_sleeping.begin(), objects_sleeping.end(), O); + if (_ii != objects_sleeping.end()) + { + objects_sleeping.erase(_ii); + if (O->net_RelcaseNeeded()) + o_remove_relcase(objects_relcase_sleeping, O); + VERIFY(std::find(objects_sleeping.begin(), objects_sleeping.end(), O) == objects_sleeping.end()); + } + else + FATAL("! Unregistered object being destroyed"); + } } g_pGamePersistent->ObjectPool.destroy(O); diff --git a/src/xrEngine/xr_object_list.h b/src/xrEngine/xr_object_list.h index b8a7ee71ff..45008c143d 100644 --- a/src/xrEngine/xr_object_list.h +++ b/src/xrEngine/xr_object_list.h @@ -20,6 +20,8 @@ class ENGINE_API CObjectList Objects destroy_queue; Objects objects_active; Objects objects_sleeping; + Objects objects_relcase_active; + Objects objects_relcase_sleeping; Objects m_crows[2]; u32 m_owner_thread_id; @@ -86,9 +88,11 @@ class ENGINE_API CObjectList void o_crow(CObject* O); void o_remove(Objects& v, CObject* O); + void o_remove_relcase(Objects& v, CObject* O); void o_activate(CObject* O); void o_sleep(CObject* O); IC u32 o_count() { return objects_active.size() + objects_sleeping.size(); }; + IC bool destroy_queues_empty() const { return force_destroy_queue.empty() && destroy_queue.empty(); } IC CObject* o_get_by_iterator(u32 _it) { if (_it < objects_active.size()) return objects_active[_it]; diff --git a/src/xrGame/Actor.h b/src/xrGame/Actor.h index 3b6071a446..06e0bfce63 100644 --- a/src/xrGame/Actor.h +++ b/src/xrGame/Actor.h @@ -580,6 +580,7 @@ class CActor : virtual void net_Destroy(); virtual BOOL net_Relevant(); // { return getSVU() | getLocal(); }; // relevant for export to server virtual void net_Relcase(CObject* O); // + virtual bool net_RelcaseNeeded() const override { return true; } virtual void xr_stdcall on_requested_spawn(CObject* object); //object serialization virtual void save(NET_Packet& output_packet); diff --git a/src/xrGame/BlackGraviArtifact.h b/src/xrGame/BlackGraviArtifact.h index 65a0835d49..d80c495934 100644 --- a/src/xrGame/BlackGraviArtifact.h +++ b/src/xrGame/BlackGraviArtifact.h @@ -34,6 +34,7 @@ class CBlackGraviArtefact : public CGraviArtefact, protected: virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } virtual void UpdateCLChild(); //гравитационный удар по всем объектам в зоне досягаемости diff --git a/src/xrGame/Car.h b/src/xrGame/Car.h index fe1905e11e..f95265574d 100644 --- a/src/xrGame/Car.h +++ b/src/xrGame/Car.h @@ -628,6 +628,7 @@ class CCar : virtual BOOL net_Relevant() { return getLocal(); }; // relevant for export to server virtual BOOL UsedAI_Locations(); virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } // Input virtual void OnMouseMove(int x, int y); virtual void OnKeyboardPress(int dik); diff --git a/src/xrGame/CustomMonster.h b/src/xrGame/CustomMonster.h index f6175e664d..81f809cc3a 100644 --- a/src/xrGame/CustomMonster.h +++ b/src/xrGame/CustomMonster.h @@ -161,6 +161,7 @@ class CCustomMonster : virtual void net_Export(NET_Packet& P); // export to server virtual void net_Import(NET_Packet& P); // import from server virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } virtual void SelectAnimation(const Fvector& _view, const Fvector& _move, float speed) = 0; diff --git a/src/xrGame/CustomZone.h b/src/xrGame/CustomZone.h index bb0d9af79d..f4bbe9442c 100644 --- a/src/xrGame/CustomZone.h +++ b/src/xrGame/CustomZone.h @@ -69,6 +69,7 @@ class CCustomZone : public CSpaceRestrictor, float effective_radius(float nearest_shape_radius); virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } virtual void OnEvent(NET_Packet& P, u16 type); float GetMaxPower() { return m_fMaxPower; } diff --git a/src/xrGame/ExplosiveItem.h b/src/xrGame/ExplosiveItem.h index d2fda0ed99..5dec1f398e 100644 --- a/src/xrGame/ExplosiveItem.h +++ b/src/xrGame/ExplosiveItem.h @@ -28,6 +28,7 @@ class CExplosiveItem : virtual void net_Export(NET_Packet& P) { CInventoryItemObject::net_Export(P); } virtual void net_Import(NET_Packet& P) { CInventoryItemObject::net_Import(P); } virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } virtual CGameObject* cast_game_object() { return this; } virtual CExplosive* cast_explosive() { return this; } virtual IDamageSource* cast_IDamageSource() { return CExplosive::cast_IDamageSource(); } diff --git a/src/xrGame/ExplosiveRocket.h b/src/xrGame/ExplosiveRocket.h index a286517eb2..d28f7f4b8a 100644 --- a/src/xrGame/ExplosiveRocket.h +++ b/src/xrGame/ExplosiveRocket.h @@ -36,6 +36,7 @@ class CExplosiveRocket : virtual BOOL net_Spawn(CSE_Abstract* DC); virtual void net_Destroy(); virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } virtual void OnH_A_Independent(); virtual void OnH_B_Independent(bool just_before_destroy); virtual void UpdateCL(); diff --git a/src/xrGame/GameObject.h b/src/xrGame/GameObject.h index 7b2e3d862b..ec72168539 100644 --- a/src/xrGame/GameObject.h +++ b/src/xrGame/GameObject.h @@ -186,6 +186,7 @@ class CGameObject : virtual BOOL net_Spawn(CSE_Abstract* DC); virtual void net_Destroy(); virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return false; } virtual void UpdateCL(); virtual void OnChangeVisual(); //object serialization diff --git a/src/xrGame/GamePersistent.cpp b/src/xrGame/GamePersistent.cpp index 9db2980d55..1bf2ad8ba4 100644 --- a/src/xrGame/GamePersistent.cpp +++ b/src/xrGame/GamePersistent.cpp @@ -1,6 +1,7 @@ #include "pch_script.h" #include "gamepersistent.h" #include "../xrEngine/fmesh.h" +#include "../xrEngine/x_ray.h" #include "../xrEngine/xr_ioconsole.h" #include "../xrEngine/gamemtllib.h" #include "../Include/xrRender/Kinematics.h" @@ -155,13 +156,32 @@ extern void init_game_globals(); void CGamePersistent::OnAppStart() { - // load game materials - GMLib.Load(); + CTimer timer; + timer.Start(); + xr_task_group nativeTasks; + u32 materialsMs = 0; +#ifndef _EDITOR + nativeTasks.run([this]() { Environment().load(); }); +#endif + nativeTasks.run([&materialsMs]() + { + CTimer materialsTimer; + materialsTimer.Start(); + GMLib.Load(); + materialsMs = materialsTimer.GetElapsed_ms(); + }); + timer.Start(); init_game_globals(); + Msg("* [STARTUP] game globals: %d ms", timer.GetElapsed_ms()); + timer.Start(); + nativeTasks.wait(); inherited::OnAppStart(); + Msg("* [STARTUP] native barrier: wait=%d materials-work=%u ms", timer.GetElapsed_ms(), materialsMs); + timer.Start(); m_pUI_core = xr_new(); m_pMainMenu = xr_new(); m_pWallmarksManager = xr_new(); + Msg("* [STARTUP] UI and main menu objects: %d ms", timer.GetElapsed_ms()); } @@ -789,6 +809,8 @@ void CGamePersistent::OnEvent(EVENT E, u64 P1, u64 P2) if (E == eQuickLoad) { PROF_EVENT("eQuickLoad"); + pApp->LoadSessionContinue("quickload"); + pApp->LoadSessionSetScenario("quickload"); if (Device.Paused()) Device.Pause(FALSE, TRUE, TRUE, "eQuickLoad"); @@ -807,10 +829,14 @@ void CGamePersistent::OnEvent(EVENT E, u64 P1, u64 P2) LPSTR saved_name = (LPSTR)(P1); + pApp->LoadSessionPhaseBegin(LoadSessionTeardown); Level().remove_objects(); + pApp->LoadSessionPhaseEnd(LoadSessionTeardown); game_sv_Single* game = smart_cast(Level().Server->game); R_ASSERT(game); + pApp->LoadSessionPhaseBegin(LoadSessionServerLua); game->restart_simulator(saved_name); + pApp->LoadSessionPhaseEnd(LoadSessionServerLua); xr_free(saved_name); return; } diff --git a/src/xrGame/GraviZone.h b/src/xrGame/GraviZone.h index a777cb0eea..e1151f066a 100644 --- a/src/xrGame/GraviZone.h +++ b/src/xrGame/GraviZone.h @@ -27,6 +27,7 @@ class CBaseGraviZone : public CCustomZone virtual BOOL net_Spawn(CSE_Abstract* DC); virtual void net_Destroy(); virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } //воздействие зоной на объект diff --git a/src/xrGame/Grenade.h b/src/xrGame/Grenade.h index 98de6e3d22..705f1d4555 100644 --- a/src/xrGame/Grenade.h +++ b/src/xrGame/Grenade.h @@ -41,6 +41,7 @@ class CGrenade : virtual BOOL net_Spawn(CSE_Abstract* DC); virtual void net_Destroy(); virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } virtual void OnH_B_Independent(bool just_before_destroy); virtual void OnH_A_Independent(); diff --git a/src/xrGame/InventoryBox.h b/src/xrGame/InventoryBox.h index a6081b9512..988d638c22 100644 --- a/src/xrGame/InventoryBox.h +++ b/src/xrGame/InventoryBox.h @@ -23,6 +23,7 @@ class CInventoryBox : public CGameObject virtual BOOL net_Spawn(CSE_Abstract* DC); virtual void net_Destroy(); virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } void AddAvailableItems(TIItemContainer& items_container) const; IC bool IsEmpty() const { return m_items.empty(); } virtual void UpdateCL(); diff --git a/src/xrGame/Level.cpp b/src/xrGame/Level.cpp index 44450ed445..3c0e41d497 100644 --- a/src/xrGame/Level.cpp +++ b/src/xrGame/Level.cpp @@ -3,6 +3,7 @@ #include "xrEngine/FDemoPlay.h" #include "xrEngine/Environment.h" #include "xrEngine/IGame_Persistent.h" +#include "xrEngine/x_ray.h" #include "ParticlesObject.h" #include "Level.h" #include "HUDManager.h" @@ -80,31 +81,6 @@ u32 lvInterpSteps = 0; #ifdef SPAWN_ANTIFREEZE BOOL spawn_antifreeze = TRUE; BOOL spawn_antifreeze_debug = FALSE; -static HANDLE prefetch_thread_signal; - -static void unpausePrefetchThreadSignal() -{ - //if (spawn_antifreeze_debug) Msg("prefetch_thread_signal Set"); - SetEvent(prefetch_thread_signal); -} - -static void pausePrefetchThreadSignal() -{ - //if (spawn_antifreeze_debug) Msg("prefetch_thread_signal Reset"); - ResetEvent(prefetch_thread_signal); -} - -static void closePrefetchThreadSignal() -{ - if (spawn_antifreeze_debug) Msg("prefetch_thread_signal Close"); - CloseHandle(prefetch_thread_signal); -} - -static void createPrefetchThreadSignal() -{ - if (spawn_antifreeze_debug) Msg("prefetch_thread_signal CreateEvent"); - prefetch_thread_signal = CreateEvent(nullptr, TRUE, FALSE, nullptr); -} struct spawn_and_prefetch_events { @@ -114,6 +90,9 @@ struct spawn_and_prefetch_events models_set* prefetched_models = nullptr; bool* closeSignal = nullptr; xrSRWLock* prefetch_lock = nullptr; + bool* busy = nullptr; + HANDLE signal = nullptr; + HANDLE stopped = nullptr; }; u16 GetSpawnInfo(NET_Packet& P, u16& parent_id, shared_str& section) @@ -142,6 +121,47 @@ u16 GetSpawnInfo(NET_Packet& P, u16& parent_id, shared_str& section) P.r_pos = 0; return id; } + +void CLevel::RegisterPreparedClientSpawnResource(u16 id, u16 parent_id, const shared_str& section, + const shared_str& actual_visual, const shared_str& ltx_visual, LPCSTR canonical_level_path) +{ + prepared_client_spawn_resource resource; + resource.section = section; + resource.parent_id = parent_id; + resource.actual_visual = actual_visual; + resource.ltx_visual = ltx_visual; + resource.level_path = canonical_level_path ? canonical_level_path : ""; + xrCriticalSectionGuard guard(prepared_client_spawn_guard); + prepared_client_spawn_resources[id] = std::move(resource); +} + +bool CLevel::PublishPreparedClientSpawnResource(NET_Packet& packet) +{ + NET_Packet copy = packet; + u16 parent_id; + shared_str section; + const u16 id = GetSpawnInfo(copy, parent_id, section); + prepared_client_spawn_resource resource; + { + xrCriticalSectionGuard guard(prepared_client_spawn_guard); + auto found = prepared_client_spawn_resources.find(id); + if (found == prepared_client_spawn_resources.end()) + return false; + resource = std::move(found->second); + prepared_client_spawn_resources.erase(found); + } + if (resource.parent_id != parent_id || resource.section != section) + return false; + + bool actual_published = false; + if (resource.actual_visual.size()) + actual_published = ::Render->models_PrefetchPrepared(resource.actual_visual.c_str(), + resource.level_path.c_str(), false); + if (!actual_published && resource.ltx_visual.size() && resource.ltx_visual != resource.actual_visual) + actual_published = ::Render->models_PrefetchPrepared( + resource.ltx_visual.c_str(), resource.level_path.c_str(), false); + return actual_published; +} #endif //-AVO @@ -153,7 +173,7 @@ struct ProcessNetPacket : public intrusive_base_nonatomic struct ProcessGameEventsData : ProcessNetPacket { - prefetch_event E; + prefetch_event E; NET_Packet PRespond; }; @@ -278,8 +298,11 @@ CLevel::CLevel() : spawn_events_data = xr_new(); prefetch_events = xr_new(); prefetched_models = xr_new(); - auto events = new spawn_and_prefetch_events({ spawn_events, spawn_events_data, prefetch_events, prefetched_models, &closeSignal, &prefetch_lock }); - createPrefetchThreadSignal(); + prefetch_thread_signal = CreateEvent(nullptr, TRUE, FALSE, nullptr); + prefetch_thread_stopped = CreateEvent(nullptr, TRUE, FALSE, nullptr); + R_ASSERT(prefetch_thread_signal && prefetch_thread_stopped); + auto events = new spawn_and_prefetch_events({ spawn_events, spawn_events_data, prefetch_events, prefetched_models, + &closeSignal, &prefetch_lock, &spawn_prefetch_busy, prefetch_thread_signal, prefetch_thread_stopped }); thread_spawn(ProcessPrefetchEvents, "Pre-Spawn Prefetcher Thread", 0, events); Msg("CLevel::CLevel() Spawn Antifreeze initialized"); #endif @@ -299,6 +322,7 @@ CLevel::~CLevel() delete_data(hud_zones_list); hud_zones_list = nullptr; Msg("- Destroying level"); + ShutdownGameSpecificPrepare(); Engine.Event.Handler_Detach(eEntitySpawn, this); Engine.Event.Handler_Detach(eEnvironment, this); Engine.Event.Handler_Detach(eChangeTrack, this); @@ -332,18 +356,27 @@ CLevel::~CLevel() delete_data(m_debug_render_queue); if (!g_dedicated_server) ai().script_engine().remove_script_process(ScriptEngine::eScriptProcessorLevel); - xr_delete(game); - xr_delete(game_events); #ifdef SPAWN_ANTIFREEZE - xr_delete(spawn_events); - xr_delete(spawn_events_data); - xr_delete(prefetch_events); - xr_delete(prefetched_models); - closeSignal = true; // signal ProcessPrefetchEvents thread to exit - unpausePrefetchThreadSignal(); + { + xrSRWLockGuard g(prefetch_lock); + closeSignal = true; + } + SetEvent(prefetch_thread_signal); + R_ASSERT(WAIT_OBJECT_0 == WaitForSingleObject(prefetch_thread_stopped, INFINITE)); + CloseHandle(prefetch_thread_signal); + CloseHandle(prefetch_thread_stopped); + prefetch_thread_signal = nullptr; + prefetch_thread_stopped = nullptr; + xr_delete(spawn_events); + xr_delete(spawn_events_data); + xr_delete(prefetch_events); + xr_delete(prefetched_models); #endif + xr_delete(game); + xr_delete(game_events); + xr_delete(m_pBulletManager); xr_delete(pStatGraphR); xr_delete(pStatGraphS); @@ -570,69 +603,73 @@ void CLevel::ProcessPrefetchEvents(void* args) auto prefetched_models = events->prefetched_models; auto closeSignal = events->closeSignal; auto prefetch_lock = events->prefetch_lock; + auto busy = events->busy; + auto signal = events->signal; + auto stopped = events->stopped; while (true) { - WaitForSingleObject(prefetch_thread_signal, INFINITE); // wait for prefetch queue event to be signaled - - if (*closeSignal == true) - { - if (spawn_antifreeze_debug) Msg("[ProcessPrefetchEvents] closeSignal received, destroying thread"); - closePrefetchThreadSignal(); - delete events; - return; - } - - { - xrSRWLockGuard g(prefetch_lock, true); - if (prefetch_events->empty()) - { - if (spawn_antifreeze_debug) Msg("[ProcessPrefetchEvents] called, but prefetch_events queue is empty"); - pausePrefetchThreadSignal(); - continue; - } - } + WaitForSingleObject(signal, INFINITE); // wait for prefetch queue event to be signaled PROF_EVENT("ProcessPrefetchEvents") prefetch_event_queue saved_prefetch_events; + bool close = false; { xrSRWLockGuard g(prefetch_lock); - if (spawn_antifreeze_debug) Msg("[ProcessPrefetchEvents] started, queue size %d", prefetch_events->size()); - saved_prefetch_events.swap(*prefetch_events); // move the events to temp queue, so we can continue processing prefetch_events in the main thread - pausePrefetchThreadSignal(); + close = *closeSignal; + if (!close) + { + if (prefetch_events->empty()) + { + if (spawn_antifreeze_debug) Msg("[ProcessPrefetchEvents] called, but prefetch_events queue is empty"); + } + else + { + if (spawn_antifreeze_debug) Msg("[ProcessPrefetchEvents] started, queue size %d", prefetch_events->size()); + saved_prefetch_events.swap(*prefetch_events); // move the events to temp queue, so we can continue processing prefetch_events in the main thread + *busy = true; + } + ResetEvent(signal); + } } - for (const auto& E : saved_prefetch_events) - { - for (const auto& model : E.models) - { - bool not_prefetched = false; + if (close) + { + if (spawn_antifreeze_debug) Msg("[ProcessPrefetchEvents] closeSignal received, destroying thread"); + delete events; + SetEvent(stopped); + return; + } - { - xrSRWLockGuard g(prefetch_lock, true); - not_prefetched = prefetched_models->find(model) == prefetched_models->end(); - } + if (saved_prefetch_events.empty()) + continue; - if (not_prefetched) - { - if (spawn_antifreeze_debug) Msg("[ProcessPrefetchEvents] Prefetching model '%s' for spawn event", model.c_str()); - ::Render->models_PrefetchOne(model.c_str(), false); + for (const auto& E : saved_prefetch_events) + { + for (const auto& model : E.models) + { + bool not_prefetched = false; + { + xrSRWLockGuard g(prefetch_lock, true); + not_prefetched = prefetched_models->find(model) == prefetched_models->end(); + } + if (!not_prefetched) + continue; - { - xrSRWLockGuard g(prefetch_lock); - prefetched_models->insert(model); // add model to prefetched models set to avoid double prefetching - } - } - } - } + ::Render->models_PrefetchOne(model.c_str(), false); + xrSRWLockGuard g(prefetch_lock); + prefetched_models->insert(model); + } + } - { + { xrSRWLockGuard g(prefetch_lock); for (auto& E : saved_prefetch_events) { spawn_events->insert(E.p); // reinsert the event to spawn_events queue for further processing spawn_events_data->emplace(E.id, E); // store the prefetch event data for later use in ProcessSpawnEvents } + *busy = false; if (spawn_antifreeze_debug) Msg("[ProcessPrefetchEvents] finished, spawn_events queue size %d", spawn_events->queue.size()); } @@ -707,9 +744,21 @@ void CLevel::ProcessSpawnEvents() } } + // Model publication can enter renderer Lua shader lookup. It must stay + // on the owner thread and immediately precede the original spawn. + if (spawn_data_it != spawn_events_data_copy.end()) + { + for (const xr_string& model : spawn_data_it->second.models) + { + if (prefetched_models->insert(model).second) + ::Render->models_PrefetchOne(model.c_str(), false); + } + } + spawn: u16 dummy16; P.r_begin(dummy16); + pApp->LoadSessionRecordClientEvent(true, 0, 0, P.B.data, P.B.count); cl_Process_Spawn(P); } } @@ -834,14 +883,11 @@ void CLevel::ProcessGameEvents() { auto& E = data->E; E.p = P; - E.models = models; - E.id = obj_id; - E.hasAlifeObject = obj != nullptr; - - events_to_prefetch.push_back(E); - - if (spawn_antifreeze_debug) Msg("[ProcessGameEvents] added M_SPAWN to prefetch_events: section %s, obj_id %d, parent_id %d, event_id %d", section.c_str(), obj_id, parent_id, dest); - it++; // Move to next event + E.models = std::move(models); + E.id = obj_id; + E.hasAlifeObject = obj != nullptr; + events_to_prefetch.push_back(std::move(E)); + ++it; continue; } } @@ -868,12 +914,14 @@ void CLevel::ProcessGameEvents() u16 dummy16; P.r_begin(dummy16); + pApp->LoadSessionRecordClientEvent(true, 0, 0, P.B.data, P.B.count); cl_Process_Spawn(P); break; } case M_EVENT: { PROF_EVENT("ProcessGameEvents M_EVENT"); + pApp->LoadSessionRecordClientEvent(false, dest, type, P.B.data, P.B.count); cl_Process_Event(dest, type, P); break; } @@ -930,8 +978,10 @@ void CLevel::ProcessGameEvents() if (!events_to_prefetch.empty()) { xrSRWLockGuard g(prefetch_lock); - prefetch_events->insert(prefetch_events->end(), events_to_prefetch.begin(), events_to_prefetch.end()); - unpausePrefetchThreadSignal(); + prefetch_events->insert(prefetch_events->end(), + std::make_move_iterator(events_to_prefetch.begin()), + std::make_move_iterator(events_to_prefetch.end())); + SetEvent(prefetch_thread_signal); } #endif @@ -957,6 +1007,7 @@ void CLevel::MakeReconnect() { if (!Engine.Event.Peek("KERNEL:disconnect")) { + pApp->LoadSessionExpectReconnect(); Engine.Event.Defer("KERNEL:disconnect"); char const* server_options = nullptr; char const* client_options = nullptr; @@ -1020,26 +1071,57 @@ void CLevel::OnFrame() } else { + const bool measure_client_spawn = pApp->LoadSessionActive() && pApp->LoadSessionPrecacheStarted(); + if (measure_client_spawn) + pApp->LoadSessionPhaseBegin(LoadSessionClientSpawn); Device.Statistic->netClient1.Begin(); ClientReceive(); Device.Statistic->netClient1.End(); - } - - ProcessGameEvents(); + + ProcessGameEvents(); #ifdef SPAWN_ANTIFREEZE - { - bool queueEmpty = false; - { - xrSRWLockGuard g(prefetch_lock); - queueEmpty = spawn_events->queue.empty(); - } - if (!queueEmpty) { - SortSpawnEventsQueue(); - ProcessSpawnEvents(); + bool queueEmpty = false; + { + xrSRWLockGuard g(prefetch_lock); + queueEmpty = spawn_events->queue.empty(); + } + if (!queueEmpty) + { + SortSpawnEventsQueue(); + ProcessSpawnEvents(); + } } +#endif + if (measure_client_spawn) + pApp->LoadSessionPhaseEnd(LoadSessionClientSpawn); } + + const auto load_queues_drained = [this]() + { + if (!net_msg_Empty() || !Objects.destroy_queues_empty()) + return false; +#ifdef SPAWN_ANTIFREEZE + xrSRWLockGuard g(prefetch_lock, true); + return game_events->queue.empty() && game_spawn_queue.empty() && spawn_events->queue.empty() && + prefetch_events->empty() && spawn_events_data->empty() && !spawn_prefetch_busy; +#else + return game_events->queue.empty() && game_spawn_queue.empty(); #endif + }; + + const bool control_ready = g_dedicated_server || + (CurrentControlEntity() != nullptr && (GameID() != eGameIDSingle || g_actor != nullptr)); + bool queues_drained = load_queues_drained(); + if (!g_dedicated_server && pApp->LoadSessionActive() && pApp->LoadSessionPrecacheStarted() && + !Device.dwPrecacheFrame && g_loading_events.empty() && bReady && control_ready && queues_drained) + { + pApp->LoadSessionPhaseBegin(LoadSessionResourceWait); + Device.m_pRender->ResourcesDeferredUpload(); + pApp->LoadSessionPhaseEnd(LoadSessionResourceWait); + queues_drained = load_queues_drained(); + } + pApp->LoadSessionTryFinish(bReady, control_ready, queues_drained); if (m_bNeed_CrPr) make_NetCorrectionPrediction(); diff --git a/src/xrGame/Level.h b/src/xrGame/Level.h index 88740cc051..f2d0df0771 100644 --- a/src/xrGame/Level.h +++ b/src/xrGame/Level.h @@ -37,6 +37,7 @@ class demo_info; class CDebugRenderer; class DBG_ScriptObject; class script_attachment; +struct level_game_specific_prepare; extern float g_fov; @@ -69,6 +70,7 @@ class CLevel : { #include "Level_network_Demo.h" void ClearAllObjects(); + void ShutdownGameSpecificPrepare(); private: #ifdef DEBUG bool m_bSynchronization = false; @@ -77,6 +79,7 @@ class CLevel : protected: typedef IGame_Level inherited; CLevelSoundManager* m_level_sound_manager = nullptr; + level_game_specific_prepare* m_game_specific_prepare = nullptr; CSpaceRestrictionManager* m_space_restriction_manager = nullptr; CSeniorityHierarchyHolder* m_seniority_hierarchy_holder = nullptr; CClientSpawnManager* m_client_spawn_manager = nullptr; @@ -222,6 +225,8 @@ class CLevel : virtual bool Load(u32 dwNum); virtual bool Load_GameSpecific_Before(); virtual bool Load_GameSpecific_After(); + virtual bool Load_Prepared_Environment(); + void BeginGameSpecificPrepare(LPCSTR canonical_level_path); virtual void Load_GameSpecific_CFORM(CDB::TRI* T, u32 count); // Events virtual void OnEvent(EVENT E, u64 P1, u64 P2); @@ -261,12 +266,28 @@ class CLevel : prefetch_event_queue* prefetch_events = nullptr; models_set* prefetched_models = nullptr; xrSRWLock prefetch_lock; + bool spawn_prefetch_busy = false; + struct prepared_client_spawn_resource + { + shared_str section; + u16 parent_id = u16(-1); + shared_str actual_visual; + shared_str ltx_visual; + xr_string level_path; + }; + xrCriticalSection prepared_client_spawn_guard; + xr_map prepared_client_spawn_resources; + void RegisterPreparedClientSpawnResource(u16 id, u16 parent_id, const shared_str& section, + const shared_str& actual_visual, const shared_str& ltx_visual, LPCSTR canonical_level_path); + bool PublishPreparedClientSpawnResource(NET_Packet& packet); bool PostponedSpawn(u16 id); void ProcessSpawnEvents(); static void ProcessPrefetchEvents(void* args); void SortSpawnEventsQueue(); private: bool closeSignal = false; + HANDLE prefetch_thread_signal = nullptr; + HANDLE prefetch_thread_stopped = nullptr; int GetSpawnEventPriority(const NET_Event& e) const; bool PostponedSpawnFind(u16 id, const NET_Event& E) const; bool PostponedSpawnFind(u16 id, NET_Packet& P) const; diff --git a/src/xrGame/Level_load.cpp b/src/xrGame/Level_load.cpp index aecfd2f934..d7aa205a0b 100644 --- a/src/xrGame/Level_load.cpp +++ b/src/xrGame/Level_load.cpp @@ -20,11 +20,90 @@ extern ENGINE_API bool g_dedicated_server; +struct level_prepared_particle +{ + shared_str name; + Fmatrix transform; +}; + +struct level_game_specific_prepare +{ + NativeLoadExecutor::Batch batch; + NativeLoadExecutor::Batch environment_batch; + xr_task_group fallback_tasks; + xr_task_group fallback_environment_tasks; + xr_vector particles; + xr_vector environment_modifiers; + CLevelSoundManager::PreparedData sounds; + xr_vector sound_environment; + xr_vector sound_occlusion; + xr_vector random_sounds; + xr_string level_path; + u32 game_type = 0; + bool has_sound_environment = false; + bool has_sound_occlusion = false; + bool environment_committed = false; + bool committed = false; + + void wait_environment() + { + if (environment_batch.Valid()) + NativeLoadExecutor::Instance().Wait(environment_batch); + fallback_environment_tasks.wait(); + } + + void wait() + { + std::exception_ptr failure; + try + { + wait_environment(); + } + catch (...) + { + failure = std::current_exception(); + } + try + { + if (batch.Valid()) + NativeLoadExecutor::Instance().Wait(batch); + fallback_tasks.wait(); + } + catch (...) + { + if (!failure) + failure = std::current_exception(); + } + if (failure) + std::rethrow_exception(failure); + } +}; + +namespace +{ +xr_string level_resource_path(LPCSTR canonical_level_path, LPCSTR file_name) +{ + xr_string path = canonical_level_path; + if (!path.empty() && path.back() != '\\' && path.back() != '/') + path += '\\'; + path += file_name; + return path; +} + +void read_level_resource(LPCSTR canonical_level_path, LPCSTR file_name, xr_vector& bytes) +{ + const xr_string path = level_resource_path(canonical_level_path, file_name); + IReader* reader = FS.r_open(path.c_str()); + R_ASSERT3(reader, "Cannot open level resource", path.c_str()); + bytes.resize(reader->length()); + reader->r(bytes.data(), bytes.size()); + FS.r_close(reader); +} +} + bool CLevel::Load_GameSpecific_Before() { // AI space - // g_pGamePersistent->LoadTitle ("st_loading_ai_objects"); - g_pGamePersistent->LoadTitle(); string_path fn_game; if (GamePersistent().GameType() == eGameIDSingle && !ai().get_alife() && FS.exist(fn_game, "$level$", "level.ai") && @@ -46,19 +125,69 @@ bool CLevel::Load_GameSpecific_Before() return (TRUE); } -bool CLevel::Load_GameSpecific_After() +void CLevel::BeginGameSpecificPrepare(LPCSTR canonical_level_path) { - R_ASSERT(m_StaticParticles.empty()); - // loading static particles - string_path fn_game; - if (FS.exist(fn_game, "$level$", "level.ps_static")) + R_ASSERT(canonical_level_path && canonical_level_path[0]); + xr_string level_path = canonical_level_path; + if (!level_path.empty() && level_path.back() != '\\' && level_path.back() != '/') + level_path += '\\'; + if (m_game_specific_prepare) + { + R_ASSERT3(!stricmp(m_game_specific_prepare->level_path.c_str(), level_path.c_str()), + "Level prepare target changed", level_path.c_str()); + return; + } + + level_game_specific_prepare* prepared = xr_new(); + prepared->level_path = std::move(level_path); + prepared->game_type = u32(g_pGamePersistent->m_game_params.m_e_game_type); + NativeLoadExecutor& executor = NativeLoadExecutor::Instance(); + prepared->batch = executor.BeginBatch(executor.CurrentGeneration()); + prepared->environment_batch = executor.BeginBatch(executor.CurrentGeneration()); + m_game_specific_prepare = prepared; + auto submit = [prepared, &executor](NativeLoadPriority priority, auto&& work) + { + if (prepared->batch.Valid()) + executor.Submit(prepared->batch, priority, std::forward(work)); + else + prepared->fallback_tasks.run(std::forward(work)); + }; + auto submit_environment = [prepared, &executor](auto&& work) { - IReader* F = FS.r_open(fn_game); + if (prepared->environment_batch.Valid()) + executor.Submit(prepared->environment_batch, NativeLoadPriority::Environment, + std::forward(work)); + else + prepared->fallback_environment_tasks.run(std::forward(work)); + }; + + submit_environment([prepared]() + { + CEnvironment::PrepareLevelModifiers(prepared->level_path.c_str(), prepared->environment_modifiers); + }); + + // pSettings is snapshotted on the owner thread. Workers below only own + // file readers, byte buffers and strings. + if (pSettings->section_exist("sounds_random")) + { + const CInifile::Sect& section = pSettings->r_section("sounds_random"); + prepared->random_sounds.reserve(section.Data.size()); + for (CInifile::SectCIt it = section.Data.begin(); it != section.Data.end(); ++it) + prepared->random_sounds.push_back(it->first); + } + + submit(NativeLoadPriority::Environment, [prepared]() + { + const xr_string file_name = level_resource_path(prepared->level_path.c_str(), "level.ps_static"); + if (!FS.exist(file_name.c_str())) + return; + + IReader* F = FS.r_open(file_name.c_str()); + R_ASSERT3(F, "Cannot open level resource", file_name.c_str()); u32 chunk = 0; string256 ref_name; Fmatrix transform; - Fvector zero_vel = {0.f, 0.f, 0.f}; u32 ver = 0; for (IReader* OBJ = F->open_chunk_iterator(chunk); OBJ; OBJ = F->open_chunk_iterator(chunk, OBJ)) { @@ -83,95 +212,121 @@ bool CLevel::Load_GameSpecific_After() transform.c.y += 0.01f; - if ((g_pGamePersistent->m_game_params.m_e_game_type & EGameIDs(gametype_usage)) || (ver == 0)) + if ((prepared->game_type & u32(gametype_usage)) || (ver == 0)) { - auto pStaticParticles = Particles::Details::Create(ref_name,FALSE,false); - pStaticParticles->UpdateParent(transform, zero_vel); - pStaticParticles->Play(false); - m_StaticParticles.push_back(pStaticParticles); + prepared->particles.push_back({ref_name, transform}); } } FS.r_close(F); + }); + + submit(NativeLoadPriority::Environment, [this, prepared]() + { + if (!g_dedicated_server) + m_level_sound_manager->Prepare(prepared->level_path.c_str(), prepared->sounds); + }); + + submit(NativeLoadPriority::Environment, [prepared]() + { + if (g_dedicated_server) + return; + const xr_string environment = level_resource_path(prepared->level_path.c_str(), "level.snd_env"); + prepared->has_sound_environment = FS.exist(environment.c_str()); + if (prepared->has_sound_environment) + read_level_resource(prepared->level_path.c_str(), "level.snd_env", prepared->sound_environment); + const xr_string occlusion = level_resource_path(prepared->level_path.c_str(), "level.som"); + prepared->has_sound_occlusion = FS.exist(occlusion.c_str()); + if (prepared->has_sound_occlusion) + read_level_resource(prepared->level_path.c_str(), "level.som", prepared->sound_occlusion); + }); +} + +bool CLevel::Load_Prepared_Environment() +{ + if (!m_game_specific_prepare) + return false; + + level_game_specific_prepare& prepared = *m_game_specific_prepare; + if (!prepared.environment_committed) + { + prepared.wait_environment(); + g_pGamePersistent->Environment().CommitLevelModifiers(prepared.environment_modifiers); + prepared.environment_committed = true; + } + return true; +} + +void CLevel::ShutdownGameSpecificPrepare() +{ + if (!m_game_specific_prepare) + return; + + try + { + m_game_specific_prepare->wait(); + } + catch (...) + { + } + xr_delete(m_game_specific_prepare); +} + +bool CLevel::Load_GameSpecific_After() +{ + if (m_game_specific_prepare && m_game_specific_prepare->committed) + return TRUE; + R_ASSERT(m_StaticParticles.empty()); + if (!m_game_specific_prepare) + BeginGameSpecificPrepare(FS.get_path("$level$")->m_Path); + + level_game_specific_prepare& prepared = *m_game_specific_prepare; + prepared.wait(); + const xr_vector& random_sounds = prepared.random_sounds; + + Fvector zero_vel = {0.f, 0.f, 0.f}; + for (const level_prepared_particle& particle : prepared.particles) + { + auto instance = Particles::Details::Create(particle.name.c_str(), FALSE, false); + instance->UpdateParent(particle.transform, zero_vel); + instance->Play(false); + m_StaticParticles.push_back(instance); } if (!g_dedicated_server) { - // loading static sounds VERIFY(m_level_sound_manager); - m_level_sound_manager->Load(); - - // loading sound environment - if (FS.exist(fn_game, "$level$", "level.snd_env")) + m_level_sound_manager->Commit(prepared.sounds); + if (prepared.has_sound_environment) { - IReader* F = FS.r_open(fn_game); - ::Sound->set_geometry_env(F); - FS.r_close(F); + IReader reader(prepared.sound_environment.data(), prepared.sound_environment.size()); + ::Sound->set_geometry_env(&reader); } else - { - // demonized: reset sound environment if the map doesn't have it, so that the next map won't be using environment of the previous one ::Sound->set_geometry_env(nullptr); - } - // loading SOM - if (FS.exist(fn_game, "$level$", "level.som")) + if (prepared.has_sound_occlusion) { - IReader* F = FS.r_open(fn_game); - ::Sound->set_geometry_som(F); - FS.r_close(F); + IReader reader(prepared.sound_occlusion.data(), prepared.sound_occlusion.size()); + ::Sound->set_geometry_som(&reader); } else - { - // demonized: same here ::Sound->set_geometry_som(nullptr); - } - // loading random (around player) sounds - if (pSettings->section_exist("sounds_random")) + Sounds_Random.reserve(random_sounds.size()); + for (const shared_str& name : random_sounds) + { + Sounds_Random.emplace_back(); + Sound->create(Sounds_Random.back(), name.c_str(), st_Effect, sg_SourceType); + } + if (!random_sounds.empty()) { - CInifile::Sect& S = pSettings->r_section("sounds_random"); - Sounds_Random.reserve(S.Data.size()); - for (CInifile::SectCIt I = S.Data.begin(); S.Data.end() != I; ++I) - { - Sounds_Random.push_back(ref_sound()); - Sound->create(Sounds_Random.back(), *I->first, st_Effect, sg_SourceType); - } Sounds_Random_dwNextTime = Device.TimerAsync() + 50000; Sounds_Random_Enabled = FALSE; } if (g_pGamePersistent->pEnvironment) - { if (CEffect_Rain* rain = g_pGamePersistent->pEnvironment->eff_Rain) - { rain->InvalidateState(); - } - } - - if (FS.exist(fn_game, "$level$", "level.fog_vol")) - { - IReader* F = FS.r_open(fn_game); - u16 version = F->r_u16(); - if (version == 2) - { - u32 cnt = F->r_u32(); - - Fmatrix volume_matrix; - for (u32 i = 0; i < cnt; ++i) - { - F->r(&volume_matrix, sizeof(volume_matrix)); - u32 sub_cnt = F->r_u32(); - for (u32 is = 0; is < sub_cnt; ++is) - { - F->r(&volume_matrix, sizeof(volume_matrix)); - } - } - } - FS.r_close(F); - } - } - if (!g_dedicated_server) - { // loading scripts ai().script_engine().remove_script_process(ScriptEngine::eScriptProcessorLevel); @@ -189,6 +344,12 @@ bool CLevel::Load_GameSpecific_After() g_pGamePersistent->Environment().SetGameTime(GetEnvironmentGameDayTimeSec(), game->GetEnvironmentGameTimeFactor()); HUD().SetRenderable(true); + prepared.committed = true; + prepared.particles.clear(); + prepared.sounds.static_sound_chunks.clear(); + prepared.sound_environment.clear(); + prepared.sound_occlusion.clear(); + prepared.random_sounds.clear(); return TRUE; } diff --git a/src/xrGame/Level_network.cpp b/src/xrGame/Level_network.cpp index e5914b4dee..3121c4c7e4 100644 --- a/src/xrGame/Level_network.cpp +++ b/src/xrGame/Level_network.cpp @@ -33,6 +33,21 @@ void CLevel::remove_objects() PROF_EVENT("remove_objects"); if (!IsGameTypeSingle()) Msg("CLevel::remove_objects - Start"); BOOL b_stored = psDeviceFlags.test(rsDisableObjectsAsCrows); + const auto load_queues_drained = [this]() + { + if (Objects.o_count() != 0 || !Objects.destroy_queues_empty() || !game_spawn_queue.empty() || + !net_msg_Empty()) + { + return false; + } +#ifdef SPAWN_ANTIFREEZE + xrSRWLockGuard guard(prefetch_lock, true); + return game_events->queue.empty() && !spawn_prefetch_busy && prefetch_events->empty() && + spawn_events->queue.empty() && spawn_events_data->empty(); +#else + return game_events->queue.empty(); +#endif + }; int loop = 5; while (loop) @@ -62,9 +77,18 @@ void CLevel::remove_objects() Msg ("Update objects list..."); #endif // #ifdef DEBUG Objects.dump_all_objects(); + + // Direct single-player delivery is synchronous. Once every visible + // queue is drained, the remaining passes are empty; preserve their + // only cumulative side effect by advancing the frame counter. + if (IsGameTypeSingle() && psNET_direct_connect && load_queues_drained()) + { + Device.dwFrame += 19 - i; + break; + } } - if (Objects.o_count() == 0) + if (load_queues_drained()) break; else { diff --git a/src/xrGame/Level_network_messages.cpp b/src/xrGame/Level_network_messages.cpp index b28dcb1183..85d39e0d7f 100644 --- a/src/xrGame/Level_network_messages.cpp +++ b/src/xrGame/Level_network_messages.cpp @@ -16,6 +16,7 @@ #include "file_transfer.h" #include "message_filter.h" #include "../xrphysics/iphworld.h" +#include "../xrEngine/x_ray.h" extern LPCSTR map_ver_string; @@ -308,6 +309,8 @@ void CLevel::ClientReceive() case M_LOAD_GAME: case M_CHANGE_LEVEL: { + if (m_type == M_LOAD_GAME) + pApp->LoadSessionBegin("load-game"); #ifdef DEBUG Msg("--- Changing level message received..."); #endif // #ifdef DEBUG @@ -324,6 +327,7 @@ void CLevel::ClientReceive() CSavedGameWrapper wrapper(saved_name); if (wrapper.level_id() == ai().level_graph().level_id()) { + pApp->LoadSessionSetScenario("quickload"); Engine.Event.Defer("Game:QuickLoad", size_t(xr_strdup(saved_name)), 0); break; diff --git a/src/xrGame/Level_network_spawn.cpp b/src/xrGame/Level_network_spawn.cpp index e961fc488a..ff0d9f5f46 100644 --- a/src/xrGame/Level_network_spawn.cpp +++ b/src/xrGame/Level_network_spawn.cpp @@ -12,6 +12,9 @@ void CLevel::cl_Process_Spawn(NET_Packet& P) { + #ifdef SPAWN_ANTIFREEZE + PublishPreparedClientSpawnResource(P); + #endif // Begin analysis shared_str s_name; P.r_stringZ(s_name); diff --git a/src/xrGame/Level_network_start_client.cpp b/src/xrGame/Level_network_start_client.cpp index 458445be7c..ce91251045 100644 --- a/src/xrGame/Level_network_start_client.cpp +++ b/src/xrGame/Level_network_start_client.cpp @@ -126,7 +126,10 @@ bool CLevel::net_start_client3() deny_m_spawn = FALSE; // Load level - R_ASSERT2(Load(level_id), "Loading failed."); + pApp->LoadSessionPhaseBegin(LoadSessionNativeLevel); + const bool level_loaded = Load(level_id); + pApp->LoadSessionPhaseEnd(LoadSessionNativeLevel); + R_ASSERT2(level_loaded, "Loading failed."); map_data.m_level_geom_crc32 = 0; if (!IsGameTypeSingle()) CalculateLevelCrc32(); @@ -226,8 +229,10 @@ bool CLevel::net_start_client5() //Device.Resources->DeferredLoad (FALSE); Device.m_pRender->DeferredLoad(FALSE); //Device.Resources->DeferredUpload (); + pApp->LoadSessionPhaseBegin(LoadSessionResourceWait); Device.m_pRender->ResourcesDeferredUpload(); LL_CheckTextures(); + pApp->LoadSessionPhaseEnd(LoadSessionResourceWait); } sended_request_connection_data = FALSE; deny_m_spawn = TRUE; diff --git a/src/xrGame/Level_start.cpp b/src/xrGame/Level_start.cpp index 32a0f21728..1ebfa8b21b 100644 --- a/src/xrGame/Level_start.cpp +++ b/src/xrGame/Level_start.cpp @@ -157,17 +157,20 @@ bool CLevel::net_start2() PROF_EVENT("CLevel::net_start2"); if (net_start_result_total && m_caServerOptions.size()) { + pApp->LoadSessionPhaseBegin(LoadSessionServerLua); GameDescriptionData game_descr; if ((m_connect_server_err = Server->Connect(m_caServerOptions, game_descr)) != xrServer::ErrNoError) { net_start_result_total = false; Msg("! Failed to start server."); + pApp->LoadSessionPhaseEnd(LoadSessionServerLua); return true; } Server->SLS_Default(); map_data.m_name = Server->level_name(m_caServerOptions); if (!g_dedicated_server) g_pGamePersistent->LoadTitle(true, map_data.m_name); + pApp->LoadSessionPhaseEnd(LoadSessionServerLua); } return true; } @@ -275,6 +278,7 @@ bool CLevel::net_start6() else { Msg("! Failed to start client. Check the connection or level existance."); + pApp->LoadSessionCancel("net start failed"); if (m_connect_server_err == xrServer::ErrConnect && !psNET_direct_connect && !g_dedicated_server) { diff --git a/src/xrGame/MainMenu.cpp b/src/xrGame/MainMenu.cpp index db1b119f7d..c4ccd5fc2d 100644 --- a/src/xrGame/MainMenu.cpp +++ b/src/xrGame/MainMenu.cpp @@ -231,6 +231,7 @@ void CMainMenu::Activate(bool bActivate) Console->Execute("stat_memory"); Device.seqRender.Add(this, 4); // 1-console 2-cursor 3-tutorial + LogStartupMenuReady(); } else @@ -291,6 +292,8 @@ void CMainMenu::Activate(bool bActivate) bool CMainMenu::ReloadUI() { + CTimer timer; + timer.Start(); if (m_startDialog) { if (m_startDialog->IsShown()) @@ -310,6 +313,7 @@ bool CMainMenu::ReloadUI() m_startDialog->ShowDialog(true); m_activatedScreenRatio = (float)Device.dwWidth / (float)Device.dwHeight > (UI_BASE_WIDTH / UI_BASE_HEIGHT + 0.01f); + Msg("* [STARTUP] main menu ReloadUI: %d ms", timer.GetElapsed_ms()); return true; } diff --git a/src/xrGame/Missile.h b/src/xrGame/Missile.h index 3c34ed102d..9bb14d040d 100644 --- a/src/xrGame/Missile.h +++ b/src/xrGame/Missile.h @@ -68,6 +68,7 @@ class CMissile : public CHudItemObject //для сети virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } protected: //время нахождения в текущем состоянии diff --git a/src/xrGame/ScriptXMLInit.cpp b/src/xrGame/ScriptXMLInit.cpp index 4bd7da4f1d..4bf1af3461 100644 --- a/src/xrGame/ScriptXMLInit.cpp +++ b/src/xrGame/ScriptXMLInit.cpp @@ -52,17 +52,19 @@ LPCSTR clearBOM(LPCSTR s) { // demonized // Send XML file contents to Lua for edit -void XMLLuaCallback(CXml &m_xml, LPCSTR xml_string) { - if (!xml_string) return; - if (xr_strlen(xml_string) == 0) return; +bool XMLLuaCallback(CXml& m_xml, LPCSTR xml_string, xr_string& transformed) { + if (!xml_string) return false; + if (xr_strlen(xml_string) == 0) return false; xml_string = clearBOM(xml_string); ::luabind::functor funct; if (ai().script_engine().functor("_G.COnXmlRead", funct)) { LPCSTR res = funct(m_xml.m_xml_file_name, xml_string); - //Msg("XMLLuaCallback, xml %s, contents %s", m_xml.m_xml_file_name, res); - m_xml.LoadFromString(res); + R_ASSERT(res); + transformed = res; + return true; } + return false; } void CScriptXmlInit::ParseFile(LPCSTR xml_file) diff --git a/src/xrGame/Spectator.h b/src/xrGame/Spectator.h index a7eeeb94d1..fb635f998e 100644 --- a/src/xrGame/Spectator.h +++ b/src/xrGame/Spectator.h @@ -74,6 +74,7 @@ class CSpectator : virtual CSpectator* cast_spectator() { return this; } virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } void GetSpectatorString(string1024& pStr); diff --git a/src/xrGame/Weapon.h b/src/xrGame/Weapon.h index bca657fd49..b531dc5943 100644 --- a/src/xrGame/Weapon.h +++ b/src/xrGame/Weapon.h @@ -70,6 +70,7 @@ class CWeapon : public CHudItemObject, virtual void net_Export(NET_Packet& P); virtual void net_Import(NET_Packet& P); virtual void net_Relcase(CObject* object) override; + virtual bool net_RelcaseNeeded() const override { return true; } virtual CWeapon* cast_weapon() { return this; } virtual CWeaponBinoculars* cast_weapon_binoculars() { return nullptr; } diff --git a/src/xrGame/WeaponBinoculars.h b/src/xrGame/WeaponBinoculars.h index 81bbe386b5..7e29b8def0 100644 --- a/src/xrGame/WeaponBinoculars.h +++ b/src/xrGame/WeaponBinoculars.h @@ -42,6 +42,7 @@ class CWeaponBinoculars : public CWeaponCustomPistol virtual CWeaponBinoculars* cast_weapon_binoculars() { return this; } virtual bool GetBriefInfo(II_BriefInfo& info); virtual void net_Relcase(CObject* object); + virtual bool net_RelcaseNeeded() const override { return true; } protected: CBinocularsVision* m_binoc_vision; diff --git a/src/xrGame/ai/monsters/basemonster/base_monster.h b/src/xrGame/ai/monsters/basemonster/base_monster.h index 08e13be9ed..39d7cc78ce 100644 --- a/src/xrGame/ai/monsters/basemonster/base_monster.h +++ b/src/xrGame/ai/monsters/basemonster/base_monster.h @@ -102,6 +102,7 @@ class CBaseMonster : public CCustomMonster, public CStepManager virtual void net_Export(NET_Packet& P); virtual void net_Import(NET_Packet& P); virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } //save/load server serialization virtual void save(NET_Packet& output_packet) { inherited::save(output_packet); } diff --git a/src/xrGame/ai/monsters/burer/burer.h b/src/xrGame/ai/monsters/burer/burer.h index 216cff9b59..233612243e 100644 --- a/src/xrGame/ai/monsters/burer/burer.h +++ b/src/xrGame/ai/monsters/burer/burer.h @@ -135,6 +135,7 @@ class CBurer : public CBaseMonster, virtual void net_Destroy(); virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } virtual void shedule_Update(u32 dt); virtual void UpdateCL(); virtual void Hit(SHit* pHDS); diff --git a/src/xrGame/ai/monsters/controller/controller.h b/src/xrGame/ai/monsters/controller/controller.h index c57d98c1f7..c1fb22fd9e 100644 --- a/src/xrGame/ai/monsters/controller/controller.h +++ b/src/xrGame/ai/monsters/controller/controller.h @@ -83,6 +83,7 @@ class CController : public CBaseMonster, virtual void net_Destroy(); virtual BOOL net_Spawn(CSE_Abstract* DC); virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } virtual void CheckSpecParams(u32 spec_params); virtual void InitThink(); diff --git a/src/xrGame/ai/monsters/poltergeist/poltergeist.h b/src/xrGame/ai/monsters/poltergeist/poltergeist.h index e52abf2d36..d4ad98bd5f 100644 --- a/src/xrGame/ai/monsters/poltergeist/poltergeist.h +++ b/src/xrGame/ai/monsters/poltergeist/poltergeist.h @@ -61,6 +61,7 @@ class CPoltergeist : public CBaseMonster, virtual BOOL net_Spawn(CSE_Abstract* DC); virtual void net_Destroy(); virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } virtual void UpdateCL(); virtual void shedule_Update(u32 dt); diff --git a/src/xrGame/ai/stalker/ai_stalker.h b/src/xrGame/ai/stalker/ai_stalker.h index e8fbc7c005..614fba95b1 100644 --- a/src/xrGame/ai/stalker/ai_stalker.h +++ b/src/xrGame/ai/stalker/ai_stalker.h @@ -205,6 +205,7 @@ class CAI_Stalker : virtual void net_Save(NET_Packet& P); virtual BOOL net_SaveRelevant(); virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } //save/load server serialization virtual void save(NET_Packet& output_packet); diff --git a/src/xrGame/alife_graph_registry.cpp b/src/xrGame/alife_graph_registry.cpp index a3fdc79d7a..835dd14038 100644 --- a/src/xrGame/alife_graph_registry.cpp +++ b/src/xrGame/alife_graph_registry.cpp @@ -9,16 +9,18 @@ #include "stdafx.h" #include "alife_graph_registry.h" #include "../xrEngine/x_ray.h" +#include "../xrEngine/IGame_Persistent.h" #include "level.h" using namespace ALife; -xr_task_group level_load; CALifeGraphRegistry::CALifeGraphRegistry() { m_level = 0; m_process_time = 0; m_actor = 0; + m_level_load_started = false; + m_level_id = -1; } CALifeGraphRegistry::~CALifeGraphRegistry() @@ -69,18 +71,9 @@ void CALifeGraphRegistry::update(CSE_ALifeDynamicObject* object) void CALifeGraphRegistry::setup_current_level() { - u8 level_id = ai().game_graph().vertex(actor()->m_tGraphID)->level_id(); + start_level_load(); - GameGraph::LEVEL_MAP::const_iterator I = ai().game_graph().header().levels().find(level_id); - Level().set_name((*I).second.name()); - int levelid = pApp->Level_ID(*(*I).second.name(), "1.0", true); - static DWORD this_thread_id = 0; - this_thread_id = GetCurrentThreadId(); - level_load.run([=]() - { - if (this_thread_id != GetCurrentThreadId()) { PROF_THREAD("X-Ray PPL Thread") } - Level().Load(levelid); - }); + u8 level_id = ai().game_graph().vertex(actor()->m_tGraphID)->level_id(); m_level = xr_new(level_id); level().set_process_time(m_process_time); @@ -101,6 +94,8 @@ void CALifeGraphRegistry::setup_current_level() m_temp.clear(); } + + GameGraph::LEVEL_MAP::const_iterator I = ai().game_graph().header().levels().find(level_id); R_ASSERT2(ai().game_graph().header().levels().end() != I, "Graph point level ID not found!"); int id = pApp->Level_ID(*(*I).second.name(), "1.0", true); @@ -108,6 +103,46 @@ void CALifeGraphRegistry::setup_current_level() ai().load(*(*I).second.name()); } +void CALifeGraphRegistry::prepare_current_level(CSE_ALifeCreatureActor* actor) +{ + VERIFY(actor); + m_actor = actor; + start_level_load(); +} + +void CALifeGraphRegistry::start_level_load() +{ + if (m_level_load_started) + return; + + m_level_load_started = true; + pApp->LoadSessionPhaseBegin(LoadSessionNativeLevel); + u8 level_id = ai().game_graph().vertex(actor()->m_tGraphID)->level_id(); + + GameGraph::LEVEL_MAP::const_iterator I = ai().game_graph().header().levels().find(level_id); + Level().set_name((*I).second.name()); + m_level_id = pApp->Level_ID(*(*I).second.name(), "1.0", true); + xr_string level_path = FS.get_path("$game_levels$")->m_Path; + level_path += *(*I).second.name(); + level_path += "\\"; + ::Render->level_Prepare(level_path.c_str()); + Level().BeginGameSpecificPrepare(level_path.c_str()); + // Drain startup producers while still on owner. The actual level commit is + // deferred until ALife/Lua has finished; only immutable prepare runs now. + WaitGamePrefetch(); + Device.m_pRender->ResourcesPrepareLoad(); + if (Level().bReady) + Msg("* [LEVEL PREPARE] static level already active; native level load skipped"); +} + +void CALifeGraphRegistry::finish_level_load() +{ + if (!m_level_load_started || Level().bReady) + return; + R_ASSERT(m_level_id >= 0); + R_ASSERT(Level().Load(m_level_id)); +} + void CALifeGraphRegistry::attach(CSE_Abstract& object, CSE_ALifeInventoryItem* item, GameGraph::_GRAPH_ID game_vertex_id, bool alife_query, bool add_children) { diff --git a/src/xrGame/alife_graph_registry.h b/src/xrGame/alife_graph_registry.h index 1ed4bfa504..f0368ad0d8 100644 --- a/src/xrGame/alife_graph_registry.h +++ b/src/xrGame/alife_graph_registry.h @@ -52,11 +52,14 @@ class CALifeGraphRegistry TERRAIN_REGISTRY m_terrain[GameGraph::LOCATION_TYPE_COUNT][GameGraph::LOCATION_COUNT]; CALifeLevelRegistry* m_level; CSE_ALifeCreatureActor* m_actor; + bool m_level_load_started; + int m_level_id; float m_process_time; xr_vector m_temp; protected: void setup_current_level(); + void start_level_load(); template IC void iterate(C& c, const F& f); @@ -64,6 +67,8 @@ class CALifeGraphRegistry CALifeGraphRegistry(); virtual ~CALifeGraphRegistry(); void on_load(); + void prepare_current_level(CSE_ALifeCreatureActor* actor); + void finish_level_load(); void update(CSE_ALifeDynamicObject* object); void attach(CSE_Abstract& object, CSE_ALifeInventoryItem* item, GameGraph::_GRAPH_ID game_vertex_id, bool alife_query = true, bool add_children = true); diff --git a/src/xrGame/alife_simulator.cpp b/src/xrGame/alife_simulator.cpp index f24b9efbc7..ac66d425ac 100644 --- a/src/xrGame/alife_simulator.cpp +++ b/src/xrGame/alife_simulator.cpp @@ -15,7 +15,10 @@ #include "mainmenu.h" #include "object_factory.h" #include "alife_object_registry.h" +#include "saved_game_wrapper.h" +#include "level.h" #include "../xrEngine/xr_ioconsole.h" +#include "../xrEngine/Render.h" #ifdef DEBUG # include "moving_objects.h" @@ -47,15 +50,28 @@ CALifeSimulator::CALifeSimulator(xrServer* server, shared_str* command_line) : CALifeSimulatorBase(server, alife_section) { PROF_EVENT("CALifeSimulator::CALifeSimulator"); + typedef IGame_Persistent::params params; + params& p = g_pGamePersistent->m_game_params; + if (!xr_strcmp(p.m_new_or_load, "load")) + { + CSavedGameWrapper saved_game(p.m_game_or_spawn); + if (saved_game.level_name() && saved_game.level_name()[0]) + { + xr_string level_path = FS.get_path("$game_levels$")->m_Path; + level_path += saved_game.level_name(); + level_path += "\\"; + ::Render->level_Prepare(level_path.c_str()); + Level().BeginGameSpecificPrepare(level_path.c_str()); + } + CALifeStorageManager::prepare_load(p.m_game_or_spawn); + } + restart_all(); ai().set_alife(this); setup_command_line(command_line); - typedef IGame_Persistent::params params; - params& p = g_pGamePersistent->m_game_params; - R_ASSERT2( xr_strlen(p.m_game_or_spawn) && !xr_strcmp(p.m_alife,"alife") && diff --git a/src/xrGame/alife_storage_manager.cpp b/src/xrGame/alife_storage_manager.cpp index 28699622e3..9312344e48 100644 --- a/src/xrGame/alife_storage_manager.cpp +++ b/src/xrGame/alife_storage_manager.cpp @@ -38,8 +38,78 @@ using namespace ALife; extern string_path g_last_saved_game; +namespace +{ +struct prepared_save +{ + NativeLoadExecutor::Batch batch; + xr_task_group fallback_task; + xr_vector data; + xr_string name; + string_path file_name{}; + bool active = false; + bool valid = false; + + void wait() + { + if (batch.Valid()) + NativeLoadExecutor::Instance().Wait(batch); + fallback_task.wait(); + } +} g_prepared_save; + +void cleanup_prepared_save() +{ + try + { + g_prepared_save.wait(); + } + catch (...) + { + } + g_prepared_save.data.clear(); + g_prepared_save.name.clear(); + g_prepared_save.file_name[0] = 0; + g_prepared_save.active = false; + g_prepared_save.valid = false; + g_prepared_save.batch = {}; +} +} + +void CALifeStorageManager::prepare_load(LPCSTR save_name) +{ + cleanup_prepared_save(); + g_prepared_save.name = save_name; + g_prepared_save.active = true; + g_prepared_save.valid = false; + CSavedGameWrapper::saved_game_full_name(save_name, g_prepared_save.file_name); + NativeLoadExecutor& executor = NativeLoadExecutor::Instance(); + g_prepared_save.batch = executor.BeginBatch(executor.CurrentGeneration()); + auto prepare = []() + { + IReader* stream = FS.r_open(g_prepared_save.file_name); + if (!stream || !CSavedGameWrapper::valid_saved_game(*stream)) + { + if (stream) + FS.r_close(stream); + return; + } + + u32 source_count = stream->r_u32(); + g_prepared_save.data.resize(source_count); + rtc_decompress(g_prepared_save.data.data(), source_count, stream->pointer(), stream->length() - 3 * sizeof(u32)); + FS.r_close(stream); + g_prepared_save.valid = true; + }; + if (g_prepared_save.batch.Valid()) + executor.Submit(g_prepared_save.batch, NativeLoadPriority::Spawn, std::move(prepare)); + else + g_prepared_save.fallback_task.run(std::move(prepare)); +} + CALifeStorageManager::~CALifeStorageManager() { + cleanup_prepared_save(); *g_last_saved_game = 0; } @@ -145,6 +215,17 @@ void CALifeStorageManager::load(void* buffer, const u32& buffer_size, LPCSTR fil CALifeObjectRegistry::OBJECT_REGISTRY::iterator B = objects().objects().begin(); CALifeObjectRegistry::OBJECT_REGISTRY::iterator E = objects().objects().end(); CALifeObjectRegistry::OBJECT_REGISTRY::iterator I; + for (I = B; I != E; ++I) + { + CSE_ALifeCreatureActor* actor = smart_cast((*I).second); + if (actor) + { + graph().prepare_current_level(actor); + break; + } + } + VERIFY(I != E); + for (I = B; I != E; ++I) { ALife::_OBJECT_ID id = (*I).second->ID; @@ -194,17 +275,18 @@ bool CALifeStorageManager::load(LPCSTR save_name_no_check) xr_strcpy(g_last_saved_game, save_name); xr_strcpy(g_bug_report_file, file_name); - IReader* stream; - stream = FS.r_open(file_name); - if (!stream) + const bool prepared = g_prepared_save.active && g_prepared_save.name == save_name; + IReader* stream = prepared ? nullptr : FS.r_open(file_name); + if (!prepared && !stream) { Msg("* Cannot find saved game %s", file_name); xr_strcpy(m_save_name, save); return (false); } - CHECK_OR_EXIT(CSavedGameWrapper::valid_saved_game(*stream), - make_string("%s\nSaved game version mismatch or saved game is corrupted",file_name)); + if (!prepared) + CHECK_OR_EXIT(CSavedGameWrapper::valid_saved_game(*stream), + make_string("%s\nSaved game version mismatch or saved game is corrupted",file_name)); /* string512 temp; strconcat (sizeof(temp),temp,CStringTable().translate("st_loading_saved_game").c_str()," \"",save_name,SAVE_EXTENSION,"\""); @@ -215,12 +297,23 @@ bool CALifeStorageManager::load(LPCSTR save_name_no_check) unload(); reload(m_section); - u32 source_count = stream->r_u32(); - void* source_data = xr_malloc(source_count); - rtc_decompress(source_data, source_count, stream->pointer(), stream->length() - 3 * sizeof(u32)); - FS.r_close(stream); - load(source_data, source_count, file_name); - xr_free(source_data); + if (prepared) + { + g_prepared_save.wait(); + CHECK_OR_EXIT(g_prepared_save.valid, + make_string("%s\nSaved game version mismatch or saved game is corrupted",file_name)); + load(g_prepared_save.data.data(), g_prepared_save.data.size(), file_name); + cleanup_prepared_save(); + } + else + { + u32 source_count = stream->r_u32(); + void* source_data = xr_malloc(source_count); + rtc_decompress(source_data, source_count, stream->pointer(), stream->length() - 3 * sizeof(u32)); + FS.r_close(stream); + load(source_data, source_count, file_name); + xr_free(source_data); + } groups().on_after_game_load(); diff --git a/src/xrGame/alife_storage_manager.h b/src/xrGame/alife_storage_manager.h index f31f08c5a0..c46bd1647e 100644 --- a/src/xrGame/alife_storage_manager.h +++ b/src/xrGame/alife_storage_manager.h @@ -29,6 +29,7 @@ class CALifeStorageManager : public virtual CALifeSimulatorBase public: IC CALifeStorageManager(xrServer* server, LPCSTR section); virtual ~CALifeStorageManager(); + static void prepare_load(LPCSTR save_name); bool load(LPCSTR save_name = 0); void save(LPCSTR save_name = 0, bool update_name = true); void save(NET_Packet& net_packet); diff --git a/src/xrGame/alife_update_manager.cpp b/src/xrGame/alife_update_manager.cpp index f2831b2b63..fefeb118e2 100644 --- a/src/xrGame/alife_update_manager.cpp +++ b/src/xrGame/alife_update_manager.cpp @@ -161,6 +161,7 @@ bool CALifeUpdateManager::change_level(NET_Packet& net_packet) { if (m_changing_level) return (false); + pApp->LoadSessionBegin("level-change"); #ifdef ENGINE_LUA_ALIFE_UPDAGE_MANAGER_CALLBACKS ::luabind::functor funct; @@ -194,6 +195,17 @@ bool CALifeUpdateManager::change_level(NET_Packet& net_packet) net_packet.r(&graph().actor()->m_tNodeID, sizeof(graph().actor()->m_tNodeID)); net_packet.r_vec3(graph().actor()->o_Position); net_packet.r_vec3(graph().actor()->o_Angle); + if (ai().game_graph().valid_vertex_id(graph().actor()->m_tGraphID)) + { + const GameGraph::_LEVEL_ID destination_level = + ai().game_graph().vertex(graph().actor()->m_tGraphID)->level_id(); + xr_string destination_path = FS.get_path("$game_levels$")->m_Path; + destination_path += *ai().game_graph().header().level(destination_level).name(); + destination_path += "\\"; + pApp->LoadSessionSetScenario(::Render->level_StaticCacheReady(destination_path.c_str()) ? + "visited-transition" : "unseen-transition"); + ::Render->level_Prepare(destination_path.c_str()); + } Level().ClientSave(); @@ -262,7 +274,6 @@ void CALifeUpdateManager::new_game(LPCSTR save_name) can_register_objects(false); spawn_new_objects(); can_register_objects(true); - CALifeObjectRegistry::OBJECT_REGISTRY::iterator I = objects().objects().begin(); CALifeObjectRegistry::OBJECT_REGISTRY::iterator E = objects().objects().end(); for (; I != E; ++I) @@ -275,8 +286,6 @@ void CALifeUpdateManager::new_game(LPCSTR save_name) Msg("* New game is successfully created!"); } -extern xr_task_group level_load; - void CALifeUpdateManager::load(LPCSTR game_name, bool no_assert, bool new_only) { PROF_EVENT("Load Alife Simulator"); @@ -304,7 +313,18 @@ void CALifeUpdateManager::load(LPCSTR game_name, bool no_assert, bool new_only) #endif // g_pGamePersistent->LoadTitle ("st_server_connecting"); g_pGamePersistent->LoadTitle(true, g_pGameLevel->name()); - level_load.wait(); + // Immutable level data was prepared in parallel with ALife/Lua. Publish it + // only now, after the Lua lifecycle has completed, on the owner thread. + try + { + graph().finish_level_load(); + } + catch (...) + { + ::Render->level_AbortAsyncLoad(); + throw; + } + pApp->LoadSessionPhaseEnd(LoadSessionNativeLevel); } void CALifeUpdateManager::reload(LPCSTR section) diff --git a/src/xrGame/entity_alive.h b/src/xrGame/entity_alive.h index d10ce5766f..72a57a2236 100644 --- a/src/xrGame/entity_alive.h +++ b/src/xrGame/entity_alive.h @@ -197,6 +197,7 @@ class CEntityAlive : public CEntity virtual CVisualMemoryManager* visual_memory() const { return (0); } virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } public: virtual Fvector predict_position(const float& time_to_check) const; diff --git a/src/xrGame/helicopter.h b/src/xrGame/helicopter.h index 0295f1649e..6f823835a8 100644 --- a/src/xrGame/helicopter.h +++ b/src/xrGame/helicopter.h @@ -294,6 +294,7 @@ class CHelicopter : public CEntity, { }; virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } virtual void save(NET_Packet& output_packet); virtual void load(IReader& input_packet); diff --git a/src/xrGame/level_sounds.cpp b/src/xrGame/level_sounds.cpp index 8875c04ddf..92438d7b17 100644 --- a/src/xrGame/level_sounds.cpp +++ b/src/xrGame/level_sounds.cpp @@ -159,20 +159,42 @@ CLevelSoundManager::CLevelSoundManager() void CLevelSoundManager::Load() { - // static level sounds - VERIFY(m_StaticSounds.empty()); - string_path fn; - if (FS.exist(fn, "$level$", "level.snd_static")) + PreparedData data; + Prepare(FS.get_path("$level$")->m_Path, data); + Commit(data); +} + +void CLevelSoundManager::Prepare(LPCSTR canonical_level_path, PreparedData& data) const +{ + xr_string file_name = canonical_level_path; + if (!file_name.empty() && file_name.back() != '\\' && file_name.back() != '/') + file_name += '\\'; + file_name += "level.snd_static"; + if (FS.exist(file_name.c_str())) { - IReader* F = FS.r_open(fn); + IReader* F = FS.r_open(file_name.c_str()); u32 chunk = 0; for (IReader* OBJ = F->open_chunk_iterator(chunk); OBJ; OBJ = F->open_chunk_iterator(chunk, OBJ)) { - m_StaticSounds.push_back(SStaticSound()); - m_StaticSounds.back().Load(*OBJ); + data.static_sound_chunks.emplace_back(); + xr_vector& bytes = data.static_sound_chunks.back(); + bytes.resize(OBJ->length()); + OBJ->r(bytes.data(), bytes.size()); } FS.r_close(F); } +} + +void CLevelSoundManager::Commit(const PreparedData& data) +{ + // Sound objects and their backend registrations stay on the owner thread. + VERIFY(m_StaticSounds.empty()); + for (const xr_vector& bytes : data.static_sound_chunks) + { + IReader reader(const_cast(bytes.data()), bytes.size()); + m_StaticSounds.emplace_back(); + m_StaticSounds.back().Load(reader); + } // music m_CurrentTrack = -1; diff --git a/src/xrGame/level_sounds.h b/src/xrGame/level_sounds.h index 79359b671f..8b37cad17d 100644 --- a/src/xrGame/level_sounds.h +++ b/src/xrGame/level_sounds.h @@ -40,6 +40,12 @@ struct SMusicTrack class CLevelSoundManager { +public: + struct PreparedData + { + xr_vector> static_sound_chunks; + }; +private: DEFINE_VECTOR(SStaticSound, StaticSoundsVec, StaticSoundsVecIt); StaticSoundsVec m_StaticSounds; DEFINE_VECTOR(SMusicTrack, MusicTrackVec, MusicTrackVecIt); @@ -49,6 +55,8 @@ class CLevelSoundManager int m_CurrentTrack; public: CLevelSoundManager(); + void Prepare(LPCSTR canonical_level_path, PreparedData& data) const; + void Commit(const PreparedData& data); void Load(); void Unload(); void __stdcall Update(); diff --git a/src/xrGame/script_zone.h b/src/xrGame/script_zone.h index 5b57af635b..77c5730d3a 100644 --- a/src/xrGame/script_zone.h +++ b/src/xrGame/script_zone.h @@ -26,6 +26,7 @@ class CScriptZone : public CSpaceRestrictor, public Feel::Touch virtual BOOL net_Spawn(CSE_Abstract* DC); virtual void net_Destroy(); virtual void net_Relcase(CObject* O); + virtual bool net_RelcaseNeeded() const override { return true; } virtual void shedule_Update(u32 dt); virtual void feel_touch_new(CObject* O); virtual void feel_touch_delete(CObject* O); diff --git a/src/xrGame/xrServer.h b/src/xrGame/xrServer.h index 79d0e0385d..28c1cc2055 100644 --- a/src/xrGame/xrServer.h +++ b/src/xrGame/xrServer.h @@ -184,7 +184,19 @@ class xrServer : public IPureServer return (m_tID_Generator.vfFreeID(ID, time)); } - void Perform_connect_spawn(CSE_Abstract* E, xrClientData* to, NET_Packet& P); + struct PreparedClientSpawn + { + CSE_Abstract* entity; + shared_str section; + shared_str actual_visual; + shared_str ltx_visual; + xr_vector textures; + NativeLoadExecutor::Batch resource_batch; + u16 id; + u16 parent_id; + }; + void Prepare_connect_spawn(CSE_Abstract* E, xr_vector& prepared); + void Perform_connect_spawn(const PreparedClientSpawn& prepared, xrClientData* to, NET_Packet& P); void Perform_transfer(NET_Packet& PR, NET_Packet& PT, CSE_Abstract* what, CSE_Abstract* from, CSE_Abstract* to); void Perform_reject(CSE_Abstract* what, CSE_Abstract* from, int delta); void Perform_destroy(CSE_Abstract* tpSE_Abstract, u32 mode); diff --git a/src/xrGame/xrServer_CL_connect.cpp b/src/xrGame/xrServer_CL_connect.cpp index 28891de95b..6a2471e1f4 100644 --- a/src/xrGame/xrServer_CL_connect.cpp +++ b/src/xrGame/xrServer_CL_connect.cpp @@ -5,29 +5,40 @@ #include "xrServer_Objects_Alife_Monsters.h" #include "Level.h" +extern ENGINE_API bool g_dedicated_server; -void xrServer::Perform_connect_spawn(CSE_Abstract* E, xrClientData* CL, NET_Packet& P) +void xrServer::Prepare_connect_spawn(CSE_Abstract* E, xr_vector& prepared) { - P.B.count = 0; xr_vector::iterator it = std::find(conn_spawned_ids.begin(), conn_spawned_ids.end(), E->ID); if (it != conn_spawned_ids.end()) - { - //. Msg("Rejecting redundant SPAWN data [%d]", E->ID); return; - } conn_spawned_ids.push_back(E->ID); if (E->net_Processed) return; if (E->s_flags.is(M_SPAWN_OBJECT_PHANTOM)) return; - //. Msg("Perform connect spawn [%d][%s]", E->ID, E->s_name.c_str()); - - // Connectivity order CSE_Abstract* Parent = ID_to_entity(E->ID_Parent); - if (Parent) Perform_connect_spawn(Parent, CL, P); + if (Parent) + Prepare_connect_spawn(Parent, prepared); + + PreparedClientSpawn spawn = {}; + spawn.entity = E; + spawn.section = E->s_name; + spawn.id = E->ID; + spawn.parent_id = E->ID_Parent; + if (CSE_Visual* visual = E->visual()) + spawn.actual_visual = visual->get_visual(); + if (pSettings->section_exist(E->s_name.c_str()) && pSettings->line_exist(E->s_name.c_str(), "visual")) + spawn.ltx_visual = pSettings->r_string(E->s_name.c_str(), "visual"); + prepared.push_back(std::move(spawn)); +} + +void xrServer::Perform_connect_spawn(const PreparedClientSpawn& prepared, xrClientData* CL, NET_Packet& P) +{ + CSE_Abstract* E = prepared.entity; + P.B.count = 0; - // Process Flags16 save = E->s_flags; //------------------------------------------------- E->s_flags.set(M_SPAWN_UPDATE,TRUE); @@ -77,10 +88,72 @@ void xrServer::SendConnectionData(IClient* _CL) conn_spawned_ids.clear(); xrClientData* CL = (xrClientData*)_CL; NET_Packet P; - // Replicate current entities on to this client + xr_vector prepared; + prepared.reserve(entities.size()); xrS_entities::iterator I = entities.begin(), E = entities.end(); for (; I != E; ++I) I->second->net_Processed = FALSE; - for (I = entities.begin(); I != E; ++I) Perform_connect_spawn(I->second, CL, P); + for (I = entities.begin(); I != E; ++I) + Prepare_connect_spawn(I->second, prepared); + NativeLoadExecutor& executor = NativeLoadExecutor::Instance(); + string_path resolved_level_path; + FS.update_path(resolved_level_path, "$level$", ""); + const xr_string level_path = resolved_level_path; + const bool prepare_local_resources = !g_dedicated_server && CL == GetServerClient(); + try + { + if (prepare_local_resources) + for (PreparedClientSpawn& spawn : prepared) + { + spawn.resource_batch = executor.BeginBatch(executor.CurrentGeneration()); + auto prepare_resources = [&spawn, level_path]() + { + if (spawn.actual_visual.size()) + ::Render->model_CollectTextures(spawn.actual_visual.c_str(), level_path.c_str(), spawn.textures); + if (spawn.ltx_visual.size() && spawn.ltx_visual != spawn.actual_visual) + ::Render->model_CollectTextures(spawn.ltx_visual.c_str(), level_path.c_str(), spawn.textures); + std::sort(spawn.textures.begin(), spawn.textures.end()); + spawn.textures.erase(std::unique(spawn.textures.begin(), spawn.textures.end()), spawn.textures.end()); + for (const xr_string& texture : spawn.textures) + Device.m_pRender->ResourcesPrefetchCreateTexture(texture.c_str(), level_path.c_str()); + }; + if (spawn.resource_batch.Valid()) + executor.Submit(spawn.resource_batch, NativeLoadPriority::Spawn, std::move(prepare_resources)); + else + prepare_resources(); + } + + u32 order_hash = 0; + u32 texture_count = 0; + u32 model_count = 0; + for (const PreparedClientSpawn& spawn : prepared) + { + if (prepare_local_resources && spawn.resource_batch.Valid()) + executor.Wait(spawn.resource_batch); + #ifdef SPAWN_ANTIFREEZE + if (prepare_local_resources) + Level().RegisterPreparedClientSpawnResource(spawn.id, spawn.parent_id, spawn.section, + spawn.actual_visual, spawn.ltx_visual, level_path.c_str()); + #endif + model_count += spawn.actual_visual.size() || spawn.ltx_visual.size() ? 1u : 0u; + texture_count += static_cast(spawn.textures.size()); + order_hash = crc32(&spawn.id, sizeof(spawn.id), order_hash); + order_hash = crc32(&spawn.parent_id, sizeof(spawn.parent_id), order_hash); + order_hash = crc32(spawn.section.c_str(), xr_strlen(spawn.section.c_str()), order_hash); + Perform_connect_spawn(spawn, CL, P); + } + Msg("* [client-spawn] prepared=%u models=%u textures=%u order_hash=%08x", static_cast(prepared.size()), + model_count, texture_count, order_hash); + } + catch (...) + { + const std::exception_ptr failure = std::current_exception(); + // Submitted workers keep references to vector elements. Drain every batch + // before the vector can unwind, even when one task failed first. + for (const PreparedClientSpawn& spawn : prepared) + if (spawn.resource_batch.Valid()) + try { executor.Wait(spawn.resource_batch); } catch (...) {} + std::rethrow_exception(failure); + } // Start to send server logo and rules SendServerInfoToClient(CL->ID); diff --git a/src/xrNetServer/NET_Client.cpp b/src/xrNetServer/NET_Client.cpp index 538c474510..9e3dcc8867 100644 --- a/src/xrNetServer/NET_Client.cpp +++ b/src/xrNetServer/NET_Client.cpp @@ -237,6 +237,12 @@ void INetQueue::Release() //cs.Leave (); } +bool INetQueue::Empty() +{ + xrCriticalSectionGuard guard(cs); + return ready.empty(); +} + // const u32 syncQueueSize = 512; const int syncSamples = 256; diff --git a/src/xrNetServer/NET_Client.h b/src/xrNetServer/NET_Client.h index eb7eab772b..80917d41b9 100644 --- a/src/xrNetServer/NET_Client.h +++ b/src/xrNetServer/NET_Client.h @@ -18,6 +18,7 @@ class XRNETSERVER_API INetQueue NET_Packet* Create(const NET_Packet& _other); NET_Packet* Retreive(); void Release(); + bool Empty(); inline void Lock() { cs.Enter(); }; inline void Unlock() { cs.Leave(); }; }; @@ -98,6 +99,7 @@ class XRNETSERVER_API IC void StartProcessQueue() { net_Queue.Lock(); }; // WARNING ! after Start mast be End !!! <- IC virtual NET_Packet* net_msg_Retreive() { return net_Queue.Retreive(); }; // | IC void net_msg_Release() { net_Queue.Release(); }; // | + IC bool net_msg_Empty() { return net_Queue.Empty(); } IC void EndProcessQueue() { net_Queue.Unlock(); }; // <- // send diff --git a/src/xrServerEntities/script_storage.cpp b/src/xrServerEntities/script_storage.cpp index b0d72d0dc3..19b61795b6 100644 --- a/src/xrServerEntities/script_storage.cpp +++ b/src/xrServerEntities/script_storage.cpp @@ -818,6 +818,7 @@ static bool unlocalRegex(xr_set& unlocals, xr_string& s, const std::r bool CScriptStorage::do_file(LPCSTR caScriptName, LPCSTR caNameSpaceName) { + static xr_map> script_source_cache; if (!unlocalizerPassed) { auto file_list = FS.file_list_open("$game_config$", "unlocalizers\\", FS_RootOnly | FS_ListFiles); if (!file_list) { @@ -874,17 +875,26 @@ bool CScriptStorage::do_file(LPCSTR caScriptName, LPCSTR caNameSpaceName) } int start = lua_gettop(lua()); string_path l_caLuaFileName; - IReader* l_tpFileReader = FS.r_open(caScriptName); - - if (!l_tpFileReader) + auto cached_source = script_source_cache.find(caScriptName); + if (cached_source == script_source_cache.end()) { - script_log(eLuaMessageTypeError, "Cannot open file \"%s\"", caScriptName); - return (false); + IReader* reader = FS.r_open(caScriptName); + if (!reader) + { + script_log(eLuaMessageTypeError, "Cannot open file \"%s\"", caScriptName); + return (false); + } + + xr_vector& source = script_source_cache[caScriptName]; + source.resize(reader->length()); + CopyMemory(source.data(), reader->pointer(), reader->length()); + FS.r_close(reader); + cached_source = script_source_cache.find(caScriptName); } // Unlocalize variables in the script defined by unlocalizers map - auto scriptContents = static_cast(l_tpFileReader->pointer()); - auto scriptLength = (size_t)l_tpFileReader->length(); + LPCSTR scriptContents = cached_source->second.data(); + auto scriptLength = cached_source->second.size(); bool unlocalPerformed = false; xr_string unlocalizerResult; xr_string loweredNameSpaceName = caNameSpaceName; @@ -894,12 +904,7 @@ bool CScriptStorage::do_file(LPCSTR caScriptName, LPCSTR caNameSpaceName) // Get contents of the script file and split by lines xr_vector tokens; - xr_string temp; - while (!l_tpFileReader->eof()) - { - char c = l_tpFileReader->r_u8(); - temp += c; - } + xr_string temp(scriptContents, scriptLength); std::stringstream stringStream(std::string(temp.c_str())); xr_string line; @@ -997,8 +1002,7 @@ bool CScriptStorage::do_file(LPCSTR caScriptName, LPCSTR caNameSpaceName) if (unlocalPerformed) { bufferLoaded = load_buffer(lua(), scriptContents, scriptLength, l_caLuaFileName, caNameSpaceName); } else { - l_tpFileReader->rewind(); - bufferLoaded = load_buffer(lua(), static_cast(l_tpFileReader->pointer()), (size_t)l_tpFileReader->length(), l_caLuaFileName, caNameSpaceName); + bufferLoaded = load_buffer(lua(), scriptContents, scriptLength, l_caLuaFileName, caNameSpaceName); } if (!bufferLoaded) @@ -1007,10 +1011,8 @@ bool CScriptStorage::do_file(LPCSTR caScriptName, LPCSTR caNameSpaceName) // lua_pop (lua(),4); // VERIFY (lua_gettop(lua()) == start - 3); lua_settop(lua(), start); - FS.r_close(l_tpFileReader); return (false); } - FS.r_close(l_tpFileReader); int errFuncId = -1; #ifdef USE_DEBUGGER diff --git a/src/xrServerEntities/xml_str_id_loader.h b/src/xrServerEntities/xml_str_id_loader.h index 8914a31b11..2af1f7cf27 100644 --- a/src/xrServerEntities/xml_str_id_loader.h +++ b/src/xrServerEntities/xml_str_id_loader.h @@ -153,6 +153,7 @@ typename void CSXML_IdToIndex::InitInternal() string_path xml_file; int count = _GetItemCount(file_str); int index = 0; + xr_unordered_flat_set ids; for (int it = 0; it < count; ++it) { _GetItem(file_str, it, xml_file); @@ -176,17 +177,11 @@ typename void CSXML_IdToIndex::InitInternal() //проверетить ID на уникальность - T_VECTOR::iterator t_it = m_pItemDataVector->begin(); - for (; m_pItemDataVector->end() != t_it; ++t_it) - { - if (shared_str((*t_it).id) == shared_str(item_name)) - break; - } - - R_ASSERT3(m_pItemDataVector->end() == t_it, "duplicate item id", item_name); + const shared_str itemId = item_name; + R_ASSERT3(ids.emplace(itemId).second, "duplicate item id", item_name); ITEM_DATA data; - data.id = item_name; + data.id = itemId; data.index = index; data.pos_in_file = i; //. data.file_name = xml_file; diff --git a/src/xrSound/Sound.h b/src/xrSound/Sound.h index 1c14f68c20..62dae62166 100644 --- a/src/xrSound/Sound.h +++ b/src/xrSound/Sound.h @@ -432,6 +432,11 @@ class XRSOUND_API CSound_manager_interface virtual void object_relcase(CObject* obj) = 0; virtual const Fvector& listener_position() = 0; + virtual void source_prefetch_start() = 0; + virtual void source_prefetch_pause() = 0; + virtual void source_prefetch_prepare(const xr_vector& sources) = 0; + virtual void source_prefetch_stop() = 0; + virtual void source_prefetch_poll() = 0; #ifdef __BORLANDC__ virtual SoundEnvironment_LIB* get_env_library () = 0; virtual void refresh_env_library () = 0; diff --git a/src/xrSound/SoundRender_Core.cpp b/src/xrSound/SoundRender_Core.cpp index 79cfd90970..b7cf53d0a0 100644 --- a/src/xrSound/SoundRender_Core.cpp +++ b/src/xrSound/SoundRender_Core.cpp @@ -82,7 +82,8 @@ void CSoundRender_Core::_initialize(int stage) if (Core.ParamsData.test(ECoreParams::prefetch_sounds)) { - i_create_all_sources(); + build_source_prefetch_manifest(); + source_prefetch_start(); } } @@ -91,6 +92,8 @@ extern xr_vector g_target_temp_data_16; void CSoundRender_Core::_clear() { + source_prefetch_stop(); + clear_source_prefetch(); bReady = FALSE; cache.destroy(); env_unload(); diff --git a/src/xrSound/SoundRender_Core.h b/src/xrSound/SoundRender_Core.h index 97aaae02c7..c4b7fbf89c 100644 --- a/src/xrSound/SoundRender_Core.h +++ b/src/xrSound/SoundRender_Core.h @@ -3,11 +3,66 @@ #include "SoundRender.h" #include "SoundRender_Environment.h" #include "SoundRender_Cache.h" +#include "SoundRender_Source.h" + +#include +#include +#include class CNotificationClient; class CSoundRender_Core : public CSound_manager_interface { + enum class ESourcePrefetchState : u8 + { + Queued, + Preparing, + Ready, + Committed, + Failed + }; + + struct SoundPrefetchJob + { + xr_string id; + xr_string path; + xr_string error; + u32 vfs = u32(-1); + u32 offset = 0; + ESourcePrefetchState state = ESourcePrefetchState::Queued; + PreparedSoundSource prepared; + }; + + xr_vector m_source_prefetch_jobs; + xr_vector m_source_prefetch_order; + xr_unordered_map m_source_prefetch_by_id; + std::mutex m_source_prefetch_mutex; + std::condition_variable m_source_prefetch_changed; + std::thread m_source_prefetch_thread; + std::atomic m_source_prefetch_idle_ms{0}; + size_t m_source_prefetch_cursor = 0; + u32 m_source_prefetch_started_at = 0; + u32 m_source_prefetch_prepared = 0; + u32 m_source_prefetch_promoted = 0; + u32 m_source_prefetch_waited_ms = 0; + u32 m_source_prefetch_failed = 0; + u32 m_source_prefetch_remaining = 0; + bool m_source_prefetch_enabled = false; + bool m_source_prefetch_pause = true; + bool m_source_prefetch_running = false; + bool m_source_prefetch_shutdown = false; + bool m_source_prefetch_completion_pending = false; + bool m_source_prefetch_completion_logged = false; + SoundPrefetchJob* m_source_prefetch_failure = nullptr; + + void source_prefetch_worker(); + void build_source_prefetch_manifest(); + void clear_source_prefetch(); + void finish_source_prepare(SoundPrefetchJob& job, PreparedSoundSource&& prepared, xr_string&& error); + CSoundRender_Source* commit_source_locked(SoundPrefetchJob& job); + u32 source_prefetch_remaining_locked() const; + u64 source_prefetch_hash_locked() const; + volatile BOOL bLocked; protected: virtual void _create_data(ref_sound_data& S, LPCSTR fName, esound_type sound_type, int game_type); @@ -125,7 +180,11 @@ class CSoundRender_Core : public CSound_manager_interface virtual BOOL is_ready() { return bReady; } virtual void object_relcase(CObject* obj); - void i_create_all_sources(); + virtual void source_prefetch_start() override; + virtual void source_prefetch_pause() override; + virtual void source_prefetch_prepare(const xr_vector& sources) override; + virtual void source_prefetch_stop() override; + virtual void source_prefetch_poll() override; virtual float get_occlusion_to(const Fvector& hear_pt, const Fvector& snd_pt, float dispersion = 0.2f); float get_occlusion(Fvector& P, float R, Fvector* occ) override; diff --git a/src/xrSound/SoundRender_Core_SourceManager.cpp b/src/xrSound/SoundRender_Core_SourceManager.cpp index 51938d7c20..634be9b837 100644 --- a/src/xrSound/SoundRender_Core_SourceManager.cpp +++ b/src/xrSound/SoundRender_Core_SourceManager.cpp @@ -3,26 +3,95 @@ #include "SoundRender_Core.h" #include "SoundRender_Source.h" -#include "../xrCore/ScopeLock.hpp" +#include + +namespace +{ +constexpr u32 StartupSoundIdleMs = 250; +constexpr u32 RuntimeSoundIdleMs = 10; + +void NormalizeSourceName(LPCSTR name, string256& id) +{ + xr_strcpy(id, name); + xr_strlwr(id); + if (strext(id)) + *strext(id) = 0; +} +} CSoundRender_Source* CSoundRender_Core::i_create_source(LPCSTR name) { - // Search string256 id; - xr_strcpy(id, name); - strlwr(id); - if (strext(id)) *strext(id) = 0; - auto it = s_sources.find(id); - if (it != s_sources.end()) + NormalizeSourceName(name, id); + { - return it->second; + std::lock_guard lock(m_source_prefetch_mutex); + const auto source = s_sources.find(id); + if (source != s_sources.end()) + return source->second; } - // Load a _new one - CSoundRender_Source* S = xr_new(); - S->load(id); - s_sources.insert({id, S}); - return S; + xr_string path; + CSoundRender_Source::resolve_path(id, path); + std::unique_lock lock(m_source_prefetch_mutex); + const auto existing = s_sources.find(id); + if (existing != s_sources.end()) + return existing->second; + + const auto found = m_source_prefetch_by_id.find(id); + SoundPrefetchJob* job = found == m_source_prefetch_by_id.end() ? nullptr : found->second; + if (!job || _stricmp(job->path.c_str(), path.c_str())) + { + // Level-local and dynamically discovered sounds retain the original synchronous path. + CSoundRender_Source* source = xr_new(); + source->load(id); + s_sources.insert({id, source}); + return source; + } + + if (job->state == ESourcePrefetchState::Queued) + { + job->state = ESourcePrefetchState::Preparing; + ++m_source_prefetch_promoted; + lock.unlock(); + + PreparedSoundSource prepared; + xr_string error; + try + { + CSoundRender_Source::prepare(job->path.c_str(), prepared, error); + } + catch (...) + { + error = make_string("Unhandled exception while preparing sound: %s", job->path.c_str()).c_str(); + } + finish_source_prepare(*job, std::move(prepared), std::move(error)); + lock.lock(); + } + else if (job->state == ESourcePrefetchState::Preparing) + { + CTimer wait_timer; + wait_timer.Start(); + m_source_prefetch_changed.wait(lock, [job]() + { + return job->state != ESourcePrefetchState::Preparing; + }); + m_source_prefetch_waited_ms += wait_timer.GetElapsed_ms(); + } + + if (job->state == ESourcePrefetchState::Ready) + return commit_source_locked(*job); + if (job->state == ESourcePrefetchState::Committed) + { + const auto source = s_sources.find(id); + R_ASSERT(source != s_sources.end()); + return source->second; + } + + xr_string error = job->error; + lock.unlock(); + R_ASSERT3(false, "Can't prepare sound source", error.c_str()); + return nullptr; } void CSoundRender_Core::i_destroy_source(CSoundRender_Source* S) @@ -30,43 +99,366 @@ void CSoundRender_Core::i_destroy_source(CSoundRender_Source* S) // No actual destroy at all } -void CSoundRender_Core::i_create_all_sources() +void CSoundRender_Core::build_source_prefetch_manifest() { - PROF_EVENT(); - CTimer T; - T.Start(); + CTimer timer; + timer.Start(); - FS_FileSet flist; - FS.file_list(flist, "$game_sounds$", FS_ListFiles, "*.ogg"); - const size_t sizeBefore = s_sources.size(); - - Lock lock; - const auto processFile = [&](const FS_File& file) + FS_FileSet files; + FS.file_list(files, "$game_sounds$", FS_ListFiles, "*.ogg"); + for (const FS_File& file : files) { string256 id; - xr_strcpy(id, file.name.c_str()); + NormalizeSourceName(file.name.c_str(), id); + if (m_source_prefetch_by_id.find(id) != m_source_prefetch_by_id.end()) + continue; - xr_strlwr(id); - if (strext(id)) - *strext(id) = 0; + SoundPrefetchJob* job = xr_new(); + job->id = id; + string_path path; + FS.update_path(path, "$game_sounds$", file.name.c_str()); + job->path = path; + if (const CLocatorAPI::file* descriptor = FS.exist(path)) + { + job->vfs = descriptor->vfs; + job->offset = descriptor->ptr; + } + m_source_prefetch_jobs.push_back(job); + m_source_prefetch_order.push_back(job); + m_source_prefetch_by_id.insert({job->id, job}); + } + m_source_prefetch_remaining = u32(m_source_prefetch_jobs.size()); + std::sort(m_source_prefetch_order.begin(), m_source_prefetch_order.end(), + [](const SoundPrefetchJob* left, const SoundPrefetchJob* right) { - ScopeLock scope(&lock); - const auto it = s_sources.find(id); - if (it != s_sources.end()) - return; - UNUSED(scope); + if (left->vfs != right->vfs) + return left->vfs < right->vfs; + if (left->offset != right->offset) + return left->offset < right->offset; + return left->id < right->id; + }); + m_source_prefetch_enabled = !m_source_prefetch_jobs.empty(); + Msg("* [SOUND PREFETCH] manifest: %u sources in %u ms", + u32(m_source_prefetch_jobs.size()), timer.GetElapsed_ms()); +} + +void CSoundRender_Core::source_prefetch_worker() +{ + _initialize_cpu_thread(); + thread_name("Sound prefetch"); + if (!SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN)) + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); + + for (;;) + { + SoundPrefetchJob* job = nullptr; + { + std::lock_guard lock(m_source_prefetch_mutex); + if (m_source_prefetch_pause || m_source_prefetch_shutdown || m_source_prefetch_failure) + break; + while (m_source_prefetch_cursor < m_source_prefetch_order.size()) + { + SoundPrefetchJob* candidate = m_source_prefetch_order[m_source_prefetch_cursor++]; + if (candidate->state == ESourcePrefetchState::Queued) + { + candidate->state = ESourcePrefetchState::Preparing; + job = candidate; + break; + } + } + if (!job) + break; + } + + PreparedSoundSource prepared; + xr_string error; + try + { + CSoundRender_Source::prepare(job->path.c_str(), prepared, error, false); + } + catch (...) + { + error = make_string("Unhandled exception while preparing sound: %s", job->path.c_str()).c_str(); + } + const bool succeeded = error.empty(); + finish_source_prepare(*job, std::move(prepared), std::move(error)); + if (succeeded) + { + std::unique_lock lock(m_source_prefetch_mutex); + m_source_prefetch_changed.wait_for(lock, + std::chrono::milliseconds(m_source_prefetch_idle_ms.load(std::memory_order_relaxed)), [this]() + { + return m_source_prefetch_pause || m_source_prefetch_shutdown || m_source_prefetch_failure; + }); + } + } + + { + std::lock_guard lock(m_source_prefetch_mutex); + m_source_prefetch_running = false; + if (!m_source_prefetch_failure && source_prefetch_remaining_locked() == 0) + m_source_prefetch_completion_pending = true; + } + m_source_prefetch_changed.notify_all(); +} + +void CSoundRender_Core::finish_source_prepare( + SoundPrefetchJob& job, PreparedSoundSource&& prepared, xr_string&& error) +{ + { + std::lock_guard lock(m_source_prefetch_mutex); + R_ASSERT(job.state == ESourcePrefetchState::Preparing); + R_ASSERT(m_source_prefetch_remaining); + --m_source_prefetch_remaining; + if (error.empty()) + { + job.prepared = std::move(prepared); + job.state = ESourcePrefetchState::Ready; + ++m_source_prefetch_prepared; + } + else + { + job.error = std::move(error); + job.state = ESourcePrefetchState::Failed; + m_source_prefetch_failure = &job; + m_source_prefetch_pause = true; + ++m_source_prefetch_failed; } + if (!m_source_prefetch_failure && source_prefetch_remaining_locked() == 0) + m_source_prefetch_completion_pending = true; + } + m_source_prefetch_changed.notify_all(); +} + +CSoundRender_Source* CSoundRender_Core::commit_source_locked(SoundPrefetchJob& job) +{ + const auto existing = s_sources.find(job.id); + if (existing != s_sources.end()) + { + job.state = ESourcePrefetchState::Committed; + return existing->second; + } + + CSoundRender_Source* source = xr_new(); + switch (job.prepared.warning) + { + case PreparedSoundSource::Warning::InvalidRate: + Msg("! Warning: Invalid source rate: %s", job.path.c_str()); + break; + case PreparedSoundSource::Warning::InvalidComment: + Log("! Invalid ogg-comment version, file: ", job.path.c_str()); + break; + case PreparedSoundSource::Warning::MissingComment: + Log("! Missing ogg-comment, file: ", job.path.c_str()); + break; + default: + break; + } + source->load_prepared(job.id.c_str(), job.prepared); + s_sources.insert({job.id, source}); + job.state = ESourcePrefetchState::Committed; + return source; +} - CSoundRender_Source* S = new CSoundRender_Source(); - S->load(id); +u32 CSoundRender_Core::source_prefetch_remaining_locked() const +{ + return m_source_prefetch_remaining; +} - lock.Enter(); - s_sources.insert({ id, S }); - lock.Leave(); +u64 CSoundRender_Core::source_prefetch_hash_locked() const +{ + u64 hash = 14695981039346656037ULL; + const auto append = [&hash](const void* data, size_t size) + { + const u8* bytes = static_cast(data); + for (size_t i = 0; i < size; ++i) + { + hash ^= bytes[i]; + hash *= 1099511628211ULL; + } }; + for (const SoundPrefetchJob* job : m_source_prefetch_jobs) + { + append(job->id.data(), job->id.size()); + append(&job->prepared.format, sizeof(job->prepared.format)); + append(&job->prepared.time_total, sizeof(job->prepared.time_total)); + append(&job->prepared.bytes_total, sizeof(job->prepared.bytes_total)); + append(&job->prepared.base_volume, sizeof(job->prepared.base_volume)); + append(&job->prepared.min_distance, sizeof(job->prepared.min_distance)); + append(&job->prepared.max_distance, sizeof(job->prepared.max_distance)); + append(&job->prepared.max_ai_distance, sizeof(job->prepared.max_ai_distance)); + append(&job->prepared.game_type, sizeof(job->prepared.game_type)); + } + return hash; +} + +void CSoundRender_Core::source_prefetch_start() +{ + std::unique_lock lock(m_source_prefetch_mutex); + if (!m_source_prefetch_enabled || m_source_prefetch_shutdown || + m_source_prefetch_failure || source_prefetch_remaining_locked() == 0) + return; + m_source_prefetch_idle_ms.store( + m_source_prefetch_started_at ? RuntimeSoundIdleMs : StartupSoundIdleMs, std::memory_order_relaxed); + if (m_source_prefetch_running) + return; - xr_parallel_foreach(flist.begin(), flist.end(), processFile); + if (m_source_prefetch_thread.joinable()) + { + std::thread completed = std::move(m_source_prefetch_thread); + m_source_prefetch_running = true; // Reserve restart while the completed worker is reaped. + lock.unlock(); + completed.join(); + lock.lock(); + m_source_prefetch_running = false; + if (m_source_prefetch_shutdown || m_source_prefetch_failure || source_prefetch_remaining_locked() == 0) + return; + } - Msg("Finished creating %d sound sources. Duration: %d ms", s_sources.size() - sizeBefore, T.GetElapsed_ms()); + m_source_prefetch_pause = false; + m_source_prefetch_running = true; + if (!m_source_prefetch_started_at) + m_source_prefetch_started_at = GetTickCount(); + try + { + m_source_prefetch_thread = std::thread(&CSoundRender_Core::source_prefetch_worker, this); + } + catch (...) + { + m_source_prefetch_pause = true; + m_source_prefetch_running = false; + throw; + } + Msg("* [SOUND PREFETCH] started: remaining=%u", source_prefetch_remaining_locked()); +} + +void CSoundRender_Core::source_prefetch_pause() +{ + { + std::lock_guard lock(m_source_prefetch_mutex); + m_source_prefetch_pause = true; + } + m_source_prefetch_changed.notify_all(); + if (m_source_prefetch_thread.joinable()) + m_source_prefetch_thread.join(); + + std::lock_guard lock(m_source_prefetch_mutex); + m_source_prefetch_running = false; + if (m_source_prefetch_enabled) + Msg("* [SOUND PREFETCH] paused: prepared=%u, promoted=%u, remaining=%u", + m_source_prefetch_prepared, m_source_prefetch_promoted, source_prefetch_remaining_locked()); +} + +void CSoundRender_Core::source_prefetch_prepare(const xr_vector& sources) +{ + if (!m_source_prefetch_enabled || sources.empty()) + return; + { + std::lock_guard lock(m_source_prefetch_mutex); + if (m_source_prefetch_shutdown || m_source_prefetch_failure) + return; + } + + CTimer timer; + timer.Start(); + xr_parallel_for(0u, static_cast(sources.size()), [&](u32 index) + { + string256 id; + NormalizeSourceName(sources[index].c_str(), id); + + SoundPrefetchJob* job = nullptr; + bool producer = false; + { + std::unique_lock lock(m_source_prefetch_mutex); + const auto found = m_source_prefetch_by_id.find(id); + if (found == m_source_prefetch_by_id.end()) + return; + + job = found->second; + if (job->state == ESourcePrefetchState::Queued) + { + job->state = ESourcePrefetchState::Preparing; + ++m_source_prefetch_promoted; + producer = true; + } + else if (job->state == ESourcePrefetchState::Preparing) + return; + else + return; + } + + if (!producer) + return; + + PreparedSoundSource prepared; + xr_string error; + try + { + CSoundRender_Source::prepare(job->path.c_str(), prepared, error, false); + } + catch (...) + { + error = make_string("Unhandled exception while preparing sound: %s", job->path.c_str()).c_str(); + } + finish_source_prepare(*job, std::move(prepared), std::move(error)); + }); + Msg("* [SOUND PREFETCH] requested sources prepared: requested=%u time=%u ms", + static_cast(sources.size()), timer.GetElapsed_ms()); +} + +void CSoundRender_Core::source_prefetch_stop() +{ + { + std::lock_guard lock(m_source_prefetch_mutex); + m_source_prefetch_shutdown = true; + m_source_prefetch_pause = true; + } + m_source_prefetch_changed.notify_all(); + if (m_source_prefetch_thread.joinable()) + m_source_prefetch_thread.join(); + std::lock_guard lock(m_source_prefetch_mutex); + m_source_prefetch_running = false; +} + +void CSoundRender_Core::source_prefetch_poll() +{ + xr_string failed_path; + xr_string failure; + { + std::lock_guard lock(m_source_prefetch_mutex); + if (m_source_prefetch_shutdown) + return; + if (m_source_prefetch_failure) + { + failed_path = m_source_prefetch_failure->path; + failure = m_source_prefetch_failure->error; + } + else if (m_source_prefetch_completion_pending && !m_source_prefetch_completion_logged) + { + m_source_prefetch_completion_pending = false; + m_source_prefetch_completion_logged = true; + Msg("* [SOUND PREFETCH] complete: prepared=%u, promoted=%u, demand_wait=%u ms, " + "duration=%u ms, metadata_hash=%016llx", + m_source_prefetch_prepared, m_source_prefetch_promoted, m_source_prefetch_waited_ms, + GetTickCount() - m_source_prefetch_started_at, source_prefetch_hash_locked()); + } + } + if (!failure.empty()) + { + Msg("! [SOUND PREFETCH] failed: %s (%s)", failed_path.c_str(), failure.c_str()); + R_ASSERT3(false, failure.c_str(), failed_path.c_str()); + } +} + +void CSoundRender_Core::clear_source_prefetch() +{ + for (SoundPrefetchJob* job : m_source_prefetch_jobs) + xr_delete(job); + m_source_prefetch_jobs.clear(); + m_source_prefetch_order.clear(); + m_source_prefetch_by_id.clear(); + m_source_prefetch_cursor = 0; + m_source_prefetch_remaining = 0; + m_source_prefetch_idle_ms.store(0, std::memory_order_relaxed); } diff --git a/src/xrSound/SoundRender_Source.h b/src/xrSound/SoundRender_Source.h index 8c5a2747b2..1d97528fe2 100644 --- a/src/xrSound/SoundRender_Source.h +++ b/src/xrSound/SoundRender_Source.h @@ -7,6 +7,29 @@ // refs struct OggVorbis_File; +struct PreparedSoundSource +{ + enum class Warning : u8 + { + None, + InvalidRate, + InvalidComment, + MissingComment + }; + + xr_string path; + WAVEFORMATEX format{}; + float time_total = 0.f; + u32 bytes_total = 0; + float base_volume = 1.f; + float min_distance = 1.f; + float max_distance = 300.f; + float max_ai_distance = 300.f; + u32 game_type = 0; + bool loaded = false; + Warning warning = Warning::None; +}; + class XRSOUND_EDITOR_API CSoundRender_Source : public CSound_source { public: @@ -32,8 +55,11 @@ class XRSOUND_EDITOR_API CSoundRender_Source : public CSound_source ~CSoundRender_Source(); void load(LPCSTR name); + void load_prepared(LPCSTR name, const PreparedSoundSource& prepared); void unload(); void decompress(u32 line, OggVorbis_File* ovf); + static bool prepare(LPCSTR path, PreparedSoundSource& prepared, xr_string& error, bool log_warnings = true); + static void resolve_path(LPCSTR name, xr_string& path); virtual float length_sec() const { return fTimeTotal; } virtual u32 game_type() const { return m_uGameType; } diff --git a/src/xrSound/SoundRender_Source_loader.cpp b/src/xrSound/SoundRender_Source_loader.cpp index 67394c2058..19bfb034d8 100644 --- a/src/xrSound/SoundRender_Source_loader.cpp +++ b/src/xrSound/SoundRender_Source_loader.cpp @@ -6,6 +6,158 @@ #include "SoundRender_Core.h" #include "SoundRender_Source.h" +namespace +{ +constexpr size_t OggPageHeaderSize = 27; +constexpr size_t OggMaximumPageSize = OggPageHeaderSize + 255 + 255 * 255; + +bool ReadOggPage(const u8* data, size_t size, size_t offset, ogg_page& page, size_t& next) +{ + if (offset > size || size - offset < OggPageHeaderSize || memcmp(data + offset, "OggS", 4) || data[offset + 4]) + return false; + + const size_t segment_count = data[offset + 26]; + const size_t header_size = OggPageHeaderSize + segment_count; + if (header_size > size - offset) + return false; + + size_t body_size = 0; + for (size_t i = 0; i < segment_count; ++i) + body_size += data[offset + OggPageHeaderSize + i]; + if (body_size > size - offset - header_size) + return false; + + page.header = const_cast(data + offset); + page.header_len = static_cast(header_size); + page.body = const_cast(data + offset + header_size); + page.body_len = static_cast(body_size); + next = offset + header_size + body_size; + + unsigned char checked_header[OggPageHeaderSize + 255]; + CopyMemory(checked_header, page.header, header_size); + ogg_page checked_page = page; + checked_page.header = checked_header; + ogg_page_checksum_set(&checked_page); + if (memcmp(checked_header + 22, page.header + 22, 4)) + return false; + return true; +} + +bool ReadSingleStreamPcmTotal( + const u8* data, size_t size, const OggVorbis_File& vorbis_file, vorbis_info& info, s64& pcm_total) +{ + if (!data || size < OggPageHeaderSize) + return false; + + const int serial = vorbis_file.current_serialno; + ogg_stream_state stream{}; + if (ogg_stream_init(&stream, serial)) + return false; + + bool valid = true; + u32 header_packets = 0; + long last_block = -1; + s64 accumulated = 0; + s64 initial_pcm = -1; + size_t offset = 0; + while (offset < size) + { + ogg_page page{}; + size_t next = 0; + if (!ReadOggPage(data, size, offset, page, next)) + { + valid = false; + break; + } + offset = next; + if (header_packets >= 3 && ogg_page_bos(&page)) + { + valid = false; + break; + } + if (ogg_page_serialno(&page) != serial) + continue; + if (ogg_stream_pagein(&stream, &page)) + { + valid = false; + break; + } + + bool setup_completed_on_page = false; + for (;;) + { + ogg_packet packet{}; + const int packet_result = ogg_stream_packetout(&stream, &packet); + if (!packet_result) + break; + if (packet_result < 0) + { + valid = false; + break; + } + if (header_packets < 3) + { + ++header_packets; + setup_completed_on_page = header_packets == 3; + continue; + } + if (setup_completed_on_page) + { + valid = false; + break; + } + + const long block = vorbis_packet_blocksize(&info, &packet); + if (block < 0) + { + valid = false; + break; + } + if (last_block != -1) + accumulated += (last_block + block) >> 2; + last_block = block; + } + if (!valid) + break; + + const s64 granule = ogg_page_granulepos(&page); + if (header_packets >= 3 && last_block != -1 && granule >= 0) + { + initial_pcm = _max(s64(0), granule - accumulated); + break; + } + } + ogg_stream_clear(&stream); + if (!valid || initial_pcm < 0) + return false; + + const size_t tail_begin = size > OggMaximumPageSize ? size - OggMaximumPageSize : 0; + s64 final_pcm = -1; + for (size_t tail = size - OggPageHeaderSize;; --tail) + { + if (data[tail] == 'O') + { + ogg_page page{}; + size_t next = 0; + if (ReadOggPage(data, size, tail, page, next) && next == size && ogg_page_eos(&page)) + { + if (ogg_page_serialno(&page) != serial) + return false; + final_pcm = ogg_page_granulepos(&page); + break; + } + } + if (tail == tail_begin) + break; + } + if (final_pcm < initial_pcm) + return false; + + pcm_total = final_pcm - initial_pcm; + return true; +} +} + // SEEK_SET 0 File beginning // SEEK_CUR 1 Current file pointer position // SEEK_END 2 End-of-file @@ -61,104 +213,205 @@ void CSoundRender_Source::decompress(u32 line, OggVorbis_File* ovf) i_decompress_fr(ovf, dest, left); } -bool CSoundRender_Source::LoadWave(LPCSTR pName) +bool CSoundRender_Source::prepare( + LPCSTR path, PreparedSoundSource& prepared, xr_string& error, bool log_warnings) { PROF_EVENT("Sound: Load ogg"); - pname = pName; + prepared = PreparedSoundSource{}; + prepared.path = path; // Load file into memory and parse WAV-format OggVorbis_File ovf; ov_callbacks ovc = {ov_read_func, ov_seek_func, ov_close_func, ov_tell_func}; - IReader* wave = FS.r_open(pname.c_str()); - R_ASSERT3(wave&&wave->length(), "Can't open wave file:", pname.c_str()); - ov_open_callbacks(wave, &ovf,NULL, 0, ovc); + IReader* wave = FS.r_open(path); + if (!wave || !wave->length()) + { + if (wave) + FS.r_close(wave); + error = make_string("Can't open wave file: %s", path).c_str(); + return false; + } + + const u8* wave_data = static_cast(wave->pointer()); + const size_t wave_size = wave->length(); + // Headers and comments do not require libvorbisfile's full seekable-open pass. + const int open_result = ov_test_callbacks(wave, &ovf, NULL, 0, ovc); + if (open_result) + { + FS.r_close(wave); + error = make_string("Invalid OGG stream (%d): %s", open_result, path).c_str(); + return false; + } vorbis_info* ovi = ov_info(&ovf, -1); - // verify - R_ASSERT3(ovi, "Invalid source info:", pname.c_str()); + if (!ovi) + { + ov_clear(&ovf); + FS.r_close(wave); + error = make_string("Invalid source info: %s", path).c_str(); + return false; + } + s64 pcm_total = 0; + if (!ReadSingleStreamPcmTotal(wave_data, wave_size, ovf, *ovi, pcm_total)) + { + // Chained, multiplexed and unusual streams retain the original full parser. + const int finish_result = ov_test_open(&ovf); + if (finish_result) + { + FS.r_close(wave); + error = make_string("Invalid OGG stream (%d): %s", finish_result, path).c_str(); + return false; + } + ovi = ov_info(&ovf, -1); + if (!ovi) + { + ov_clear(&ovf); + FS.r_close(wave); + error = make_string("Invalid source info: %s", path).c_str(); + return false; + } + pcm_total = ov_pcm_total(&ovf, -1); + } //R_ASSERT3(ovi->rate == 44100, "Invalid source rate:", pname.c_str()); - if (ovi->rate != 44100) { - Msg("! Warning: Invalid source rate: %s", pname.c_str()); + if (log_warnings) + Msg("! Warning: Invalid source rate: %s", path); + else + prepared.warning = PreparedSoundSource::Warning::InvalidRate; + ov_clear(&ovf); + FS.r_close(wave); + return true; + } + if (pcm_total < 0) + { ov_clear(&ovf); FS.r_close(wave); + error = make_string("Invalid PCM length: %s", path).c_str(); return false; } #ifdef DEBUG - if (ovi->channels == 2) + if (log_warnings && ovi->channels == 2) { - Msg("stereo sound source [%s]", pname.c_str()); + Msg("stereo sound source [%s]", path); } #endif // #ifdef DEBUG - ZeroMemory(&m_wformat, sizeof( WAVEFORMATEX )); + ZeroMemory(&prepared.format, sizeof(WAVEFORMATEX)); + prepared.format.nSamplesPerSec = ovi->rate; //44100; + prepared.format.wFormatTag = WAVE_FORMAT_PCM; + prepared.format.nChannels = u16(ovi->channels); + prepared.format.wBitsPerSample = 16; + prepared.format.nBlockAlign = prepared.format.wBitsPerSample / 8 * prepared.format.nChannels; + prepared.format.nAvgBytesPerSec = prepared.format.nSamplesPerSec * prepared.format.nBlockAlign; - m_wformat.nSamplesPerSec = (ovi->rate); //44100; - m_wformat.wFormatTag = WAVE_FORMAT_PCM; - m_wformat.nChannels = u16(ovi->channels); - m_wformat.wBitsPerSample = 16; - - m_wformat.nBlockAlign = m_wformat.wBitsPerSample / 8 * m_wformat.nChannels; - m_wformat.nAvgBytesPerSec = m_wformat.nSamplesPerSec * m_wformat.nBlockAlign; - - s64 pcm_total = ov_pcm_total(&ovf, -1); - dwBytesTotal = u32(pcm_total * m_wformat.nBlockAlign); - fTimeTotal = s_f_def_source_footer + dwBytesTotal / float(m_wformat.nAvgBytesPerSec); + prepared.bytes_total = u32(pcm_total * prepared.format.nBlockAlign); + prepared.time_total = s_f_def_source_footer + prepared.bytes_total / float(prepared.format.nAvgBytesPerSec); vorbis_comment* ovm = ov_comment(&ovf, -1); - if (ovm->comments) + if (ovm && ovm->comments && ovm->user_comments[0] && ovm->comment_lengths[0] >= sizeof(u32)) { IReader F(ovm->user_comments[0], ovm->comment_lengths[0]); u32 vers = F.r_u32(); - if (vers == 0x0001) + if (vers == 0x0001 && ovm->comment_lengths[0] >= 16) { - m_fMinDist = F.r_float(); - m_fMaxDist = F.r_float(); - m_fBaseVolume = 1.0f; - m_uGameType = F.r_u32(); - m_fMaxAIDist = m_fMaxDist; + prepared.min_distance = F.r_float(); + prepared.max_distance = F.r_float(); + prepared.base_volume = 1.0f; + prepared.game_type = F.r_u32(); + prepared.max_ai_distance = prepared.max_distance; } - else if (vers == 0x0002) + else if (vers == 0x0002 && ovm->comment_lengths[0] >= 20) { - m_fMinDist = F.r_float(); - m_fMaxDist = F.r_float(); - m_fBaseVolume = F.r_float(); - m_uGameType = F.r_u32(); - m_fMaxAIDist = m_fMaxDist; + prepared.min_distance = F.r_float(); + prepared.max_distance = F.r_float(); + prepared.base_volume = F.r_float(); + prepared.game_type = F.r_u32(); + prepared.max_ai_distance = prepared.max_distance; } - else if (vers == OGG_COMMENT_VERSION) + else if (vers == OGG_COMMENT_VERSION && ovm->comment_lengths[0] >= 24) { - m_fMinDist = F.r_float(); - m_fMaxDist = F.r_float(); - m_fBaseVolume = F.r_float(); - m_uGameType = F.r_u32(); - m_fMaxAIDist = F.r_float(); + prepared.min_distance = F.r_float(); + prepared.max_distance = F.r_float(); + prepared.base_volume = F.r_float(); + prepared.game_type = F.r_u32(); + prepared.max_ai_distance = F.r_float(); } else { - if (Core.isDebug()) + if (log_warnings && Core.isDebug()) { - Log("! Invalid ogg-comment version, file: ", pname.c_str()); + Log("! Invalid ogg-comment version, file: ", path); } + else if (!log_warnings && Core.isDebug()) + prepared.warning = PreparedSoundSource::Warning::InvalidComment; } } else { - if (Core.isDebug()) + if (log_warnings && Core.isDebug()) { - Log("! Missing ogg-comment, file: ", pname.c_str()); + Log("! Missing ogg-comment, file: ", path); } + else if (!log_warnings && Core.isDebug()) + prepared.warning = PreparedSoundSource::Warning::MissingComment; + } + if (prepared.max_ai_distance < 0.1f || prepared.max_distance < 0.1f) + { + ov_clear(&ovf); + FS.r_close(wave); + error = make_string("Invalid max distance: %s", path).c_str(); + return false; } - R_ASSERT3((m_fMaxAIDist >= 0.1f) && (m_fMaxDist >= 0.1f), "Invalid max distance.", pname.c_str()); ov_clear(&ovf); FS.r_close(wave); + prepared.loaded = true; return true; } +bool CSoundRender_Source::LoadWave(LPCSTR path) +{ + PreparedSoundSource prepared; + xr_string error; + R_ASSERT3(prepare(path, prepared, error), "Can't prepare sound source", error.c_str()); + load_prepared(fname.c_str(), prepared); + return prepared.loaded; +} + +void CSoundRender_Source::resolve_path(LPCSTR name, xr_string& path) +{ + string_path fn; + strconcat(sizeof(fn), fn, name, ".ogg"); + if (!FS.exist("$level$", fn)) + FS.update_path(fn, "$game_sounds$", fn); + + if (!FS.exist(fn)) + { + Msg("! Can't find sound '%s'", name); + FS.update_path(fn, "$game_sounds$", "$no_sound.ogg"); + } + path = fn; +} + +void CSoundRender_Source::load_prepared(LPCSTR name, const PreparedSoundSource& prepared) +{ + fname = name; + pname = prepared.path.c_str(); + m_wformat = prepared.format; + fTimeTotal = prepared.time_total; + dwBytesTotal = prepared.bytes_total; + m_fBaseVolume = prepared.base_volume; + m_fMinDist = prepared.min_distance; + m_fMaxDist = prepared.max_distance; + m_fMaxAIDist = prepared.max_ai_distance; + m_uGameType = prepared.game_type; + if (prepared.loaded) + SoundRender->cache.cat_create(CAT, dwBytesTotal); +} + void CSoundRender_Source::load(LPCSTR name) { string_path fn, N; @@ -167,20 +420,12 @@ void CSoundRender_Source::load(LPCSTR name) if (strext(N)) *strext(N) = 0; fname = N; - - strconcat(sizeof(fn), fn, N, ".ogg"); - if (!FS.exist("$level$", fn)) - FS.update_path(fn, "$game_sounds$", fn); - - if (!FS.exist(fn)){ - { - Msg("! Can't find sound '%s'", name); - FS.update_path(fn, "$game_sounds$", "$no_sound.ogg"); - } - } + xr_string path; + resolve_path(N, path); + xr_strcpy(fn, path.c_str()); if (LoadWave(fn)) - SoundRender->cache.cat_create(CAT, dwBytesTotal); + return; } void CSoundRender_Source::unload() diff --git a/src/xrXMLParser/xrXMLParser.cpp b/src/xrXMLParser/xrXMLParser.cpp index fa67c1abb0..9690cb9f23 100644 --- a/src/xrXMLParser/xrXMLParser.cpp +++ b/src/xrXMLParser/xrXMLParser.cpp @@ -3,7 +3,7 @@ #include "xrXMLParser.h" -extern void XMLLuaCallback(CXml &m_xml, LPCSTR xml_string); +extern bool XMLLuaCallback(CXml& m_xml, LPCSTR xml_string, xr_string& transformed); XRXMLPARSER_API CXml::CXml() : m_root(NULL), @@ -131,16 +131,11 @@ void CXml::Load(LPCSTR path, LPCSTR xml_filename) W.w_stringZ(""); FS.r_close(F); - m_Doc.Parse(&m_Doc, (LPCSTR)W.pointer()); - if (m_Doc.Error()) - { - string1024 str; - xr_sprintf(str, "XML file:%s value:%s errDescr:%s", m_xml_file_name, m_Doc.Value(), m_Doc.ErrorDesc()); - R_ASSERT2(false, str); - } - - m_root = m_Doc.FirstChildElement(); - XMLLuaCallback(*this, (LPCSTR)W.pointer()); + xr_string transformed; + const LPCSTR source = XMLLuaCallback(*this, (LPCSTR)W.pointer(), transformed) + ? transformed.c_str() + : (LPCSTR)W.pointer(); + LoadFromString(source); } void CXml::LoadFromString(LPCSTR xml_string)