Add dynamic textures streaming based on visibility
This commit is contained in:
@@ -13,6 +13,7 @@ GPUContext::GPUContext(GPUDevice* device)
|
||||
|
||||
void GPUContext::FrameBegin()
|
||||
{
|
||||
_lastRenderTime = Platform::GetTimeSeconds();
|
||||
}
|
||||
|
||||
void GPUContext::FrameEnd()
|
||||
|
||||
@@ -121,18 +121,14 @@ private:
|
||||
|
||||
protected:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GPUContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="device">The graphics device.</param>
|
||||
double _lastRenderTime = -1;
|
||||
GPUContext(GPUDevice* device);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the graphics device handle
|
||||
/// Gets the graphics device.
|
||||
/// </summary>
|
||||
/// <returns>Graphics device</returns>
|
||||
FORCE_INLINE GPUDevice* GetDevice() const
|
||||
{
|
||||
return _device;
|
||||
|
||||
@@ -191,6 +191,8 @@ void GPUResource::OnDeleteObject()
|
||||
PersistentScriptingObject::OnDeleteObject();
|
||||
}
|
||||
|
||||
double GPUResourceView::DummyLastRenderTime = -1;
|
||||
|
||||
struct GPUDevice::PrivateData
|
||||
{
|
||||
AssetReference<Shader> QuadShader;
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the native pointer to the underlying view. It's a platform-specific handle.
|
||||
/// </summary>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<GPUConstantBufferDX12*>(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<GPUBuffer*>& vertexBuffers, const uint32* vertexBuffersOffsets)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,10 @@ public:
|
||||
/// Calculates target quality level (0-1) for the given resource.
|
||||
/// </summary>
|
||||
/// <param name="resource">The resource.</param>
|
||||
/// <param name="now">The current time.</param>
|
||||
/// <param name="now">The current time and date.</param>
|
||||
/// <param name="currentTime">The current platform time (seconds).</param>
|
||||
/// <returns>Target quality (0-1).</returns>
|
||||
virtual float CalculateTargetQuality(StreamableResource* resource, DateTime now) = 0;
|
||||
virtual float CalculateTargetQuality(StreamableResource* resource, DateTime now, double currentTime) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the residency level for a given resource and quality level.
|
||||
|
||||
@@ -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--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(20), Limit(0, 1)")
|
||||
float QualityScale = 1.0f;
|
||||
float Quality = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(25), Limit(0, 1)")
|
||||
float QualityIfInvisible = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// The time (in seconds) after which texture is considered to be invisible (if it's not rendered by a certain amount of time).
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(26), Limit(0)")
|
||||
float TimeToInvisible = 20.0f;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
|
||||
Reference in New Issue
Block a user