Refactor Streaming with new settings and textures streaming configuration

This commit is contained in:
Wojtek Figat
2021-06-17 15:33:34 +02:00
parent 64501a8645
commit 4744fa05ef
42 changed files with 631 additions and 480 deletions

View File

@@ -1,16 +0,0 @@
// Copyright (c) 2012-2021 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;

View File

@@ -1,67 +0,0 @@
// Copyright (c) 2012-2021 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();
}

View File

@@ -1,20 +0,0 @@
// Copyright (c) 2012-2021 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;
};

View File

@@ -1,50 +0,0 @@
// Copyright (c) 2012-2021 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 * (StreamingQuality)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;
}

View File

@@ -1,19 +0,0 @@
// Copyright (c) 2012-2021 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;
};

View File

@@ -1,50 +0,0 @@
// Copyright (c) 2012-2021 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;
}

View File

@@ -1,19 +0,0 @@
// Copyright (c) 2012-2021 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;
};

View File

@@ -1,71 +0,0 @@
// Copyright (c) 2012-2021 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;
}

View File

@@ -1,19 +0,0 @@
// Copyright (c) 2012-2021 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;
};

View File

@@ -2,13 +2,13 @@
#pragma once
#include "Config.h"
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Types/BaseTypes.h"
class StreamingGroup;
class StreamableResource;
/// <summary>
/// Base interface for all streamable resource handlers
/// Base interface for all streamable resource handlers that implement resource streaming policy.
/// </summary>
class FLAXENGINE_API IStreamingHandler
{
@@ -19,20 +19,20 @@ public:
public:
/// <summary>
/// Calculates target quality level for the given resource.
/// Calculates target quality level (0-1) 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;
/// <returns>Target quality (0-1).</returns>
virtual float 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>
/// <param name="quality">The quality level (0-1).</param>
/// <returns>Residency level</returns>
virtual int32 CalculateResidency(StreamableResource* resource, StreamingQuality quality) = 0;
virtual int32 CalculateResidency(StreamableResource* resource, float quality) = 0;
/// <summary>
/// Calculates the residency level to stream for a given resource and target residency.

View File

@@ -1,13 +1,13 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "StreamableResource.h"
#include "StreamingManager.h"
#include "Streaming.h"
StreamableResource::StreamableResource(StreamingGroup* group)
: _group(group)
, _isDynamic(true)
, _isStreaming(false)
, _streamingQuality(MAX_STREAMING_QUALITY)
, _streamingQuality(1.0f)
{
ASSERT(_group != nullptr);
}
@@ -23,7 +23,7 @@ void StreamableResource::startStreaming(bool isDynamic)
if (_isStreaming == false)
{
StreamingManager::Resources.Add(this);
Streaming::Resources.Add(this);
_isStreaming = true;
}
}
@@ -32,7 +32,7 @@ void StreamableResource::stopStreaming()
{
if (_isStreaming == true)
{
StreamingManager::Resources.Remove(this);
Streaming::Resources.Remove(this);
Streaming = StreamingCache();
_isStreaming = false;
}

View File

@@ -4,7 +4,6 @@
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Collections/SamplesBuffer.h"
#include "Config.h"
class StreamingGroup;
class Task;
@@ -18,7 +17,7 @@ protected:
StreamingGroup* _group;
bool _isDynamic, _isStreaming;
StreamingQuality _streamingQuality;
float _streamingQuality;
StreamableResource(StreamingGroup* group);
~StreamableResource();
@@ -38,17 +37,13 @@ public:
/// </summary>
FORCE_INLINE bool IsDynamic() const
{
#if ENABLE_RESOURCES_DYNAMIC_STREAMING
return _isDynamic;
#else
return false;
#endif
}
/// <summary>
/// Gets resource streaming quality level
/// </summary>
FORCE_INLINE StreamingQuality GetStreamingQuality() const
FORCE_INLINE float GetStreamingQuality() const
{
return _streamingQuality;
}
@@ -114,7 +109,7 @@ public:
DateTime LastUpdate = 0;
int32 TargetResidency = 0;
DateTime TargetResidencyChange = 0;
SamplesBuffer<StreamingQuality, 5> QualitySamples;
SamplesBuffer<float, 5> QualitySamples;
};
StreamingCache Streaming;

