Files
FlaxEngine/Source/Engine/Streaming/Streaming.cpp
2022-01-14 13:31:12 +01:00

325 lines
9.8 KiB
C++

// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
#include "Streaming.h"
#include "StreamableResource.h"
#include "StreamingGroup.h"
#include "StreamingSettings.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Threading/TaskGraph.h"
#include "Engine/Threading/Task.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Textures/GPUSampler.h"
#include "Engine/Serialization/Serialization.h"
namespace StreamingManagerImpl
{
int32 LastUpdateResourcesIndex = 0;
CriticalSection ResourcesLock;
Array<StreamableResource*> Resources;
Array<GPUSampler*, InlinedAllocation<32>> TextureGroupSamplers;
GPUSampler* FallbackSampler = nullptr;
}
using namespace StreamingManagerImpl;
class StreamingService : public EngineService
{
public:
StreamingService()
: EngineService(TEXT("Streaming"), 100)
{
}
bool Init() override;
void BeforeExit() override;
};
class StreamingSystem : public TaskGraphSystem
{
public:
void Job(int32 index);
void Execute(TaskGraph* graph) override;
};
namespace
{
TaskGraphSystem* System = nullptr;
}
StreamingService StreamingServiceInstance;
Array<TextureGroup, InlinedAllocation<32>> Streaming::TextureGroups;
void StreamingSettings::Apply()
{
Streaming::TextureGroups = TextureGroups;
SAFE_DELETE_GPU_RESOURCES(TextureGroupSamplers);
TextureGroupSamplers.Resize(TextureGroups.Count(), false);
}
void StreamingSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
DESERIALIZE(TextureGroups);
}
StreamableResource::StreamableResource(StreamingGroup* group)
: _group(group)
, _isDynamic(true)
, _isStreaming(false)
, _streamingQuality(1.0f)
{
ASSERT(_group != nullptr);
}
StreamableResource::~StreamableResource()
{
StopStreaming();
}
void StreamableResource::RequestStreamingUpdate()
{
Streaming.LastUpdate = 0;
}
void StreamableResource::CancelStreaming()
{
Streaming.TargetResidency = 0;
Streaming.LastUpdate = DateTime::MaxValue().Ticks;
}
void StreamableResource::StartStreaming(bool isDynamic)
{
_isDynamic = isDynamic;
if (!_isStreaming)
{
_isStreaming = true;
ResourcesLock.Lock();
Resources.Add(this);
ResourcesLock.Unlock();
}
}
void StreamableResource::StopStreaming()
{
if (_isStreaming)
{
ResourcesLock.Lock();
Resources.Remove(this);
ResourcesLock.Unlock();
Streaming = StreamingCache();
_isStreaming = false;
}
}
void UpdateResource(StreamableResource* resource, DateTime now, double currentTime)
{
ASSERT(resource && resource->CanBeUpdated());
// Pick group and handler dedicated for that resource
auto group = resource->GetGroup();
auto handler = group->GetHandler();
// Calculate target quality for that asset
float targetQuality = 1.0f;
if (resource->IsDynamic())
{
targetQuality = handler->CalculateTargetQuality(resource, now, currentTime);
targetQuality = Math::Saturate(targetQuality);
}
// Update quality smoothing
resource->Streaming.QualitySamples.Add(targetQuality);
targetQuality = resource->Streaming.QualitySamples.Maximum();
targetQuality = Math::Saturate(targetQuality);
// Calculate target residency level (discrete value)
auto maxResidency = resource->GetMaxResidency();
auto currentResidency = resource->GetCurrentResidency();
auto allocatedResidency = resource->GetAllocatedResidency();
auto targetResidency = handler->CalculateResidency(resource, targetQuality);
ASSERT(allocatedResidency >= currentResidency && allocatedResidency >= 0);
resource->Streaming.LastUpdate = now.Ticks;
// Check if a target residency level has been changed
if (targetResidency != resource->Streaming.TargetResidency)
{
// Register change
resource->Streaming.TargetResidency = targetResidency;
resource->Streaming.TargetResidencyChange = now.Ticks;
}
// Check if need to change resource current residency
if (handler->RequiresStreaming(resource, currentResidency, targetResidency))
{
// Check if need to change allocation for that resource
if (allocatedResidency != targetResidency)
{
// Update resource allocation
Task* allocateTask = resource->UpdateAllocation(targetResidency);
if (allocateTask)
{
// When resource wants to perform reallocation on a task then skip further updating until it's done
allocateTask->Start();
resource->RequestStreamingUpdate();
return;
}
else if (resource->GetAllocatedResidency() < targetResidency)
{
// Allocation failed (eg. texture format is not supported or run out of memory)
resource->CancelStreaming();
return;
}
}
// Calculate residency level to stream in (resources may want to increase/decrease it's quality in steps rather than at once)
int32 requestedResidency = handler->CalculateRequestedResidency(resource, targetResidency);
// Create streaming task (resource type specific)
Task* streamingTask = resource->CreateStreamingTask(requestedResidency);
if (streamingTask != nullptr)
{
streamingTask->Start();
}
}
else
{
// TODO: Check if target residency is stable (no changes for a while)
// TODO: deallocate or decrease memory usage after timeout? (timeout should be smaller on low mem)
}
// low memory case:
// if we are on budget and cannot load everything we have to:
// decrease global resources quality scale (per resources group)
// decrease asset deallocate timeout
// low mem detecting:
// for low mem we have to get whole memory budget for a group and then
// subtract immutable resources mem usage (like render targets or non dynamic resources)
// so we get amount of memory we can distribute and we can detect if we are out of the limit
// low mem should be updated once per a few frames
}
bool StreamingService::Init()
{
System = New<StreamingSystem>();
Engine::UpdateGraph->AddSystem(System);
return false;
}
void StreamingService::BeforeExit()
{
SAFE_DELETE_GPU_RESOURCE(FallbackSampler);
SAFE_DELETE_GPU_RESOURCES(TextureGroupSamplers);
TextureGroupSamplers.Resize(0);
SAFE_DELETE(System);
}
void StreamingSystem::Job(int32 index)
{
PROFILE_CPU_NAMED("Streaming.Job");
// TODO: use streaming settings
TimeSpan ResourceUpdatesInterval = TimeSpan::FromMilliseconds(100);
int32 MaxResourcesPerUpdate = 50;
// Start update
ScopeLock lock(ResourcesLock);
auto now = DateTime::NowUTC();
const int32 resourcesCount = Resources.Count();
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
int32 resourcesChecks = resourcesCount;
while (resourcesUpdates > 0 && resourcesChecks-- > 0)
{
// Move forward
LastUpdateResourcesIndex++;
if (LastUpdateResourcesIndex >= resourcesCount)
LastUpdateResourcesIndex = 0;
// Peek resource
const auto resource = Resources[LastUpdateResourcesIndex];
// Try to update it
if (now - DateTime(resource->Streaming.LastUpdate) >= ResourceUpdatesInterval && resource->CanBeUpdated())
{
UpdateResource(resource, now, currentTime);
resourcesUpdates--;
}
}
// TODO: add StreamingManager stats, update time per frame, updates per frame, etc.
}
void StreamingSystem::Execute(TaskGraph* graph)
{
if (Resources.Count() == 0 || GPUDevice::Instance->GetState() != GPUDevice::DeviceState::Ready)
return;
// Schedule work to update all storage containers in async
Function<void(int32)> job;
job.Bind<StreamingSystem, &StreamingSystem::Job>(this);
graph->DispatchJob(job, 1);
}
StreamingStats Streaming::GetStats()
{
StreamingStats stats;
ResourcesLock.Lock();
stats.ResourcesCount = Resources.Count();
for (auto e : Resources)
{
if (e->Streaming.TargetResidency > e->GetCurrentResidency())
stats.StreamingResourcesCount++;
}
ResourcesLock.Unlock();
return stats;
}
void Streaming::RequestStreamingUpdate()
{
PROFILE_CPU();
ResourcesLock.Lock();
for (auto e : Resources)
e->RequestStreamingUpdate();
ResourcesLock.Unlock();
}
GPUSampler* Streaming::GetTextureGroupSampler(int32 index)
{
GPUSampler* sampler = nullptr;
if (index >= 0 && index < TextureGroupSamplers.Count())
{
// Sampler from texture group options
auto& group = TextureGroups[index];
auto desc = GPUSamplerDescription::New(group.SamplerFilter);
desc.MaxAnisotropy = group.MaxAnisotropy;
sampler = TextureGroupSamplers[index];
if (!sampler)
{
sampler = GPUSampler::New();
sampler->Init(desc);
TextureGroupSamplers[index] = sampler;
}
if (sampler->GetDescription().Filter != desc.Filter || sampler->GetDescription().MaxAnisotropy != desc.MaxAnisotropy)
sampler->Init(desc);
}
if (!sampler)
{
// Default sampler to prevent issue
if (!FallbackSampler)
{
FallbackSampler = GPUSampler::New();
FallbackSampler->Init(GPUSamplerDescription::New(GPUSamplerFilter::Trilinear));
}
sampler = FallbackSampler;
}
return sampler;
}