diff --git a/Source/Engine/Graphics/GPUContext.cpp b/Source/Engine/Graphics/GPUContext.cpp index cff2c4766..a1955fb4c 100644 --- a/Source/Engine/Graphics/GPUContext.cpp +++ b/Source/Engine/Graphics/GPUContext.cpp @@ -13,6 +13,7 @@ GPUContext::GPUContext(GPUDevice* device) void GPUContext::FrameBegin() { + _lastRenderTime = Platform::GetTimeSeconds(); } void GPUContext::FrameEnd() diff --git a/Source/Engine/Graphics/GPUContext.h b/Source/Engine/Graphics/GPUContext.h index ed2417c96..5e1219fa2 100644 --- a/Source/Engine/Graphics/GPUContext.h +++ b/Source/Engine/Graphics/GPUContext.h @@ -121,18 +121,14 @@ private: protected: - /// - /// Initializes a new instance of the class. - /// - /// The graphics device. + double _lastRenderTime = -1; GPUContext(GPUDevice* device); public: /// - /// Gets the graphics device handle + /// Gets the graphics device. /// - /// Graphics device FORCE_INLINE GPUDevice* GetDevice() const { return _device; diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index 5ea4914ba..e3f6542b5 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -191,6 +191,8 @@ void GPUResource::OnDeleteObject() PersistentScriptingObject::OnDeleteObject(); } +double GPUResourceView::DummyLastRenderTime = -1; + struct GPUDevice::PrivateData { AssetReference QuadShader; diff --git a/Source/Engine/Graphics/GPUResource.h b/Source/Engine/Graphics/GPUResource.h index 92fa9c769..4f7d09c63 100644 --- a/Source/Engine/Graphics/GPUResource.h +++ b/Source/Engine/Graphics/GPUResource.h @@ -65,6 +65,9 @@ public: public: + // Points to the cache used by the resource for the resource visibility/usage detection. Written during rendering when resource is used. + double LastRenderTime = -1; + /// /// Action fired when resource GPU state gets released. All objects and async tasks using this resource should release references to this object nor use its data. /// @@ -215,14 +218,19 @@ API_CLASS(Abstract, NoSpawn, Attributes="HideInEditor") class FLAXENGINE_API GPU { DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUResourceView); protected: + static double DummyLastRenderTime; explicit GPUResourceView(const SpawnParams& params) : PersistentScriptingObject(params) + , LastRenderTime(&DummyLastRenderTime) { } public: + // Points to the cache used by the resource for the resource visibility/usage detection. Written during rendering when resource view is used. + double* LastRenderTime; + /// /// Gets the native pointer to the underlying view. It's a platform-specific handle. /// diff --git a/Source/Engine/Graphics/Textures/GPUTexture.h b/Source/Engine/Graphics/Textures/GPUTexture.h index 8b5385f9e..82171182b 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.h +++ b/Source/Engine/Graphics/Textures/GPUTexture.h @@ -31,11 +31,12 @@ protected: { } - void Init(GPUResource* parent, PixelFormat format, MSAALevel msaa) + FORCE_INLINE void Init(GPUResource* parent, PixelFormat format, MSAALevel msaa) { _parent = parent; _format = format; _msaa = msaa; + LastRenderTime = &parent->LastRenderTime; } public: diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp index 9c9af3145..b283c7a5f 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp @@ -327,26 +327,26 @@ void GPUContextDX11::BindCB(int32 slot, GPUConstantBuffer* cb) void GPUContextDX11::BindSR(int32 slot, GPUResourceView* view) { ASSERT(slot >= 0 && slot < GPU_MAX_SR_BINDED); - auto handle = view ? ((IShaderResourceDX11*)view->GetNativePtr())->SRV() : nullptr; - if (_srHandles[slot] != handle) { _srDirtyFlag = true; _srHandles[slot] = handle; + if (view) + *view->LastRenderTime = _lastRenderTime; } } void GPUContextDX11::BindUA(int32 slot, GPUResourceView* view) { ASSERT(slot >= 0 && slot < GPU_MAX_UA_BINDED); - auto handle = view ? ((IShaderResourceDX11*)view->GetNativePtr())->UAV() : nullptr; - if (_uaHandles[slot] != handle) { _uaDirtyFlag = true; _uaHandles[slot] = handle; + if (view) + *view->LastRenderTime = _lastRenderTime; } } diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index 0d3309219..ffa0a5550 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -809,9 +809,7 @@ void GPUContextDX12::ResetCB() void GPUContextDX12::BindCB(int32 slot, GPUConstantBuffer* cb) { ASSERT(slot >= 0 && slot < GPU_MAX_CB_BINDED); - auto cbDX12 = static_cast(cb); - if (_cbHandles[slot] != cbDX12) { _cbDirtyFlag = true; @@ -828,6 +826,8 @@ void GPUContextDX12::BindSR(int32 slot, GPUResourceView* view) _srMaskDirtyGraphics |= 1 << slot; _srMaskDirtyCompute |= 1 << slot; _srHandles[slot] = handle; + if (view) + *view->LastRenderTime = _lastRenderTime; } } @@ -835,6 +835,8 @@ void GPUContextDX12::BindUA(int32 slot, GPUResourceView* view) { ASSERT(slot >= 0 && slot < GPU_MAX_UA_BINDED); _uaHandles[slot] = view ? (IShaderResourceDX12*)view->GetNativePtr() : nullptr; + if (view) + *view->LastRenderTime = _lastRenderTime; } void GPUContextDX12::BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets) diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index 63b5503a8..6e73ec157 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -948,26 +948,26 @@ void GPUContextVulkan::BindCB(int32 slot, GPUConstantBuffer* cb) void GPUContextVulkan::BindSR(int32 slot, GPUResourceView* view) { ASSERT(slot >= 0 && slot < GPU_MAX_SR_BINDED); - const auto handle = view ? (DescriptorOwnerResourceVulkan*)view->GetNativePtr() : nullptr; - if (_srHandles[slot] != handle) { _srDirtyFlag = true; _srHandles[slot] = handle; + if (view) + *view->LastRenderTime = _lastRenderTime; } } void GPUContextVulkan::BindUA(int32 slot, GPUResourceView* view) { ASSERT(slot >= 0 && slot < GPU_MAX_UA_BINDED); - const auto handle = view ? (DescriptorOwnerResourceVulkan*)view->GetNativePtr() : nullptr; - if (_uaHandles[slot] != handle) { _uaDirtyFlag = true; _uaHandles[slot] = handle; + if (view) + *view->LastRenderTime = _lastRenderTime; } } diff --git a/Source/Engine/Streaming/IStreamingHandler.h b/Source/Engine/Streaming/IStreamingHandler.h index 3e9674141..299c530b4 100644 --- a/Source/Engine/Streaming/IStreamingHandler.h +++ b/Source/Engine/Streaming/IStreamingHandler.h @@ -22,9 +22,10 @@ public: /// Calculates target quality level (0-1) for the given resource. /// /// The resource. - /// The current time. + /// The current time and date. + /// The current platform time (seconds). /// Target quality (0-1). - virtual float CalculateTargetQuality(StreamableResource* resource, DateTime now) = 0; + virtual float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) = 0; /// /// Calculates the residency level for a given resource and quality level. diff --git a/Source/Engine/Streaming/Streaming.cpp b/Source/Engine/Streaming/Streaming.cpp index 8b879b3dd..395a890b9 100644 --- a/Source/Engine/Streaming/Streaming.cpp +++ b/Source/Engine/Streaming/Streaming.cpp @@ -84,7 +84,7 @@ void StreamableResource::StopStreaming() } } -void UpdateResource(StreamableResource* resource, DateTime now) +void UpdateResource(StreamableResource* resource, DateTime now, double currentTime) { ASSERT(resource && resource->CanBeUpdated()); @@ -96,7 +96,7 @@ void UpdateResource(StreamableResource* resource, DateTime now) float targetQuality = 1.0f; if (resource->IsDynamic()) { - targetQuality = handler->CalculateTargetQuality(resource, now); + targetQuality = handler->CalculateTargetQuality(resource, now, currentTime); targetQuality = Math::Saturate(targetQuality); } @@ -175,7 +175,7 @@ void StreamingManagerService::Update() // Configuration // TODO: use game settings static TimeSpan ManagerUpdatesInterval = TimeSpan::FromMilliseconds(30); - static TimeSpan ResourceUpdatesInterval = TimeSpan::FromMilliseconds(200); + static TimeSpan ResourceUpdatesInterval = TimeSpan::FromMilliseconds(100); static int32 MaxResourcesPerUpdate = 50; // Check if skip update @@ -191,6 +191,7 @@ void StreamingManagerService::Update() // Start update int32 resourcesUpdates = Math::Min(MaxResourcesPerUpdate, resourcesCount); + double currentTime = Platform::GetTimeSeconds(); // Update high priority queue and then rest of the resources // Note: resources in the update queue are updated always, while others only between specified intervals @@ -208,7 +209,7 @@ void StreamingManagerService::Update() // Try to update it if (now - resource->Streaming.LastUpdate >= ResourceUpdatesInterval && resource->CanBeUpdated()) { - UpdateResource(resource, now); + UpdateResource(resource, now, currentTime); resourcesUpdates--; } } diff --git a/Source/Engine/Streaming/StreamingHandlers.cpp b/Source/Engine/Streaming/StreamingHandlers.cpp index bf43d317a..2fb4cd840 100644 --- a/Source/Engine/Streaming/StreamingHandlers.cpp +++ b/Source/Engine/Streaming/StreamingHandlers.cpp @@ -4,26 +4,33 @@ #include "Streaming.h" #include "Engine/Core/Math/Math.h" #include "Engine/Graphics/Textures/StreamingTexture.h" +#include "Engine/Graphics/Textures/GPUTexture.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Audio/AudioClip.h" #include "Engine/Audio/Audio.h" #include "Engine/Audio/AudioSource.h" -float TexturesStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now) +float TexturesStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) { ASSERT(resource); auto& texture = *(StreamingTexture*)resource; const TextureHeader& header = *texture.GetHeader(); - if (header.TextureGroup < 0 || header.TextureGroup >= Streaming::TextureGroups.Count()) + float result = 1.0f; + if (header.TextureGroup >= 0 && header.TextureGroup < Streaming::TextureGroups.Count()) { - // Full texture load by default - return 1.0f; - } + // Quality based on texture group settings + const TextureGroup& group = Streaming::TextureGroups[header.TextureGroup]; + result = group.Quality; - // Quality based on texture group settings - const TextureGroup& group = Streaming::TextureGroups[header.TextureGroup]; - return group.QualityScale; + // Drop quality if invisible + const double lastRenderTime = texture.GetTexture()->LastRenderTime; + if (lastRenderTime < 0 || group.TimeToInvisible <= (float)(currentTime - lastRenderTime)) + { + result *= group.QualityIfInvisible; + } + } + return result; } int32 TexturesStreamingHandler::CalculateResidency(StreamableResource* resource, float quality) @@ -86,7 +93,7 @@ int32 TexturesStreamingHandler::CalculateRequestedResidency(StreamableResource* return residency; } -float ModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now) +float ModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) { // TODO: calculate a proper quality levels for models based on render time and streaming enable/disable options return 1.0f; @@ -127,7 +134,7 @@ int32 ModelsStreamingHandler::CalculateRequestedResidency(StreamableResource* re return residency; } -float SkinnedModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now) +float SkinnedModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) { // TODO: calculate a proper quality levels for models based on render time and streaming enable/disable options return 1.0f; @@ -168,7 +175,7 @@ int32 SkinnedModelsStreamingHandler::CalculateRequestedResidency(StreamableResou return residency; } -float AudioStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now) +float AudioStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) { // Audio clips don't use quality but only residency return 1.0f; diff --git a/Source/Engine/Streaming/StreamingHandlers.h b/Source/Engine/Streaming/StreamingHandlers.h index b6080d616..0c20dcfda 100644 --- a/Source/Engine/Streaming/StreamingHandlers.h +++ b/Source/Engine/Streaming/StreamingHandlers.h @@ -11,7 +11,7 @@ class FLAXENGINE_API TexturesStreamingHandler : public IStreamingHandler { public: // [IStreamingHandler] - float CalculateTargetQuality(StreamableResource* resource, DateTime now) override; + float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) override; int32 CalculateResidency(StreamableResource* resource, float quality) override; int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; }; @@ -23,7 +23,7 @@ class FLAXENGINE_API ModelsStreamingHandler : public IStreamingHandler { public: // [IStreamingHandler] - float CalculateTargetQuality(StreamableResource* resource, DateTime now) override; + float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) override; int32 CalculateResidency(StreamableResource* resource, float quality) override; int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; }; @@ -35,7 +35,7 @@ class FLAXENGINE_API SkinnedModelsStreamingHandler : public IStreamingHandler { public: // [IStreamingHandler] - float CalculateTargetQuality(StreamableResource* resource, DateTime now) override; + float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) override; int32 CalculateResidency(StreamableResource* resource, float quality) override; int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; }; @@ -47,7 +47,7 @@ class FLAXENGINE_API AudioStreamingHandler : public IStreamingHandler { public: // [IStreamingHandler] - float CalculateTargetQuality(StreamableResource* resource, DateTime now) override; + float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) override; int32 CalculateResidency(StreamableResource* resource, float quality) override; int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override; bool RequiresStreaming(StreamableResource* resource, int32 currentResidency, int32 targetResidency) override; diff --git a/Source/Engine/Streaming/TextureGroup.h b/Source/Engine/Streaming/TextureGroup.h index dfd6dba07..87ad7dc4d 100644 --- a/Source/Engine/Streaming/TextureGroup.h +++ b/Source/Engine/Streaming/TextureGroup.h @@ -26,7 +26,19 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextureGroup); /// The quality scale factor applied to textures in this group. Can be used to increase or decrease textures resolution. In range 0-1 where 0 means lowest quality, 1 means full quality. /// API_FIELD(Attributes="EditorOrder(20), Limit(0, 1)") - float QualityScale = 1.0f; + float Quality = 1.0f; + + /// + /// The quality scale factor applied when texture is invisible for some time (defined by TimeToInvisible). Used to decrease texture quality when it's not rendered. + /// + API_FIELD(Attributes="EditorOrder(25), Limit(0, 1)") + float QualityIfInvisible = 0.5f; + + /// + /// The time (in seconds) after which texture is considered to be invisible (if it's not rendered by a certain amount of time). + /// + API_FIELD(Attributes="EditorOrder(26), Limit(0)") + float TimeToInvisible = 20.0f; /// /// The minimum amount of loaded mip levels for textures in this group. Defines the amount of the mips that should be always loaded. Higher values decrease streaming usage and keep more mips loaded.