View File

@@ -1,13 +1,15 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "StreamingManager.h"
#include "Streaming.h"
#include "StreamableResource.h"
#include "StreamingGroup.h"
#include "StreamingSettings.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"
#include "Engine/Serialization/Serialization.h"
namespace StreamingManagerImpl
{
@@ -21,7 +23,6 @@ using namespace StreamingManagerImpl;
class StreamingManagerService : public EngineService
{
public:
StreamingManagerService()
: EngineService(TEXT("Streaming Manager"), 100)
{
@@ -32,7 +33,18 @@ public:
StreamingManagerService StreamingManagerServiceInstance;
StreamableResourcesCollection StreamingManager::Resources;
StreamableResourcesCollection Streaming::Resources;
Array<TextureGroup, InlinedAllocation<32>> Streaming::TextureGroups;
void StreamingSettings::Apply()
{
Streaming::TextureGroups = TextureGroups;
}
void StreamingSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
DESERIALIZE(TextureGroups);
}
void UpdateResource(StreamableResource* resource, DateTime now)
{
@@ -43,18 +55,18 @@ void UpdateResource(StreamableResource* resource, DateTime now)
auto handler = group->GetHandler();
// Calculate target quality for that asset
StreamingQuality targetQuality = MAX_STREAMING_QUALITY;
float targetQuality = 1.0f;
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);
targetQuality = Math::Saturate(targetQuality);
}
// Update quality smoothing
resource->Streaming.QualitySamples.Add(targetQuality);
targetQuality = resource->Streaming.QualitySamples.Maximum();
targetQuality = Math::Clamp<StreamingQuality>(targetQuality, MIN_STREAMING_QUALITY, MAX_STREAMING_QUALITY);
targetQuality = Math::Saturate(targetQuality);
// Calculate target residency level (discrete value)
auto maxResidency = resource->GetMaxResidency();
@@ -132,7 +144,7 @@ void StreamingManagerService::Update()
// Check if skip update
auto now = DateTime::NowUTC();
auto delta = now - LastUpdateTime;
int32 resourcesCount = StreamingManager::Resources.ResourcesCount();
int32 resourcesCount = Streaming::Resources.ResourcesCount();
if (resourcesCount == 0 || delta < ManagerUpdatesInterval || GPUDevice::Instance->GetState() != GPUDevice::DeviceState::Ready)
return;
LastUpdateTime = now;
@@ -154,7 +166,7 @@ void StreamingManagerService::Update()
LastUpdateResourcesIndex = 0;
// Peek resource
const auto resource = StreamingManager::Resources[LastUpdateResourcesIndex];
const auto resource = Streaming::Resources[LastUpdateResourcesIndex];
// Try to update it
if (now - resource->Streaming.LastUpdate >= ResourceUpdatesInterval && resource->CanBeUpdated())

View File

@@ -0,0 +1,25 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Scripting/ScriptingType.h"
#include "TextureGroup.h"
#include "StreamableResourcesCollection.h"
/// <summary>
/// The content streaming service.
/// </summary>
API_CLASS(Static) class FLAXENGINE_API Streaming
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Streaming);
/// <summary>
/// List with all resources
/// </summary>
static StreamableResourcesCollection Resources;
/// <summary>
/// Textures streaming configuration (per-group).
/// </summary>
API_FIELD() static Array<TextureGroup, InlinedAllocation<32>> TextureGroups;
};

View File

