diff --git a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp index 2085ad14a..d1d043acd 100644 --- a/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp +++ b/trinity/Eve/SpaceObject/Children/EveChildMesh.cpp @@ -383,19 +383,6 @@ void EveChildMesh::UpdateVisibility( const EveUpdateContext& updateContext, cons if( m_mesh ) { - CcpMath::AxisAlignedBox bounds; - if( m_animationUpdater && m_animationUpdater->IsInitialized() && m_animationUpdater->GetAnimationTransforms() ) - { - auto [meshBindingIndices, boneCount] = GetMeshBindingIndices(); - auto [morphTargets, morphTargetCount] = GetMorphTargets( MorphTargetAnimationFilter::ALL ); - bounds = m_mesh->GetBounds( m_animationUpdater->GetAnimationTransforms(), meshBindingIndices, boneCount, morphTargets, morphTargetCount ); - } - else - { - bounds = m_mesh->GetBounds(); - } - - bounds.Transform( m_worldTransform ); m_currentScreenSize = frustum.GetPixelSizeAccross( m_worldBoundingSphere ); if( m_instancedMesh ) diff --git a/trinity/Include/ITriEffectParameter.h b/trinity/Include/ITriEffectParameter.h index 25bf80c66..f41b0dfb5 100644 --- a/trinity/Include/ITriEffectParameter.h +++ b/trinity/Include/ITriEffectParameter.h @@ -4,6 +4,7 @@ #define ITr2EffectParameter_h #include "ITr2EffectValue.h" +#include "Tr2ScreenSizeRequests.h" BLUE_DECLARE( Tr2Shader ); @@ -36,6 +37,11 @@ BLUE_INTERFACE( ITriEffectTextureParameter ) : { static const size_t UV_SET_MAX_COUNT = 8; virtual void UsedWithScreenSize( float screenSize, float worldRadius, const std::vector& uvDensities ) = 0; + virtual void UsedWithScreenSize( float screenSize, float worldRadius, const std::vector& uvDensities, Tr2ScreenSizeRequests& requests ) + { + UsedWithScreenSize( screenSize, worldRadius, uvDensities ); + requests.Invalidate(); + } virtual void EnableTextureLoding( const std::array& uvDensityScale ) = 0; virtual void DisableTextureLoding() = 0; }; diff --git a/trinity/Resources/TriTextureRes.cpp b/trinity/Resources/TriTextureRes.cpp index 85d60156d..092393983 100644 --- a/trinity/Resources/TriTextureRes.cpp +++ b/trinity/Resources/TriTextureRes.cpp @@ -1,6 +1,7 @@ // Copyright © 2023 CCP ehf. #include "StdAfx.h" +#include "Tr2ScreenSizeRequests.h" #include "TriTextureRes.h" #include "TriSettingsRegistrar.h" @@ -154,6 +155,7 @@ TriTextureRes::TriTextureRes() : TriTextureRes::~TriTextureRes() { + Tr2ScreenSizeRequests::BumpGeneration(); // Note: ReleaseResources now happens in Unlock due to threading issues. // If a texture resource was deleted while it was still being loaded it would cause crash. Tr2TextureLodManager::Instance().UnregisterTexture( this ); @@ -641,6 +643,7 @@ BlueAsyncRes::LoadingResult TriTextureRes::DoLoad() m_originalResolution = std::min( m_loadedBitmap->GetWidth(), m_loadedBitmap->GetHeight() ); m_originalResolution = m_originalResolution << m_requestedLoadMip; + Tr2ScreenSizeRequests::BumpGeneration(); m_cpuMip = m_requestedLoadMip; m_maxMip = m_loadedBitmap->GetTrueMipCount(); if( m_maxMip > 4 ) @@ -715,8 +718,10 @@ void TriTextureRes::RequestResolution( const uint32_t requestedLod ) { return; } - m_hadLodRequests = true; - + if( !m_hadLodRequests.load( std::memory_order_relaxed ) ) + { + m_hadLodRequests = true; + } AtomicMinUpdate( m_requestedMip, requestedLod ); } diff --git a/trinity/Shader/Parameter/TriTextureParameter.cpp b/trinity/Shader/Parameter/TriTextureParameter.cpp index e0c0af9d1..9429c4e1b 100644 --- a/trinity/Shader/Parameter/TriTextureParameter.cpp +++ b/trinity/Shader/Parameter/TriTextureParameter.cpp @@ -2,6 +2,7 @@ #include "StdAfx.h" #include "TriTextureParameter.h" +#include "Tr2ScreenSizeRequests.h" #include "ITr2TextureProvider.h" #include "Shader/Tr2Shader.h" #include "Shader/Tr2Material.h" @@ -50,56 +51,105 @@ bool TriTextureParameter::SupportsDirtyNotification() const return true; } +namespace +{ +// Screen sizes for which this parameter keeps requesting the same LOD: the LOD only changes when +// screenSize / minDensity crosses a power of two of the texture resolution. +void ConstrainScreenSize( Tr2ScreenSizeRequests& requests, float resolution, float minDensity, uint32_t requestedLod, float originalResolution ) +{ + if( minDensity <= 0.0f || originalResolution <= 0.0f ) + { + return; + } + if( resolution <= 0.0f ) + { + requests.Invalidate(); + return; + } + const float eps = 1e-4f; + if( resolution <= 1.0f ) + { + requests.Constrain( 0.0f, minDensity * ( 1.0f - eps ) ); + return; + } + if( resolution > originalResolution ) + { + requests.Constrain( minDensity * originalResolution * ( 1.0f + eps ), std::numeric_limits::max() ); + return; + } + const float upper = originalResolution / float( 1u << requestedLod ); + requests.Constrain( std::max( minDensity, minDensity * upper * 0.5f ) * ( 1.0f + eps ), minDensity * upper * ( 1.0f - eps ) ); +} +} + void TriTextureParameter::UsedWithScreenSize( float screenSize, float worldRadius, const std::vector& uvDensities ) { - if( m_textureRes ) + RequestLod( screenSize, worldRadius, uvDensities, nullptr ); +} + +void TriTextureParameter::UsedWithScreenSize( float screenSize, float worldRadius, const std::vector& uvDensities, Tr2ScreenSizeRequests& requests ) +{ + RequestLod( screenSize, worldRadius, uvDensities, &requests ); +} + +void TriTextureParameter::RequestLod( float screenSize, float worldRadius, const std::vector& uvDensities, Tr2ScreenSizeRequests* requests ) +{ + if( !m_textureRes ) { - uint32_t requestedLod = 0; - if( m_textureLodEnabled && !uvDensities.empty() ) - { - size_t i = 0; - float resolution = 0; + return; + } + uint32_t requestedLod = 0; + if( m_textureLodEnabled && !uvDensities.empty() ) + { + size_t i = 0; + float resolution = 0; + float minDensity = 0; + { + auto density = worldRadius * m_uvDensityScale[0]; + if( density > 0 ) { - auto density = worldRadius * m_uvDensityScale[0]; - if( density > 0 ) - { - resolution = std::max( resolution, screenSize / density ); - } + resolution = std::max( resolution, screenSize / density ); + minDensity = density; } + } - for( auto uv : uvDensities ) + for( auto uv : uvDensities ) + { + auto scale = m_uvDensityScale[1 + i++]; + auto density = uv * scale; + if( density > 0 ) { - auto scale = m_uvDensityScale[1 + i++]; - auto density = uv * scale; - if( density > 0 ) - { - resolution = std::max( resolution, screenSize / density ); - } + resolution = std::max( resolution, screenSize / density ); + minDensity = minDensity > 0 ? std::min( minDensity, density ) : density; } - if( resolution != 0 ) + } + if( resolution != 0 ) + { + auto requestedResolution = std::max( 1.f, resolution ); + auto resolutionChange = (uint32_t)( m_textureRes->GetOriginalResolutionAsFloat() / requestedResolution ); + if( resolutionChange > 0 ) { - auto requestedResolution = std::max( 1.f, resolution ); - auto resolutionChange = (uint32_t)( m_textureRes->GetOriginalResolutionAsFloat() / requestedResolution ); - if( resolutionChange > 0 ) - { - // quickly calculate log2 of resolutionChange, which gives us the required LOD + // quickly calculate log2 of resolutionChange, which gives us the required LOD #if __APPLE__ - requestedLod = 31 - (uint32_t)__builtin_clz( resolutionChange ); + requestedLod = 31 - (uint32_t)__builtin_clz( resolutionChange ); #else - unsigned long reverse; - _BitScanReverse( &reverse, resolutionChange ); - requestedLod = reverse; + unsigned long reverse; + _BitScanReverse( &reverse, resolutionChange ); + requestedLod = reverse; #endif - } } - m_textureRes->RequestResolution( requestedLod ); } - else + if( requests ) { - m_textureRes->RequestResolution( requestedLod ); + ConstrainScreenSize( *requests, resolution, minDensity, requestedLod, m_textureRes->GetOriginalResolutionAsFloat() ); } } + if( requests ) + { + requests->Add( m_textureRes, requestedLod ); + } + m_textureRes->RequestResolution( requestedLod ); } // ------------------------------------------------------------- @@ -129,12 +179,14 @@ void TriTextureParameter::SetResourcePath( const char* resourcePath ) void TriTextureParameter::EnableTextureLoding( const std::array& uvDensityScale ) { m_textureLodEnabled = true; + Tr2ScreenSizeRequests::BumpGeneration(); std::copy( begin( uvDensityScale ), end( uvDensityScale ), begin( m_uvDensityScale ) ); } void TriTextureParameter::DisableTextureLoding() { m_textureLodEnabled = false; + Tr2ScreenSizeRequests::BumpGeneration(); } bool TriTextureParameter::OnModified( Be::Var* val ) @@ -150,6 +202,7 @@ bool TriTextureParameter::OnModified( Be::Var* val ) m_lowResResource = nullptr; } m_textureRes = nullptr; + Tr2ScreenSizeRequests::BumpGeneration(); Initialize(); @@ -208,6 +261,7 @@ bool TriTextureParameter::Initialize() m_lowResResource = nullptr; } m_textureRes = nullptr; + Tr2ScreenSizeRequests::BumpGeneration(); if( !m_resourcePath.empty() ) { @@ -235,6 +289,7 @@ bool TriTextureParameter::Initialize() m_resource->OnTextureChange().RegisterListener( this ); } m_textureRes = BlueCastPtr( m_resource ); + Tr2ScreenSizeRequests::BumpGeneration(); } OnTextureChanged(); return true; @@ -264,6 +319,7 @@ void TriTextureParameter::SetResource( ITr2TextureProvider* newRes ) m_lowResResource = nullptr; } m_textureRes = BlueCastPtr( m_resource ); + Tr2ScreenSizeRequests::BumpGeneration(); RebuildEffectHandles( m_cachedEffect ); OnTextureChanged(); } diff --git a/trinity/Shader/Parameter/TriTextureParameter.h b/trinity/Shader/Parameter/TriTextureParameter.h index 8229f1f80..810ff9e63 100644 --- a/trinity/Shader/Parameter/TriTextureParameter.h +++ b/trinity/Shader/Parameter/TriTextureParameter.h @@ -53,6 +53,7 @@ BLUE_CLASS( TriTextureParameter ) : bool SupportsDirtyNotification() const override; void UsedWithScreenSize( float screenSize, float worldRadius, const std::vector& uvDensities ) override; + void UsedWithScreenSize( float screenSize, float worldRadius, const std::vector& uvDensities, Tr2ScreenSizeRequests& requests ) override; void EnableTextureLoding( const std::array& uvDensityScale ) override; void DisableTextureLoding() override; @@ -91,6 +92,8 @@ BLUE_CLASS( TriTextureParameter ) : Tr2ShaderPtr m_cachedEffect; + void RequestLod( float screenSize, float worldRadius, const std::vector& uvDensities, Tr2ScreenSizeRequests* requests ); + BlueSharedString m_name; ITr2TextureProviderPtr m_resource; diff --git a/trinity/Shader/Tr2Effect.cpp b/trinity/Shader/Tr2Effect.cpp index 69f41d81c..3c646b59f 100644 --- a/trinity/Shader/Tr2Effect.cpp +++ b/trinity/Shader/Tr2Effect.cpp @@ -1,6 +1,7 @@ // Copyright © 2023 CCP ehf. #include "StdAfx.h" +#include "Tr2ScreenSizeRequests.h" #include "ccpparser.h" @@ -675,6 +676,7 @@ void Tr2Effect::RebuildCachedDataInternal() auto bk = m_shader; m_shader = nullptr; m_lodTextureParameters.clear(); + Tr2ScreenSizeRequests::BumpGeneration(); m_compatibleWithGdr = true; if( m_effectResource ) @@ -861,6 +863,7 @@ void Tr2Effect::ReleaseCachedData( BlueAsyncRes* p ) m_resources[i]->RebuildEffectHandles( nullptr ); } m_lodTextureParameters.clear(); + Tr2ScreenSizeRequests::BumpGeneration(); } } @@ -2073,6 +2076,7 @@ void Tr2Effect::AddLoddable( ITriEffectParameter* param, const char* name ) loddable->DisableTextureLoding(); } m_lodTextureParameters.push_back( loddable ); + Tr2ScreenSizeRequests::BumpGeneration(); } } } diff --git a/trinity/Shader/Tr2Material.cpp b/trinity/Shader/Tr2Material.cpp index 2fbdeba9c..21c23fecc 100644 --- a/trinity/Shader/Tr2Material.cpp +++ b/trinity/Shader/Tr2Material.cpp @@ -1,6 +1,7 @@ // Copyright © 2017 CCP ehf. #include "StdAfx.h" +#include "Tr2ScreenSizeRequests.h" #include "Tr2Material.h" #include "ITriReroutable.h" #include "Tr2Shader.h" @@ -369,6 +370,14 @@ void Tr2Material::UsedWithScreenSize( float screenSize, float worldRadius, const } } +void Tr2Material::UsedWithScreenSize( float screenSize, float worldRadius, const std::vector& uvDensities, Tr2ScreenSizeRequests& requests ) +{ + for( auto& value : m_lodTextureParameters ) + { + value->UsedWithScreenSize( screenSize, worldRadius, uvDensities, requests ); + } +} + void Tr2Material::GetUsedBindlessTextures( uint32_t techniqueIndex, Tr2BindlessResourcesAL& usedTextures ) { if( !m_shader ) diff --git a/trinity/Shader/Tr2Material.h b/trinity/Shader/Tr2Material.h index 229bed144..44c20dcaf 100644 --- a/trinity/Shader/Tr2Material.h +++ b/trinity/Shader/Tr2Material.h @@ -3,6 +3,7 @@ #pragma once #include "Tr2EffectDescription.h" +struct Tr2ScreenSizeRequests; BLUE_DECLARE( Tr2Shader ); BLUE_DECLARE_INTERFACE( ITriEffectTextureParameter ); @@ -219,6 +220,7 @@ BLUE_CLASS( Tr2Material ) : void MarkConstantBuffersDirty(); void UsedWithScreenSize( float screenSize, float worldRadius, const std::vector& uvDensities ); + void UsedWithScreenSize( float screenSize, float worldRadius, const std::vector& uvDensities, Tr2ScreenSizeRequests& requests ); void GetUsedBindlessTextures( uint32_t techniqueIndex, Tr2BindlessResourcesAL& usedTextures ); diff --git a/trinity/Tr2MeshBase.cpp b/trinity/Tr2MeshBase.cpp index a76db3aa8..5365dd23a 100644 --- a/trinity/Tr2MeshBase.cpp +++ b/trinity/Tr2MeshBase.cpp @@ -3,6 +3,8 @@ #include "StdAfx.h" #include "Tr2MeshBase.h" #include "Resources/TriGeometryRes.h" +#include "Resources/TriTextureRes.h" +#include "TriSettingsRegistrar.h" #include "Utilities/BoundingBox.h" #include "Raytracing/Tr2RaytracingGeometry.h" @@ -575,23 +577,48 @@ void Tr2MeshBase::SetMaterialBoundsAdjustment( const Tr2MaterialBoundsAdjustment CacheBounds(); } +bool g_meshScreenSizeRequestCache = true; +TRI_REGISTER_SETTING( "meshScreenSizeRequestCache", g_meshScreenSizeRequestCache ); + +CCP_STATS_DECLARE( meshScreenSizeCacheMisses, "Trinity/Mesh/screenSizeRequestCacheMisses", true, CST_COUNTER_HIGH, "Meshes that re-evaluated their texture LOD requests this frame." ); + void Tr2MeshBase::UseWithScreenSize( float screenSize, float worldRadius ) const { - if( auto geometry = GetGeometryResource() ) + auto geometry = GetGeometryResource(); + if( !geometry ) + { + return; + } + auto lod = geometry->GetMeshLod( m_meshIndex, screenSize ); + if( !lod ) + { + return; + } + auto& cache = m_screenSizeCache; + const uint32_t generation = Tr2ScreenSizeRequests::Generation(); + if( g_meshScreenSizeRequestCache && cache.geometry == geometry && cache.lod == lod && cache.worldRadius == worldRadius && cache.generation == generation && cache.requests.Covers( screenSize ) ) + { + for( auto& request : cache.requests.requests ) + { + request.texture->RequestResolution( request.lod ); + } + return; + } + CCP_STATS_INC( meshScreenSizeCacheMisses ); + cache.geometry = geometry; + cache.lod = lod; + cache.worldRadius = worldRadius; + cache.generation = generation; + cache.requests.Reset(); + for( auto areaType : m_areaLookupArray ) { - if( auto lod = geometry->GetMeshLod( m_meshIndex, screenSize ) ) + if( areaType ) { - for( auto areaType : m_areaLookupArray ) + for( auto& area : *areaType ) { - if( areaType ) + if( area && area->GetMaterialInterface() ) { - for( auto& area : *areaType ) - { - if( area && area->GetMaterialInterface() ) - { - area->GetMaterialInterface()->UsedWithScreenSize( screenSize, worldRadius, lod->m_uvDensities ); - } - } + area->GetMaterialInterface()->UsedWithScreenSize( screenSize, worldRadius, lod->m_uvDensities, cache.requests ); } } } diff --git a/trinity/Tr2MeshBase.h b/trinity/Tr2MeshBase.h index e665c62bf..9b81fb807 100644 --- a/trinity/Tr2MeshBase.h +++ b/trinity/Tr2MeshBase.h @@ -11,6 +11,7 @@ #include "Utilities/Tr2MaterialBoundsAdjustment.h" #include "Tr2RingBuffer.h" +#include "Tr2ScreenSizeRequests.h" BLUE_DECLARE( TriGeometryRes ); @@ -126,6 +127,16 @@ BLUE_CLASS( Tr2MeshBase ) : PTr2MeshAreaVector* m_areaLookupArray[TRIBATCHTYPE_COUNT_OF_BATCH_TYPES]; + struct ScreenSizeCache + { + const TriGeometryRes* geometry = nullptr; + const TriGeometryResLodData* lod = nullptr; + float worldRadius = 0.0f; + uint32_t generation = 0; + Tr2ScreenSizeRequests requests; + }; + mutable ScreenSizeCache m_screenSizeCache; + // skeleton/bone data std::vector m_jointMappingAnimRig; const std::string* m_pBoneList; diff --git a/trinity/Tr2ScreenSizeRequests.h b/trinity/Tr2ScreenSizeRequests.h new file mode 100644 index 000000000..0014e45aa --- /dev/null +++ b/trinity/Tr2ScreenSizeRequests.h @@ -0,0 +1,72 @@ +// Copyright © 2026 CCP ehf. + +#pragma once +#ifndef Tr2ScreenSizeRequests_H +#define Tr2ScreenSizeRequests_H + +#include +#include +#include + +class TriTextureRes; + +// The texture LOD requests one UseWithScreenSize walk produced, plus the screen-size range over which +// every request stays the same, so later frames can replay them instead of re-walking the materials. +struct Tr2ScreenSizeRequests +{ + struct Request + { + TriTextureRes* texture; + uint32_t lod; + }; + + std::vector requests; + float minScreenSize = 0.0f; + float maxScreenSize = std::numeric_limits::max(); + bool valid = true; + + void Reset() + { + requests.clear(); + minScreenSize = 0.0f; + maxScreenSize = std::numeric_limits::max(); + valid = true; + } + + void Add( TriTextureRes* texture, uint32_t lod ) + { + requests.push_back( { texture, lod } ); + } + + void Constrain( float lo, float hi ) + { + minScreenSize = std::max( minScreenSize, lo ); + maxScreenSize = std::min( maxScreenSize, hi ); + } + + void Invalidate() + { + valid = false; + } + + bool Covers( float screenSize ) const + { + return valid && screenSize >= minScreenSize && screenSize < maxScreenSize; + } + + // Bumped whenever anything a cached request depends on changes (texture assigned/loaded/destroyed, LOD parameters). + static uint32_t Generation() + { + return s_generation.load( std::memory_order_acquire ); + } + + static void BumpGeneration() + { + s_generation.fetch_add( 1, std::memory_order_acq_rel ); + } + +private: + inline static std::atomic s_generation{ 1 }; +}; + +#endif