You're breathtaking!
This commit is contained in:
16
Source/Engine/Streaming/Config.h
Normal file
16
Source/Engine/Streaming/Config.h
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Resources streaming types
|
||||
typedef float StreamingQuality;
|
||||
|
||||
#define MIN_STREAMING_QUALITY 0.0f
|
||||
#define MAX_STREAMING_QUALITY 1.0f
|
||||
|
||||
/// <summary>
|
||||
/// Enables/disables dynamic resources streaming feature
|
||||
/// </summary>
|
||||
#define ENABLE_RESOURCES_DYNAMIC_STREAMING 1
|
||||
|
||||
class StreamableResource;
|
||||
67
Source/Engine/Streaming/Handlers/AudioStreamingHandler.cpp
Normal file
67
Source/Engine/Streaming/Handlers/AudioStreamingHandler.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "AudioStreamingHandler.h"
|
||||
#include "Engine/Audio/AudioClip.h"
|
||||
#include "Engine/Audio/Audio.h"
|
||||
#include "Engine/Audio/AudioSource.h"
|
||||
|
||||
StreamingQuality AudioStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now)
|
||||
{
|
||||
// Audio clips don't use quality but only residency
|
||||
return MAX_STREAMING_QUALITY;
|
||||
}
|
||||
|
||||
int32 AudioStreamingHandler::CalculateResidency(StreamableResource* resource, StreamingQuality quality)
|
||||
{
|
||||
ASSERT(resource);
|
||||
auto clip = static_cast<AudioClip*>(resource);
|
||||
const int32 chunksCount = clip->Buffers.Count();
|
||||
bool chunksMask[ASSET_FILE_DATA_CHUNKS];
|
||||
Platform::MemoryClear(chunksMask, sizeof(chunksMask));
|
||||
|
||||
// Find audio chunks required for streaming
|
||||
clip->StreamingQueue.Clear();
|
||||
for (int32 sourceIndex = 0; sourceIndex < Audio::Sources.Count(); sourceIndex++)
|
||||
{
|
||||
// TODO: collect refs to audio clip from sources and use faster iteration (but do it thread-safe)
|
||||
|
||||
const auto src = Audio::Sources[sourceIndex];
|
||||
if (src->Clip == clip && src->GetState() == AudioSource::States::Playing)
|
||||
{
|
||||
// Stream the current and the next chunk if could be used in a while
|
||||
const int32 chunk = src->_streamingFirstChunk;
|
||||
ASSERT(Math::IsInRange(chunk, 0, chunksCount));
|
||||
chunksMask[chunk] = true;
|
||||
|
||||
const float StreamingDstSec = 2.0f;
|
||||
if (chunk + 1 < chunksCount && src->GetTime() + StreamingDstSec >= clip->GetBufferStartTime(src->_streamingFirstChunk))
|
||||
{
|
||||
chunksMask[chunk + 1] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to enqueue chunks to modify (load or unload)
|
||||
for (int32 i = 0; i < chunksCount; i++)
|
||||
{
|
||||
if (chunksMask[i] != (clip->Buffers[i] != 0))
|
||||
{
|
||||
clip->StreamingQueue.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
return clip->StreamingQueue.Count();
|
||||
}
|
||||
|
||||
int32 AudioStreamingHandler::CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency)
|
||||
{
|
||||
// No smoothing or slowdown in residency change
|
||||
return targetResidency;
|
||||
}
|
||||
|
||||
bool AudioStreamingHandler::RequiresStreaming(StreamableResource* resource, int32 currentResidency, int32 targetResidency)
|
||||
{
|
||||
// Audio clips use streaming queue buffer to detect streaming request start
|
||||
const auto clip = static_cast<AudioClip*>(resource);
|
||||
return clip->StreamingQueue.HasItems();
|
||||
}
|
||||
20
Source/Engine/Streaming/Handlers/AudioStreamingHandler.h
Normal file
20
Source/Engine/Streaming/Handlers/AudioStreamingHandler.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Streaming/IStreamingHandler.h"
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of IStreamingHandler for audio resources.
|
||||
/// </summary>
|
||||
/// <seealso cref="IStreamingHandler" />
|
||||
class FLAXENGINE_API AudioStreamingHandler : public IStreamingHandler
|
||||
{
|
||||
public:
|
||||
|
||||
// [IStreamingHandler]
|
||||
StreamingQuality CalculateTargetQuality(StreamableResource* resource, DateTime now) override;
|
||||
int32 CalculateResidency(StreamableResource* resource, StreamingQuality quality) override;
|
||||
int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override;
|
||||
bool RequiresStreaming(StreamableResource* resource, int32 currentResidency, int32 targetResidency) override;
|
||||
};
|
||||
50
Source/Engine/Streaming/Handlers/ModelsStreamingHandler.cpp
Normal file
50
Source/Engine/Streaming/Handlers/ModelsStreamingHandler.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ModelsStreamingHandler.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
|
||||
StreamingQuality ModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now)
|
||||
{
|
||||
ASSERT(resource);
|
||||
|
||||
// TODO: calculate a proper quality levels for models based on render time and streaming enable/disable options
|
||||
|
||||
return MAX_STREAMING_QUALITY;
|
||||
}
|
||||
|
||||
int32 ModelsStreamingHandler::CalculateResidency(StreamableResource* resource, StreamingQuality quality)
|
||||
{
|
||||
ASSERT(resource);
|
||||
auto& model = *(Model*)resource;
|
||||
auto lodCount = model.GetLODsCount();
|
||||
|
||||
if (quality < ZeroTolerance)
|
||||
return 0;
|
||||
|
||||
int32 lods = Math::CeilToInt(quality * lodCount);
|
||||
|
||||
ASSERT(model.IsValidLODIndex(lods - 1));
|
||||
return lods;
|
||||
}
|
||||
|
||||
int32 ModelsStreamingHandler::CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency)
|
||||
{
|
||||
ASSERT(resource);
|
||||
auto& model = *(Model*)resource;
|
||||
|
||||
// Always load only single LOD at once
|
||||
int32 residency = targetResidency;
|
||||
int32 currentResidency = model.GetCurrentResidency();
|
||||
if (currentResidency < targetResidency)
|
||||
{
|
||||
// Up
|
||||
residency = currentResidency + 1;
|
||||
}
|
||||
else if (currentResidency > targetResidency)
|
||||
{
|
||||
// Down
|
||||
residency = currentResidency - 1;
|
||||
}
|
||||
|
||||
return residency;
|
||||
}
|
||||
19
Source/Engine/Streaming/Handlers/ModelsStreamingHandler.h
Normal file
19
Source/Engine/Streaming/Handlers/ModelsStreamingHandler.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Streaming/IStreamingHandler.h"
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of IStreamingHandler for streamable models.
|
||||
/// </summary>
|
||||
/// <seealso cref="IStreamingHandler" />
|
||||
class FLAXENGINE_API ModelsStreamingHandler : public IStreamingHandler
|
||||
{
|
||||
public:
|
||||
|
||||
// [IStreamingHandler]
|
||||
StreamingQuality CalculateTargetQuality(StreamableResource* resource, DateTime now) override;
|
||||
int32 CalculateResidency(StreamableResource* resource, StreamingQuality quality) override;
|
||||
int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override;
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SkinnedModelsStreamingHandler.h"
|
||||
#include "Engine/Content/Assets/SkinnedModel.h"
|
||||
|
||||
StreamingQuality SkinnedModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now)
|
||||
{
|
||||
ASSERT(resource);
|
||||
|
||||
// TODO: calculate a proper quality levels for models based on render time and streaming enable/disable options
|
||||
|
||||
return MAX_STREAMING_QUALITY;
|
||||
}
|
||||
|
||||
int32 SkinnedModelsStreamingHandler::CalculateResidency(StreamableResource* resource, StreamingQuality quality)
|
||||
{
|
||||
ASSERT(resource);
|
||||
auto& model = *(SkinnedModel*)resource;
|
||||
auto lodCount = model.GetLODsCount();
|
||||
|
||||
if (quality < ZeroTolerance)
|
||||
return 0;
|
||||
|
||||
int32 lods = Math::CeilToInt(quality * lodCount);
|
||||
|
||||
ASSERT(model.IsValidLODIndex(lods - 1));
|
||||
return lods;
|
||||
}
|
||||
|
||||
int32 SkinnedModelsStreamingHandler::CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency)
|
||||
{
|
||||
ASSERT(resource);
|
||||
auto& model = *(SkinnedModel*)resource;
|
||||
|
||||
// Always load only single LOD at once
|
||||
int32 residency = targetResidency;
|
||||
int32 currentResidency = model.GetCurrentResidency();
|
||||
if (currentResidency < targetResidency)
|
||||
{
|
||||
// Up
|
||||
residency = currentResidency + 1;
|
||||
}
|
||||
else if (currentResidency > targetResidency)
|
||||
{
|
||||
// Down
|
||||
residency = currentResidency - 1;
|
||||
}
|
||||
|
||||
return residency;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Streaming/IStreamingHandler.h"
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of IStreamingHandler for streamable skinned models.
|
||||
/// </summary>
|
||||
/// <seealso cref="IStreamingHandler" />
|
||||
class FLAXENGINE_API SkinnedModelsStreamingHandler : public IStreamingHandler
|
||||
{
|
||||
public:
|
||||
|
||||
// [IStreamingHandler]
|
||||
StreamingQuality CalculateTargetQuality(StreamableResource* resource, DateTime now) override;
|
||||
int32 CalculateResidency(StreamableResource* resource, StreamingQuality quality) override;
|
||||
int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override;
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "TexturesStreamingHandler.h"
|
||||
#include "Engine/Core/Math/Math.h"
|
||||
#include "Engine/Graphics/Textures/StreamingTexture.h"
|
||||
|
||||
StreamingQuality TexturesStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now)
|
||||
{
|
||||
ASSERT(resource);
|
||||
auto& texture = *(StreamingTexture*)resource;
|
||||
|
||||
// TODO: calculate a proper quality levels for the textures
|
||||
|
||||
//return 0.5f;
|
||||
|
||||
return MAX_STREAMING_QUALITY;
|
||||
}
|
||||
|
||||
int32 TexturesStreamingHandler::CalculateResidency(StreamableResource* resource, StreamingQuality quality)
|
||||
{
|
||||
ASSERT(resource);
|
||||
auto& texture = *(StreamingTexture*)resource;
|
||||
ASSERT(texture.IsInitialized());
|
||||
const auto totalMipLevels = texture.TotalMipLevels();
|
||||
|
||||
if (quality < ZeroTolerance)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32 mipLevels = Math::CeilToInt(quality * totalMipLevels);
|
||||
if (mipLevels > 0 && mipLevels < 3 && totalMipLevels > 1 && texture.IsBlockCompressed())
|
||||
{
|
||||
// Block compressed textures require minimum size of 4 (3 mips or more)
|
||||
mipLevels = 3;
|
||||
}
|
||||
|
||||
ASSERT(Math::IsInRange(mipLevels, 0, totalMipLevels));
|
||||
return mipLevels;
|
||||
}
|
||||
|
||||
int32 TexturesStreamingHandler::CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency)
|
||||
{
|
||||
ASSERT(resource);
|
||||
auto& texture = *(StreamingTexture*)resource;
|
||||
ASSERT(texture.IsInitialized());
|
||||
|
||||
int32 residency = targetResidency;
|
||||
int32 currentResidency = texture.GetCurrentResidency();
|
||||
|
||||
// Check if go up or down
|
||||
if (currentResidency < targetResidency)
|
||||
{
|
||||
// Up
|
||||
residency = Math::Min(currentResidency + 2, targetResidency);
|
||||
|
||||
// Stream first a few mips very fast (they are very small)
|
||||
const int32 QuickStartMipsCount = 6;
|
||||
if (currentResidency == 0)
|
||||
{
|
||||
residency = Math::Min(QuickStartMipsCount, targetResidency);
|
||||
}
|
||||
}
|
||||
else if (currentResidency > targetResidency)
|
||||
{
|
||||
// Down at once
|
||||
residency = targetResidency;
|
||||
}
|
||||
|
||||
return residency;
|
||||
}
|
||||
19
Source/Engine/Streaming/Handlers/TexturesStreamingHandler.h
Normal file
19
Source/Engine/Streaming/Handlers/TexturesStreamingHandler.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Streaming/IStreamingHandler.h"
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of IStreamingHandler for streamable textures.
|
||||
/// </summary>
|
||||
/// <seealso cref="IStreamingHandler" />
|
||||
class FLAXENGINE_API TexturesStreamingHandler : public IStreamingHandler
|
||||
{
|
||||
public:
|
||||
|
||||
// [IStreamingHandler]
|
||||
StreamingQuality CalculateTargetQuality(StreamableResource* resource, DateTime now) override;
|
||||
int32 CalculateResidency(StreamableResource* resource, StreamingQuality quality) override;
|
||||
int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override;
|
||||
};
|
||||
56
Source/Engine/Streaming/IStreamingHandler.h
Normal file
56
Source/Engine/Streaming/IStreamingHandler.h
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Config.h"
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
|
||||
class StreamingGroup;
|
||||
|
||||
/// <summary>
|
||||
/// Base interface for all streamable resource handlers
|
||||
/// </summary>
|
||||
class FLAXENGINE_API IStreamingHandler
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~IStreamingHandler() = default;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Calculates target quality level for the given resource.
|
||||
/// </summary>
|
||||
/// <param name="resource">The resource.</param>
|
||||
/// <param name="now">The current time.</param>
|
||||
/// <returns>Target Quality</returns>
|
||||
virtual StreamingQuality CalculateTargetQuality(StreamableResource* resource, DateTime now) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the residency level for a given resource and quality level.
|
||||
/// </summary>
|
||||
/// <param name="resource">The resource.</param>
|
||||
/// <param name="quality">The quality level.</param>
|
||||
/// <returns>Residency level</returns>
|
||||
virtual int32 CalculateResidency(StreamableResource* resource, StreamingQuality quality) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the residency level to stream for a given resource and target residency.
|
||||
/// </summary>
|
||||
/// <param name="resource">The resource.</param>
|
||||
/// <param name="targetResidency">The target residency level.</param>
|
||||
/// <returns>Residency level to stream</returns>
|
||||
virtual int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the specified resource requires the streaming.
|
||||
/// </summary>
|
||||
/// <param name="resource">The resource.</param>
|
||||
/// <param name="currentResidency">The current residency level.</param>
|
||||
/// <param name="targetResidency">The target residency level.</param>
|
||||
/// <returns>True if perform resource streaming, otherwise false.</returns>
|
||||
virtual bool RequiresStreaming(StreamableResource* resource, int32 currentResidency, int32 targetResidency)
|
||||
{
|
||||
return currentResidency != targetResidency;
|
||||
}
|
||||
};
|
||||
39
Source/Engine/Streaming/StreamableResource.cpp
Normal file
39
Source/Engine/Streaming/StreamableResource.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "StreamableResource.h"
|
||||
#include "StreamingManager.h"
|
||||
|
||||
StreamableResource::StreamableResource(StreamingGroup* group)
|
||||
: _group(group)
|
||||
, _isDynamic(true)
|
||||
, _isStreaming(false)
|
||||
, _streamingQuality(MAX_STREAMING_QUALITY)
|
||||
{
|
||||
ASSERT(_group != nullptr);
|
||||
}
|
||||
|
||||
StreamableResource::~StreamableResource()
|
||||
{
|
||||
stopStreaming();
|
||||
}
|
||||
|
||||
void StreamableResource::startStreaming(bool isDynamic)
|
||||
{
|
||||
_isDynamic = isDynamic;
|
||||
|
||||
if (_isStreaming == false)
|
||||
{
|
||||
StreamingManager::Resources.Add(this);
|
||||
_isStreaming = true;
|
||||
}
|
||||
}
|
||||
|
||||
void StreamableResource::stopStreaming()
|
||||
{
|
||||
if (_isStreaming == true)
|
||||
{
|
||||
StreamingManager::Resources.Remove(this);
|
||||
Streaming = StreamingCache();
|
||||
_isStreaming = false;
|
||||
}
|
||||
}
|
||||
155
Source/Engine/Streaming/StreamableResource.h
Normal file
155
Source/Engine/Streaming/StreamableResource.h
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Engine/Core/Collections/SamplesBuffer.h"
|
||||
#include "Config.h"
|
||||
|
||||
class StreamingGroup;
|
||||
class Task;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all resource types that can be dynamically streamed.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API StreamableResource
|
||||
{
|
||||
protected:
|
||||
|
||||
StreamingGroup* _group;
|
||||
bool _isDynamic, _isStreaming;
|
||||
StreamingQuality _streamingQuality;
|
||||
|
||||
StreamableResource(StreamingGroup* group);
|
||||
~StreamableResource();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets resource group
|
||||
/// </summary>
|
||||
/// <returns>Streaming Group</returns>
|
||||
FORCE_INLINE StreamingGroup* GetGroup() const
|
||||
{
|
||||
return _group;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets value indicating whenever resource can be used in dynamic streaming (otherwise use always the best quality)
|
||||
/// </summary>
|
||||
/// <returns>Is dynamic streamable</returns>
|
||||
FORCE_INLINE bool IsDynamic() const
|
||||
{
|
||||
#if ENABLE_RESOURCES_DYNAMIC_STREAMING
|
||||
return _isDynamic;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets resource streaming quality level
|
||||
/// </summary>
|
||||
/// <returns>Streaming Quality level</returns>
|
||||
FORCE_INLINE StreamingQuality GetStreamingQuality() const
|
||||
{
|
||||
return _streamingQuality;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets resource maximum residency level.
|
||||
/// </summary>
|
||||
/// <returns>Residency</returns>
|
||||
virtual int32 GetMaxResidency() const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets resource current residency level.
|
||||
/// </summary>
|
||||
/// <returns>Residency</returns>
|
||||
virtual int32 GetCurrentResidency() const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets resource allocated residency level.
|
||||
/// </summary>
|
||||
/// <returns>Residency</returns>
|
||||
virtual int32 GetAllocatedResidency() const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets resource target residency level.
|
||||
/// </summary>
|
||||
/// <returns>Residency</returns>
|
||||
int32 GetTargetResidency() const
|
||||
{
|
||||
return Streaming.TargetResidency;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this resource has been allocated.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool IsAllocated() const
|
||||
{
|
||||
return GetAllocatedResidency() != 0;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance can be updated. Which means: no async streaming, no pending action in background.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if this instance can be updated; otherwise, <c>false</c>.</returns>
|
||||
virtual bool CanBeUpdated() const = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Updates the resource allocation to the given residency level. May not be updated now but in an async operation.
|
||||
/// </summary>
|
||||
/// <param name="residency">The target allocation residency.</param>
|
||||
/// <returns>Async task that updates resource allocation or null if already done it. Warning: need to call task start to perform allocation.</returns>
|
||||
virtual Task* UpdateAllocation(int32 residency) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates streaming task (or tasks sequence) to perform resource streaming for the desire residency level.
|
||||
/// </summary>
|
||||
/// <param name="residency">The target residency.</param>
|
||||
/// <returns>Async task or tasks that update resource residency level. Must be preceded with UpdateAllocation call.</returns>
|
||||
virtual Task* CreateStreamingTask(int32 residency) = 0;
|
||||
|
||||
public:
|
||||
|
||||
// Streaming Manager cached variables
|
||||
struct StreamingCache
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum usage distance since last update (eg. mesh draw distance from camera).
|
||||
/// Used to calculate resource quality.
|
||||
/// </summary>
|
||||
//float MinDstSinceLastUpdate;
|
||||
|
||||
DateTime LastUpdate;
|
||||
int32 TargetResidency;
|
||||
DateTime TargetResidencyChange;
|
||||
SamplesBuffer<StreamingQuality, 5> QualitySamples;
|
||||
|
||||
StreamingCache()
|
||||
//: MinDstSinceLastUpdate(MAX_float)
|
||||
: LastUpdate(0)
|
||||
, TargetResidency(0)
|
||||
, TargetResidencyChange(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
StreamingCache Streaming;
|
||||
|
||||
/// <summary>
|
||||
/// Requests the streaming update for this resource during next streaming manager update.
|
||||
/// </summary>
|
||||
void RequestStreamingUpdate()
|
||||
{
|
||||
Streaming.LastUpdate.Ticks = 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void startStreaming(bool isDynamic);
|
||||
void stopStreaming();
|
||||
};
|
||||
103
Source/Engine/Streaming/StreamableResourcesCollection.h
Normal file
103
Source/Engine/Streaming/StreamableResourcesCollection.h
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Delegate.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Platform/CriticalSection.h"
|
||||
|
||||
class StreamableResource;
|
||||
|
||||
/// <summary>
|
||||
/// Container for collection of Streamable Resources.
|
||||
/// </summary>
|
||||
class StreamableResourcesCollection
|
||||
{
|
||||
private:
|
||||
|
||||
CriticalSection _locker;
|
||||
Array<StreamableResource*> _resources;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StreamableResourcesCollection"/> class.
|
||||
/// </summary>
|
||||
StreamableResourcesCollection()
|
||||
: _resources(4096)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Event called on new resource added to the collection.
|
||||
/// </summary>
|
||||
Delegate<StreamableResource*> Added;
|
||||
|
||||
/// <summary>
|
||||
/// Event called on new resource added from the collection.
|
||||
/// </summary>
|
||||
Delegate<StreamableResource*> Removed;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of registered resources.
|
||||
/// </summary>
|
||||
/// <returns>The resources count.</returns>
|
||||
int32 ResourcesCount() const
|
||||
{
|
||||
_locker.Lock();
|
||||
const int32 result = _resources.Count();
|
||||
_locker.Unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the resource at the given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>The resource at the given index.</returns>
|
||||
StreamableResource* operator[](int32 index) const
|
||||
{
|
||||
_locker.Lock();
|
||||
StreamableResource* result = _resources.At(index);
|
||||
_locker.Unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Adds the resource to the collection.
|
||||
/// </summary>
|
||||
/// <param name="resource">The resource to add.</param>
|
||||
void Add(StreamableResource* resource)
|
||||
{
|
||||
ASSERT(resource);
|
||||
|
||||
_locker.Lock();
|
||||
ASSERT(_resources.Contains(resource) == false);
|
||||
_resources.Add(resource);
|
||||
_locker.Unlock();
|
||||
|
||||
Added(resource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes resource from the collection.
|
||||
/// </summary>
|
||||
/// <param name="resource">The resource to remove.</param>
|
||||
void Remove(StreamableResource* resource)
|
||||
{
|
||||
ASSERT(resource);
|
||||
|
||||
_locker.Lock();
|
||||
ASSERT(_resources.Contains(resource) == true);
|
||||
_resources.Remove(resource);
|
||||
_locker.Unlock();
|
||||
|
||||
Removed(resource);
|
||||
}
|
||||
};
|
||||
10
Source/Engine/Streaming/Streaming.Build.cs
Normal file
10
Source/Engine/Streaming/Streaming.Build.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using Flax.Build;
|
||||
|
||||
/// <summary>
|
||||
/// Content streaming module.
|
||||
/// </summary>
|
||||
public class Streaming : EngineModule
|
||||
{
|
||||
}
|
||||
45
Source/Engine/Streaming/StreamingGroup.cpp
Normal file
45
Source/Engine/Streaming/StreamingGroup.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "StreamingGroup.h"
|
||||
#include "Handlers/TexturesStreamingHandler.h"
|
||||
#include "Handlers/ModelsStreamingHandler.h"
|
||||
#include "Handlers/SkinnedModelsStreamingHandler.h"
|
||||
#include "Handlers/AudioStreamingHandler.h"
|
||||
|
||||
StreamingGroup::StreamingGroup(Type type, IStreamingHandler* handler)
|
||||
: _type(type)
|
||||
, _handler(handler)
|
||||
{
|
||||
ASSERT(_handler != nullptr);
|
||||
}
|
||||
|
||||
StreamingGroups::StreamingGroups()
|
||||
: _textures(nullptr)
|
||||
, _models(nullptr)
|
||||
, _skinnedModels(nullptr)
|
||||
, _audio(nullptr)
|
||||
, _groups(8)
|
||||
{
|
||||
// Register in-build streaming groups
|
||||
Add(_textures = New<StreamingGroup>(StreamingGroup::Type::Textures, New<TexturesStreamingHandler>()));
|
||||
Add(_models = New<StreamingGroup>(StreamingGroup::Type::Models, New<ModelsStreamingHandler>()));
|
||||
Add(_skinnedModels = New<StreamingGroup>(StreamingGroup::Type::Models, New<SkinnedModelsStreamingHandler>()));
|
||||
Add(_audio = New<StreamingGroup>(StreamingGroup::Type::Audio, New<AudioStreamingHandler>()));
|
||||
}
|
||||
|
||||
StreamingGroups::~StreamingGroups()
|
||||
{
|
||||
// Cleanup
|
||||
_groups.ClearDelete();
|
||||
_handlers.ClearDelete();
|
||||
}
|
||||
|
||||
void StreamingGroups::Add(StreamingGroup* group)
|
||||
{
|
||||
ASSERT(group && !_groups.Contains(group));
|
||||
|
||||
// Register group and it's handler
|
||||
_groups.Add(group);
|
||||
if (!_handlers.Contains(group->GetHandler()))
|
||||
_handlers.Add(group->GetHandler());
|
||||
}
|
||||
151
Source/Engine/Streaming/StreamingGroup.h
Normal file
151
Source/Engine/Streaming/StreamingGroup.h
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IStreamingHandler.h"
|
||||
#include "Engine/Core/Enums.h"
|
||||
#include "Engine/Core/Singleton.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
/// <summary>
|
||||
/// Describes streamable resources group object.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API StreamingGroup
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Declares the Group type
|
||||
/// </summary>
|
||||
DECLARE_ENUM_4(Type, Custom, Textures, Models, Audio);
|
||||
|
||||
protected:
|
||||
|
||||
Type _type;
|
||||
IStreamingHandler* _handler;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StreamingGroup"/> class.
|
||||
/// </summary>
|
||||
/// <param name="type">The group type.</param>
|
||||
/// <param name="handler">Group dedicated handler.</param>
|
||||
StreamingGroup(Type type, IStreamingHandler* handler);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets the group type.
|
||||
/// </summary>
|
||||
/// <returns>Type</returns>
|
||||
FORCE_INLINE Type GetType() const
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the group type name.
|
||||
/// </summary>
|
||||
/// <returns>Typename</returns>
|
||||
FORCE_INLINE const Char* GetTypename() const
|
||||
{
|
||||
return ToString(_type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the group streaming handler used by this group.
|
||||
/// </summary>
|
||||
/// <returns>Handler</returns>
|
||||
FORCE_INLINE IStreamingHandler* GetHandler() const
|
||||
{
|
||||
return _handler;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Streaming groups manager.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API StreamingGroups : public Singleton<StreamingGroups>
|
||||
{
|
||||
private:
|
||||
|
||||
StreamingGroup* _textures;
|
||||
StreamingGroup* _models;
|
||||
StreamingGroup* _skinnedModels;
|
||||
StreamingGroup* _audio;
|
||||
|
||||
Array<StreamingGroup*> _groups;
|
||||
Array<IStreamingHandler*> _handlers;
|
||||
|
||||
public:
|
||||
|
||||
StreamingGroups();
|
||||
~StreamingGroups();
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets textures group.
|
||||
/// </summary>
|
||||
/// <returns>Group</returns>
|
||||
FORCE_INLINE StreamingGroup* Textures() const
|
||||
{
|
||||
return _textures;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets models group.
|
||||
/// </summary>
|
||||
/// <returns>Group</returns>
|
||||
FORCE_INLINE StreamingGroup* Models() const
|
||||
{
|
||||
return _models;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets skinned models group.
|
||||
/// </summary>
|
||||
/// <returns>Group</returns>
|
||||
FORCE_INLINE StreamingGroup* SkinnedModels() const
|
||||
{
|
||||
return _skinnedModels;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets audio group.
|
||||
/// </summary>
|
||||
/// <returns>Group</returns>
|
||||
FORCE_INLINE StreamingGroup* Audio() const
|
||||
{
|
||||
return _audio;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the groups.
|
||||
/// </summary>
|
||||
/// <returns>Groups.</returns>
|
||||
FORCE_INLINE const Array<StreamingGroup*>& Groups() const
|
||||
{
|
||||
return _groups;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the handlers.
|
||||
/// </summary>
|
||||
/// <returns>Groups.</returns>
|
||||
FORCE_INLINE const Array<IStreamingHandler*>& Handlers() const
|
||||
{
|
||||
return _handlers;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified streaming group.
|
||||
/// </summary>
|
||||
/// <param name="group">The group.</param>
|
||||
void Add(StreamingGroup* group);
|
||||
};
|
||||
168
Source/Engine/Streaming/StreamingManager.cpp
Normal file
168
Source/Engine/Streaming/StreamingManager.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "StreamingManager.h"
|
||||
#include "StreamableResource.h"
|
||||
#include "StreamingGroup.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Threading/Task.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
|
||||
namespace StreamingManagerImpl
|
||||
{
|
||||
DateTime LastUpdateTime(0);
|
||||
CriticalSection UpdateLocker;
|
||||
int32 LastUpdateResourcesIndex = 0;
|
||||
}
|
||||
|
||||
using namespace StreamingManagerImpl;
|
||||
|
||||
class StreamingManagerService : public EngineService
|
||||
{
|
||||
public:
|
||||
|
||||
StreamingManagerService()
|
||||
: EngineService(TEXT("Streaming Manager"), 100)
|
||||
{
|
||||
}
|
||||
|
||||
void Update() override;
|
||||
};
|
||||
|
||||
StreamingManagerService StreamingManagerServiceInstance;
|
||||
|
||||
StreamableResourcesCollection StreamingManager::Resources;
|
||||
|
||||
void UpdateResource(StreamableResource* resource, DateTime now)
|
||||
{
|
||||
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
|
||||
StreamingQuality targetQuality = MAX_STREAMING_QUALITY;
|
||||
if (resource->IsDynamic())
|
||||
{
|
||||
targetQuality = handler->CalculateTargetQuality(resource, now);
|
||||
// TODO: here we should apply resources group master scale (based on game settings quality level and memory level)
|
||||
targetQuality = Math::Clamp<StreamingQuality>(targetQuality, MIN_STREAMING_QUALITY, MAX_STREAMING_QUALITY);
|
||||
}
|
||||
|
||||
// Update quality smoothing
|
||||
resource->Streaming.QualitySamples.Add(targetQuality);
|
||||
targetQuality = resource->Streaming.QualitySamples.Maximum();
|
||||
targetQuality = Math::Clamp<StreamingQuality>(targetQuality, MIN_STREAMING_QUALITY, MAX_STREAMING_QUALITY);
|
||||
|
||||
// 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);
|
||||
|
||||
// Assign last update time
|
||||
auto updateTimeDelta = now - resource->Streaming.LastUpdate;
|
||||
resource->Streaming.LastUpdate = now;
|
||||
|
||||
// Check if a target residency level has been changed
|
||||
if (targetResidency != resource->Streaming.TargetResidency)
|
||||
{
|
||||
// Register change
|
||||
resource->Streaming.TargetResidency = targetResidency;
|
||||
resource->Streaming.TargetResidencyChange = now;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate residency level to stream in (resources may want to incease/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
|
||||
}
|
||||
|
||||
void StreamingManagerService::Update()
|
||||
{
|
||||
// Configuration
|
||||
// TODO: use game settings
|
||||
static TimeSpan ManagerUpdatesInterval = TimeSpan::FromMilliseconds(30);
|
||||
static TimeSpan ResourceUpdatesInterval = TimeSpan::FromMilliseconds(200);
|
||||
static int32 MaxResourcesPerUpdate = 50;
|
||||
|
||||
// Check if skip update
|
||||
auto now = DateTime::NowUTC();
|
||||
auto delta = now - LastUpdateTime;
|
||||
int32 resourcesCount = StreamingManager::Resources.ResourcesCount();
|
||||
if (resourcesCount == 0 || delta < ManagerUpdatesInterval || GPUDevice::Instance->GetState() != GPUDevice::DeviceState::Ready)
|
||||
return;
|
||||
LastUpdateTime = now;
|
||||
|
||||
PROFILE_CPU();
|
||||
|
||||
// Start update
|
||||
ScopeLock lock(UpdateLocker);
|
||||
int32 resourcesUpdates = Math::Min(MaxResourcesPerUpdate, resourcesCount);
|
||||
|
||||
// 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 = StreamingManager::Resources[LastUpdateResourcesIndex];
|
||||
|
||||
// Try to update it
|
||||
if (now - resource->Streaming.LastUpdate >= ResourceUpdatesInterval && resource->CanBeUpdated())
|
||||
{
|
||||
UpdateResource(resource, now);
|
||||
resourcesUpdates--;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add StreamingManager stats, update time per frame, updates per frame, etc.
|
||||
}
|
||||
18
Source/Engine/Streaming/StreamingManager.h
Normal file
18
Source/Engine/Streaming/StreamingManager.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "StreamableResourcesCollection.h"
|
||||
|
||||
/// <summary>
|
||||
/// Main class for dynamic resources streaming service
|
||||
/// </summary>
|
||||
class FLAXENGINE_API StreamingManager
|
||||
{
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// List with all resources
|
||||
/// </summary>
|
||||
static StreamableResourcesCollection Resources;
|
||||
};
|
||||
Reference in New Issue
Block a user