@@ -1,10 +1,7 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "StreamingGroup.h"
#include "Handlers/TexturesStreamingHandler.h"
#include "Handlers/ModelsStreamingHandler.h"
#include "Handlers/SkinnedModelsStreamingHandler.h"
#include "Handlers/AudioStreamingHandler.h"
#include "StreamingHandlers.h"
StreamingGroup::StreamingGroup(Type type, IStreamingHandler* handler)
: _type(type)

View File

@@ -0,0 +1,230 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "StreamingHandlers.h"
#include "Streaming.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Graphics/Textures/StreamingTexture.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)
{
ASSERT(resource);
auto& texture = *(StreamingTexture*)resource;
const TextureHeader& header = *texture.GetHeader();
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];
return group.QualityScale;
}
int32 TexturesStreamingHandler::CalculateResidency(StreamableResource* resource, float quality)
{
if (quality < ZeroTolerance)
return 0;
ASSERT(resource);
auto& texture = *(StreamingTexture*)resource;
ASSERT(texture.IsInitialized());
const TextureHeader& header = *texture.GetHeader();
const int32 totalMipLevels = texture.TotalMipLevels();
int32 mipLevels = Math::CeilToInt(quality * (float)totalMipLevels);
if (header.TextureGroup >= 0 && header.TextureGroup < Streaming::TextureGroups.Count())
{
const TextureGroup& group = Streaming::TextureGroups[header.TextureGroup];
mipLevels = Math::Clamp(mipLevels + group.MipLevelsBias, group.MipLevelsMin, group.MipLevelsMax);
#if USE_EDITOR
// Simulate per-platform limit in Editor
int32 max;
if (group.MipLevelsMaxPerPlatform.TryGet(PLATFORM_TYPE, max))
mipLevels = Math::Min(mipLevels, max);
#endif
}
if (mipLevels > 0 && mipLevels < 3 && totalMipLevels > 1 && texture.IsBlockCompressed())
{
// Block compressed textures require minimum size of 4 (3 mips or more)
mipLevels = 3;
}
return Math::Clamp(mipLevels, 0, totalMipLevels);
}
int32 TexturesStreamingHandler::CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency)
{
ASSERT(resource);
auto& texture = *(StreamingTexture*)resource;
ASSERT(texture.IsInitialized());
int32 residency = targetResidency;
int32 currentResidency = texture.GetCurrentResidency();
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;
}
float ModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now)
{
// TODO: calculate a proper quality levels for models based on render time and streaming enable/disable options
return 1.0f;
}
int32 ModelsStreamingHandler::CalculateResidency(StreamableResource* resource, float quality)
{
if (quality < ZeroTolerance)
return 0;
ASSERT(resource);
auto& model = *(Model*)resource;
const int32 lodCount = model.GetLODsCount();
int32 lods = Math::CeilToInt(quality * (float)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;
}
float SkinnedModelsStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now)
{
// TODO: calculate a proper quality levels for models based on render time and streaming enable/disable options
return 1.0f;
}
int32 SkinnedModelsStreamingHandler::CalculateResidency(StreamableResource* resource, float quality)
{
if (quality < ZeroTolerance)
return 0;
ASSERT(resource);
auto& model = *(SkinnedModel*)resource;
const int32 lodCount = model.GetLODsCount();
int32 lods = Math::CeilToInt(quality * (float)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;
}
float AudioStreamingHandler::CalculateTargetQuality(StreamableResource* resource, DateTime now)
{
// Audio clips don't use quality but only residency
return 1.0f;
}
int32 AudioStreamingHandler::CalculateResidency(StreamableResource* resource, float 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();
}

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Streaming/IStreamingHandler.h"
/// <summary>
/// Implementation of IStreamingHandler for streamable textures.
/// </summary>
class FLAXENGINE_API TexturesStreamingHandler : public IStreamingHandler
{
public:
// [IStreamingHandler]
float CalculateTargetQuality(StreamableResource* resource, DateTime now) override;
int32 CalculateResidency(StreamableResource* resource, float quality) override;
int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override;
};
/// <summary>
/// Implementation of IStreamingHandler for streamable models.
/// </summary>
class FLAXENGINE_API ModelsStreamingHandler : public IStreamingHandler
{
public:
// [IStreamingHandler]
float CalculateTargetQuality(StreamableResource* resource, DateTime now) override;
int32 CalculateResidency(StreamableResource* resource, float quality) override;
int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override;
};
/// <summary>
/// Implementation of IStreamingHandler for streamable skinned models.
/// </summary>
class FLAXENGINE_API SkinnedModelsStreamingHandler : public IStreamingHandler
{
public:
// [IStreamingHandler]
float CalculateTargetQuality(StreamableResource* resource, DateTime now) override;
int32 CalculateResidency(StreamableResource* resource, float quality) override;
int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override;
};
/// <summary>
/// Implementation of IStreamingHandler for audio clips.
/// </summary>
class FLAXENGINE_API AudioStreamingHandler : public IStreamingHandler
{
public:
// [IStreamingHandler]
float CalculateTargetQuality(StreamableResource* resource, DateTime now) override;
int32 CalculateResidency(StreamableResource* resource, float quality) override;
int32 CalculateRequestedResidency(StreamableResource* resource, int32 targetResidency) override;
bool RequiresStreaming(StreamableResource* resource, int32 currentResidency, int32 targetResidency) override;
};

View File

@@ -1,18 +0,0 @@
// Copyright (c) 2012-2021 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;
};

View File

@@ -0,0 +1,32 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Config/Settings.h"
#include "TextureGroup.h"
/// <summary>
/// Content streaming settings.
/// </summary>
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class FLAXENGINE_API StreamingSettings : public SettingsBase
{
DECLARE_SCRIPTING_TYPE_MINIMAL(StreamingSettings);
public:
/// <summary>
/// Textures streaming configuration (per-group).
/// </summary>
API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Textures\")")
Array<TextureGroup, InlinedAllocation<32>> TextureGroups;
public:
/// <summary>
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
/// </summary>
static StreamingSettings* Get();
// [SettingsBase]
void Apply() override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override;
};

View File

@@ -0,0 +1,56 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Types/String.h"
#include "Engine/Core/ISerializable.h"
#if USE_EDITOR
#include "Engine/Core/Collections/Dictionary.h"
#endif
/// <summary>
/// Settings container for a group of textures. Defines the data streaming options and resource quality.
/// </summary>
API_STRUCT() struct TextureGroup : ISerializable
{
API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE_MINIMAL(TextureGroup);
/// <summary>
/// The name of the group.
/// </summary>
API_FIELD(Attributes="EditorOrder(10)")
String Name;
/// <summary>
/// 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;
/// <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.
/// </summary>
API_FIELD(Attributes="EditorOrder(30), Limit(0, 14)")
int32 MipLevelsMin = 0;
/// <summary>
/// The maximum amount of loaded mip levels for textures in this group. Defines the maximum amount of the mips that can be loaded. Overriden per-platform. Lower values reduce textures quality and improve performance.
/// </summary>
API_FIELD(Attributes="EditorOrder(40), Limit(1, 14)")
int32 MipLevelsMax = 14;
/// <summary>
/// The loaded mip levels bias for textures in this group. Can be used to increase or decrease quality of the streaming for textures in this group (eg. bump up the quality during cinematic sequence).
/// </summary>
API_FIELD(Attributes="EditorOrder(50), Limit(-14, 14)")
int32 MipLevelsBias = 0;
#if USE_EDITOR
/// <summary>
/// The per-platform maximum amount of mip levels for textures in this group. Can be used to strip textures quality when cooking game for a target platform.
/// </summary>
API_FIELD(Attributes="EditorOrder(50)")
Dictionary<PlatformType, int32> MipLevelsMaxPerPlatform;
#endif
};