Merge branch 'master' into PressGToGameModeAndPToNavigate

This commit is contained in:
Saas
2025-12-11 21:04:00 +01:00
committed by GitHub
780 changed files with 62335 additions and 6538 deletions
+17 -1
View File
@@ -7,6 +7,7 @@
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Threading/TaskGraph.h"
class BehaviorSystem : public TaskGraphSystem
@@ -38,6 +39,7 @@ TaskGraphSystem* Behavior::System = nullptr;
void BehaviorSystem::Job(int32 index)
{
PROFILE_CPU_NAMED("Behavior.Job");
PROFILE_MEM(AI);
Behaviors[index]->UpdateAsync();
}
@@ -57,6 +59,7 @@ void BehaviorSystem::Execute(TaskGraph* graph)
bool BehaviorService::Init()
{
PROFILE_MEM(AI);
Behavior::System = New<BehaviorSystem>();
Engine::UpdateGraph->AddSystem(Behavior::System);
return false;
@@ -70,9 +73,9 @@ void BehaviorService::Dispose()
Behavior::Behavior(const SpawnParams& params)
: Script(params)
, Tree(this)
{
_knowledge.Behavior = this;
Tree.Changed.Bind<Behavior, &Behavior::ResetLogic>(this);
}
void Behavior::UpdateAsync()
@@ -172,6 +175,19 @@ void Behavior::OnDisable()
BehaviorServiceInstance.UpdateList.Remove(this);
}
void Behavior::OnAssetChanged(Asset* asset, void* caller)
{
ResetLogic();
}
void Behavior::OnAssetLoaded(Asset* asset, void* caller)
{
}
void Behavior::OnAssetUnloaded(Asset* asset, void* caller)
{
}
#if USE_EDITOR
bool Behavior::GetNodeDebugRelevancy(const BehaviorTreeNode* node, const Behavior* behavior)
+6 -1
View File
@@ -11,7 +11,7 @@
/// <summary>
/// Behavior instance script that runs Behavior Tree execution.
/// </summary>
API_CLASS(Attributes="Category(\"Flax Engine\")") class FLAXENGINE_API Behavior : public Script
API_CLASS(Attributes="Category(\"Flax Engine\")") class FLAXENGINE_API Behavior : public Script, private IAssetReference
{
API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE(Behavior);
@@ -92,6 +92,11 @@ public:
void OnDisable() override;
private:
// [IAssetReference]
void OnAssetChanged(Asset* asset, void* caller) override;
void OnAssetLoaded(Asset* asset, void* caller) override;
void OnAssetUnloaded(Asset* asset, void* caller) override;
#if USE_EDITOR
// Editor-only utilities to debug nodes state.
API_FUNCTION(Internal) static bool GetNodeDebugRelevancy(const BehaviorTreeNode* node, const Behavior* behavior);
+2
View File
@@ -4,6 +4,7 @@
#include "BehaviorTree.h"
#include "BehaviorTreeNodes.h"
#include "BehaviorKnowledgeSelector.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/ManagedCLR/MProperty.h"
@@ -144,6 +145,7 @@ BehaviorKnowledge::~BehaviorKnowledge()
void BehaviorKnowledge::InitMemory(BehaviorTree* tree)
{
PROFILE_MEM(AI);
if (Tree)
FreeMemory();
if (!tree)
+2
View File
@@ -10,6 +10,7 @@
#include "Engine/Serialization/JsonSerializer.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "FlaxEngine.Gen.h"
#if USE_EDITOR
#include "Engine/Level/Level.h"
@@ -275,6 +276,7 @@ Asset::LoadResult BehaviorTree::load()
if (surfaceChunk == nullptr)
return LoadResult::MissingDataChunk;
MemoryReadStream surfaceStream(surfaceChunk->Get(), surfaceChunk->Size());
PROFILE_MEM(AI);
if (Graph.Load(&surfaceStream, true))
{
LOG(Warning, "Failed to load graph \'{0}\'", ToString());
+9 -3
View File
@@ -4,6 +4,7 @@
#include "AnimEvent.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Level/Actors/AnimatedModel.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h"
@@ -52,7 +53,7 @@ namespace
AnimationsService AnimationManagerInstance;
TaskGraphSystem* Animations::System = nullptr;
ConcurrentSystemLocker Animations::SystemLocker;
ReadWriteLock Animations::SystemLocker;
#if USE_EDITOR
Delegate<Animations::DebugFlowInfo> Animations::DebugFlow;
#endif
@@ -69,6 +70,7 @@ AnimContinuousEvent::AnimContinuousEvent(const SpawnParams& params)
bool AnimationsService::Init()
{
PROFILE_MEM(Animations);
Animations::System = New<AnimationsSystem>();
Engine::UpdateGraph->AddSystem(Animations::System);
return false;
@@ -83,6 +85,7 @@ void AnimationsService::Dispose()
void AnimationsSystem::Job(int32 index)
{
PROFILE_CPU_NAMED("Animations.Job");
PROFILE_MEM(Animations);
auto animatedModel = AnimationManagerInstance.UpdateList[index];
if (CanUpdateModel(animatedModel))
{
@@ -121,7 +124,7 @@ void AnimationsSystem::Execute(TaskGraph* graph)
Active = true;
// Ensure no animation assets can be reloaded/modified during async update
Animations::SystemLocker.Begin(false);
Animations::SystemLocker.ReadLock();
// Setup data for async update
const auto& tickData = Time::Update;
@@ -147,6 +150,7 @@ void AnimationsSystem::PostExecute(TaskGraph* graph)
if (!Active)
return;
PROFILE_CPU_NAMED("Animations.PostExecute");
PROFILE_MEM(Animations);
// Update gameplay
for (int32 index = 0; index < AnimationManagerInstance.UpdateList.Count(); index++)
@@ -161,16 +165,18 @@ void AnimationsSystem::PostExecute(TaskGraph* graph)
// Cleanup
AnimationManagerInstance.UpdateList.Clear();
Animations::SystemLocker.End(false);
Animations::SystemLocker.ReadUnlock();
Active = false;
}
void Animations::AddToUpdate(AnimatedModel* obj)
{
ScopeWriteLock lock(SystemLocker);
AnimationManagerInstance.UpdateList.Add(obj);
}
void Animations::RemoveFromUpdate(AnimatedModel* obj)
{
ScopeWriteLock lock(SystemLocker);
AnimationManagerInstance.UpdateList.Remove(obj);
}
+1 -2
View File
@@ -4,7 +4,6 @@
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Core/Delegate.h"
#include "Engine/Threading/ConcurrentSystemLocker.h"
class TaskGraphSystem;
class AnimatedModel;
@@ -23,7 +22,7 @@ API_CLASS(Static) class FLAXENGINE_API Animations
API_FIELD(ReadOnly) static TaskGraphSystem* System;
// Data access locker for animations data.
static ConcurrentSystemLocker SystemLocker;
static ReadWriteLock SystemLocker;
#if USE_EDITOR
// Data wrapper for the debug flow information.
@@ -93,6 +93,7 @@ void MultiBlendBucketInit(AnimGraphInstanceData::Bucket& bucket)
void BlendPoseBucketInit(AnimGraphInstanceData::Bucket& bucket)
{
bucket.BlendPose.TransitionPosition = 0.0f;
bucket.BlendPose.BlendPoseIndex = -1;
bucket.BlendPose.PreviousBlendPoseIndex = -1;
}
@@ -6,6 +6,7 @@
#include "Engine/Content/Assets/SkinnedModel.h"
#include "Engine/Graphics/Models/SkeletonData.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Threading/Threading.h"
extern void RetargetSkeletonPose(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, const Transform* sourceNodes, Transform* targetNodes);
+3 -1
View File
@@ -239,7 +239,8 @@ public:
struct BlendPoseBucket
{
float TransitionPosition;
int32 PreviousBlendPoseIndex;
int16 BlendPoseIndex;
int16 PreviousBlendPoseIndex;
};
struct StateMachineBucket
@@ -810,6 +811,7 @@ public:
{
// Copy the node transformations
Platform::MemoryCopy(dstNodes->Nodes.Get(), srcNodes->Nodes.Get(), sizeof(Transform) * _skeletonNodesCount);
dstNodes->RootMotion = srcNodes->RootMotion;
// Copy the animation playback state
dstNodes->Position = srcNodes->Position;
@@ -246,11 +246,19 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f;
#define ADD_OUTGOING_EVENT(type) context.Data->OutgoingEvents.Add({ k.Value.Instance, (AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime, AnimGraphInstanceData::OutgoingEvent::type })
if ((k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration
&& (Math::FloorToInt(animPos) != 0 && Math::CeilToInt(animPrevPos) != Math::CeilToInt(anim->GetDuration()) && Math::FloorToInt(animPrevPos) != 0 && Math::CeilToInt(animPos) != Math::CeilToInt(anim->GetDuration())))
&& (Math::FloorToInt(animPos) != 0 && Math::CeilToInt(animPrevPos) != Math::CeilToInt(anim->GetDuration())
&& Math::FloorToInt(animPrevPos) != 0 && Math::CeilToInt(animPos) != Math::CeilToInt(anim->GetDuration())))
// Handle the edge case of an event on 0 or on max animation duration during looping
|| (loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == anim->GetDuration())
|| (!loop && duration == 0.0f && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::NearEqual(k.Time, anim->GetDuration()))
|| (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|| (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && k.Time == 0.0f)
|| (loop && Math::FloorToInt(animPos) == 0 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && Math::NearEqual(k.Time, anim->GetDuration()))
|| (loop && Math::FloorToInt(animPrevPos) == 0 && Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::NearEqual(k.Time, anim->GetDuration()))
|| (Math::FloorToInt(animPos) == 1 && Math::FloorToInt(animPrevPos) == 0 && k.Time == 1.0f)
|| (Math::FloorToInt(animPos) == 0 && Math::FloorToInt(animPrevPos) == 1 && k.Time == 1.0f)
|| (Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::NearEqual(k.Time, anim->GetDuration() - 1.0f))
|| (Math::CeilToInt(animPos) == Math::CeilToInt(anim->GetDuration()) - 1 && Math::CeilToInt(animPrevPos) == Math::CeilToInt(anim->GetDuration()) && Math::NearEqual(k.Time, anim->GetDuration() - 1.0f))
|| (Math::FloorToInt(animPos) == 0 && Math::FloorToInt(animPrevPos) == 0 && k.Time == 0.0f)
)
{
int32 stateIndex = -1;
@@ -676,9 +684,12 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
if (!ANIM_GRAPH_IS_VALID_PTR(poseB))
nodesB = GetEmptyNodes();
const Transform* srcA = nodesA->Nodes.Get();
const Transform* srcB = nodesB->Nodes.Get();
Transform* dst = nodes->Nodes.Get();
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]);
Transform::Lerp(srcA[i], srcB[i], alpha, dst[i]);
}
Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
nodes->Position = Math::Lerp(nodesA->Position, nodesB->Position, alpha);
@@ -1263,21 +1274,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
{
const auto valueA = tryGetValue(node->GetBox(1), Value::Null);
const auto valueB = tryGetValue(node->GetBox(2), Value::Null);
const auto nodes = node->GetNodes(this);
auto nodesA = static_cast<AnimGraphImpulse*>(valueA.AsPointer);
auto nodesB = static_cast<AnimGraphImpulse*>(valueB.AsPointer);
if (!ANIM_GRAPH_IS_VALID_PTR(valueA))
nodesA = GetEmptyNodes();
if (!ANIM_GRAPH_IS_VALID_PTR(valueB))
nodesB = GetEmptyNodes();
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
{
Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]);
}
Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
value = nodes;
value = Blend(node, valueA, valueB, alpha, AlphaBlendMode::Linear);
}
break;
@@ -1758,35 +1755,38 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// [2]: int Pose Count
// [3]: AlphaBlendMode Mode
// Prepare
auto& bucket = context.Data->State[node->BucketIndex].BlendPose;
const int32 poseIndex = (int32)tryGetValue(node->GetBox(1), node->Values[0]);
const int16 poseIndex = (int32)tryGetValue(node->GetBox(1), node->Values[0]);
const float blendDuration = (float)tryGetValue(node->GetBox(2), node->Values[1]);
const int32 poseCount = Math::Clamp(node->Values[2].AsInt, 0, MaxBlendPoses);
const AlphaBlendMode mode = (AlphaBlendMode)node->Values[3].AsInt;
// Skip if nothing to blend
if (poseCount == 0 || poseIndex < 0 || poseIndex >= poseCount)
{
break;
// Check if swap transition end points
if (bucket.PreviousBlendPoseIndex == poseIndex && bucket.BlendPoseIndex != poseIndex && bucket.TransitionPosition >= ANIM_GRAPH_BLEND_THRESHOLD)
{
bucket.TransitionPosition = blendDuration - bucket.TransitionPosition;
Swap(bucket.BlendPoseIndex, bucket.PreviousBlendPoseIndex);
}
// Check if transition is not active (first update, pose not changing or transition ended)
bucket.TransitionPosition += context.DeltaTime;
bucket.BlendPoseIndex = poseIndex;
if (bucket.PreviousBlendPoseIndex == -1 || bucket.PreviousBlendPoseIndex == poseIndex || bucket.TransitionPosition >= blendDuration || blendDuration <= ANIM_GRAPH_BLEND_THRESHOLD)
{
bucket.TransitionPosition = 0.0f;
bucket.BlendPoseIndex = poseIndex;
bucket.PreviousBlendPoseIndex = poseIndex;
value = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null);
value = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.BlendPoseIndex), Value::Null);
break;
}
ASSERT(bucket.PreviousBlendPoseIndex >= 0 && bucket.PreviousBlendPoseIndex < poseCount);
// Blend two animations
{
const float alpha = bucket.TransitionPosition / blendDuration;
const auto valueA = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.PreviousBlendPoseIndex), Value::Null);
const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null);
const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.BlendPoseIndex), Value::Null);
value = Blend(node, valueA, valueB, alpha, mode);
}
@@ -7,6 +7,7 @@
#include "Engine/Content/Content.h"
#include "Engine/Content/Deprecated.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Audio/AudioClip.h"
#include "Engine/Graphics/PostProcessSettings.h"
#if USE_EDITOR
@@ -249,6 +250,7 @@ bool SceneAnimation::Save(const StringView& path)
Asset::LoadResult SceneAnimation::load()
{
TrackStatesCount = 0;
PROFILE_MEM(AnimationsData);
// Get the data chunk
if (LoadChunk(0))
@@ -12,6 +12,7 @@
#include "Engine/Audio/AudioSource.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/Script.h"
#include "Engine/Scripting/ManagedCLR/MException.h"
@@ -151,6 +152,7 @@ void SceneAnimationPlayer::Tick(float dt)
SceneAnimation* anim = Animation.Get();
if (!anim || !anim->IsLoaded())
return;
PROFILE_MEM(Animations);
// Setup state
if (_tracks.Count() != anim->TrackStatesCount)
@@ -229,6 +231,7 @@ void SceneAnimationPlayer::MapTrack(const StringView& from, const Guid& to)
SceneAnimation* anim = Animation.Get();
if (!anim || !anim->IsLoaded())
return;
PROFILE_MEM(Animations);
for (int32 j = 0; j < anim->Tracks.Count(); j++)
{
const auto& track = anim->Tracks[j];
+3
View File
@@ -8,6 +8,7 @@
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Level/Level.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/CommandLine.h"
#include "Engine/Core/Log.h"
@@ -151,6 +152,7 @@ void Audio::SetEnableHRTF(bool value)
bool AudioService::Init()
{
PROFILE_CPU_NAMED("Audio.Init");
PROFILE_MEM(Audio);
const auto settings = AudioSettings::Get();
const bool mute = CommandLine::Options.Mute.IsTrue() || settings->DisableAudio;
@@ -211,6 +213,7 @@ bool AudioService::Init()
void AudioService::Update()
{
PROFILE_CPU_NAMED("Audio.Update");
PROFILE_MEM(Audio);
// Update the master volume
float masterVolume = MasterVolume;
+44 -21
View File
@@ -7,50 +7,67 @@
#include "Engine/Core/Log.h"
#include "Engine/Content/Upgraders/AudioClipUpgrader.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ManagedCLR/MUtils.h"
#include "Engine/Streaming/StreamingGroup.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Tools/AudioTool/OggVorbisDecoder.h"
#include "Engine/Tools/AudioTool/AudioTool.h"
#include "Engine/Threading/Threading.h"
REGISTER_BINARY_ASSET_WITH_UPGRADER(AudioClip, "FlaxEngine.AudioClip", AudioClipUpgrader, false);
AudioClip::StreamingTask::StreamingTask(AudioClip* asset)
: _asset(asset)
, _dataLock(asset->Storage->Lock())
{
}
bool AudioClip::StreamingTask::HasReference(Object* resource) const
{
return _asset == resource;
}
bool AudioClip::StreamingTask::Run()
{
AssetReference<AudioClip> ref = _asset.Get();
if (ref == nullptr || AudioBackend::Instance == nullptr)
PROFILE_CPU_NAMED("AudioStreaming");
PROFILE_MEM(Audio);
AssetReference<AudioClip> clip = _asset.Get();
if (clip == nullptr || AudioBackend::Instance == nullptr)
return true;
ScopeLock lock(ref->Locker);
const auto& queue = ref->StreamingQueue;
if (queue.Count() == 0)
return false;
auto clip = ref.Get();
#if TRACY_ENABLE
const StringView name(clip->GetPath());
ZoneName(*name, name.Length());
#endif
// Update the buffers
// Process the loading queue (hold the asset lock)
clip->Locker.Lock();
const auto& queue = clip->StreamingQueue;
Array<int32, FixedAllocation<ASSET_FILE_DATA_CHUNKS>> loadQueue;
for (int32 i = 0; i < queue.Count(); i++)
{
const auto idx = queue[i];
const int32 idx = queue[i];
uint32& bufferID = clip->Buffers[idx];
if (bufferID == 0)
{
bufferID = AudioBackend::Buffer::Create();
// Load buffers outside the asset lock to prevent lock contention
loadQueue.Add(idx);
}
else
{
// Release unused data
// Release unused buffer
AudioBackend::Buffer::Delete(bufferID);
bufferID = 0;
}
}
clip->Locker.Unlock();
// Load missing buffers data (from asset chunks)
for (int32 i = 0; i < queue.Count(); i++)
for (int32 i = 0; i < loadQueue.Count(); i++)
{
if (clip->WriteBuffer(queue[i]))
{
if (clip->WriteBuffer(loadQueue[i]))
return true;
}
}
// Update the sources
@@ -318,6 +335,7 @@ bool AudioClip::init(AssetInitData& initData)
Asset::LoadResult AudioClip::load()
{
PROFILE_MEM(Audio);
#if !COMPILE_WITH_OGG_VORBIS
if (AudioHeader.Format == AudioFormat::Vorbis)
{
@@ -410,14 +428,11 @@ void AudioClip::unload(bool isReloading)
bool AudioClip::WriteBuffer(int32 chunkIndex)
{
// Ignore if buffer is not created
const uint32 bufferID = Buffers[chunkIndex];
if (bufferID == 0)
return false;
// Ensure audio backend exists
if (AudioBackend::Instance == nullptr)
return true;
PROFILE_CPU();
PROFILE_MEM(Audio);
const auto chunk = GetChunk(chunkIndex);
if (chunk == nullptr || chunk->IsMissing())
@@ -429,6 +444,7 @@ bool AudioClip::WriteBuffer(int32 chunkIndex)
Array<byte> tmp1, tmp2;
AudioDataInfo info = AudioHeader.Info;
const uint32 bytesPerSample = info.BitDepth / 8;
ZoneValue(chunk->Size() / 1024); // Audio data size (in kB)
// Get raw data or decompress it
switch (Format())
@@ -436,6 +452,7 @@ bool AudioClip::WriteBuffer(int32 chunkIndex)
case AudioFormat::Vorbis:
{
#if COMPILE_WITH_OGG_VORBIS
PROFILE_CPU_NAMED("OggVorbisDecode");
OggVorbisDecoder decoder;
MemoryReadStream stream(chunk->Get(), chunk->Size());
AudioDataInfo tmpInfo;
@@ -472,7 +489,13 @@ bool AudioClip::WriteBuffer(int32 chunkIndex)
data = Span<byte>(tmp2.Get(), tmp2.Count());
}
// Write samples to the audio buffer
// Write samples to the audio buffer (create one if missing)
Locker.Lock(); // StreamingTask loads buffers without lock so do it here
uint32& bufferID = Buffers[chunkIndex];
if (bufferID == 0)
bufferID = AudioBackend::Buffer::Create();
AudioBackend::Buffer::Write(bufferID, data.Get(), info);
Locker.Unlock();
return false;
}
+2 -13
View File
@@ -44,22 +44,11 @@ public:
FlaxStorage::LockData _dataLock;
public:
/// <summary>
/// Init
/// </summary>
/// <param name="asset">Parent asset</param>
StreamingTask(AudioClip* asset)
: _asset(asset)
, _dataLock(asset->Storage->Lock())
{
}
StreamingTask(AudioClip* asset);
public:
// [ThreadPoolTask]
bool HasReference(Object* resource) const override
{
return _asset == resource;
}
bool HasReference(Object* resource) const override;
protected:
// [ThreadPoolTask]
+2 -2
View File
@@ -23,11 +23,11 @@ void AudioListener::Update()
{
// Update the velocity
const Vector3 pos = GetPosition();
const float dt = Time::Update.UnscaledDeltaTime.GetTotalSeconds();
const float dt = Math::Max(Time::Update.UnscaledDeltaTime.GetTotalSeconds(), 0.00001f);
const auto prevVelocity = _velocity;
_velocity = (pos - _prevPos) / dt;
_prevPos = pos;
if (_velocity != prevVelocity)
if (_velocity != prevVelocity && !_velocity.IsNanOrInfinity())
{
AudioBackend::Listener::VelocityChanged(_velocity);
}
+18 -6
View File
@@ -8,6 +8,7 @@
#include "Engine/Engine/Time.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "AudioBackend.h"
#include "Audio.h"
@@ -21,9 +22,8 @@ AudioSource::AudioSource(const SpawnParams& params)
, _playOnStart(false)
, _startTime(0.0f)
, _allowSpatialization(true)
, Clip(this)
{
Clip.Changed.Bind<AudioSource, &AudioSource::OnClipChanged>(this);
Clip.Loaded.Bind<AudioSource, &AudioSource::OnClipLoaded>(this);
}
void AudioSource::SetVolume(float value)
@@ -121,6 +121,7 @@ void AudioSource::Play()
auto state = _state;
if (state == States::Playing)
return;
PROFILE_CPU();
if (Clip == nullptr || Clip->WaitForLoaded())
{
LOG(Warning, "Cannot play audio source without a clip ({0})", GetNamePath());
@@ -167,8 +168,8 @@ void AudioSource::Play()
}
else
{
// Source was nt properly added to the Audio Backend
LOG(Warning, "Cannot play unitialized audio source.");
// Source was not properly added to the Audio Backend
LOG(Warning, "Cannot play uninitialized audio source.");
}
}
@@ -189,6 +190,7 @@ void AudioSource::Stop()
{
if (_state == States::Stopped)
return;
PROFILE_CPU();
_state = States::Stopped;
_isActuallyPlayingSth = false;
@@ -264,7 +266,7 @@ void AudioSource::RequestStreamingBuffersUpdate()
_needToUpdateStreamingBuffers = true;
}
void AudioSource::OnClipChanged()
void AudioSource::OnAssetChanged(Asset* asset, void* caller)
{
Stop();
@@ -276,7 +278,7 @@ void AudioSource::OnClipChanged()
}
}
void AudioSource::OnClipLoaded()
void AudioSource::OnAssetLoaded(Asset* asset, void* caller)
{
if (!SourceID)
return;
@@ -302,6 +304,10 @@ void AudioSource::OnClipLoaded()
}
}
void AudioSource::OnAssetUnloaded(Asset* asset, void* caller)
{
}
bool AudioSource::UseStreaming() const
{
if (Clip == nullptr || Clip->WaitForLoaded())
@@ -383,6 +389,7 @@ bool AudioSource::IntersectsItself(const Ray& ray, Real& distance, Vector3& norm
void AudioSource::Update()
{
PROFILE_CPU();
PROFILE_MEM(Audio);
// Update the velocity
const Vector3 pos = GetPosition();
@@ -401,6 +408,9 @@ void AudioSource::Update()
_startingToPlay = false;
}
if (Math::NearEqual(GetTime(), _startTime) && _isActuallyPlayingSth && _startingToPlay)
ClipStarted();
if (!UseStreaming() && Math::NearEqual(GetTime(), 0.0f) && _isActuallyPlayingSth && !_startingToPlay)
{
int32 queuedBuffers;
@@ -416,6 +426,7 @@ void AudioSource::Update()
{
Stop();
}
ClipFinished();
}
}
@@ -486,6 +497,7 @@ void AudioSource::Update()
{
Stop();
}
ClipFinished();
}
ASSERT(_streamingFirstChunk < clip->Buffers.Count());
+15 -3
View File
@@ -13,7 +13,7 @@
/// Whether or not an audio source is spatial is controlled by the assigned AudioClip.The volume and the pitch of a spatial audio source is controlled by its position and the AudioListener's position/direction/velocity.
/// </remarks>
API_CLASS(Attributes="ActorContextMenu(\"New/Audio/Audio Source\"), ActorToolbox(\"Other\")")
class FLAXENGINE_API AudioSource : public Actor
class FLAXENGINE_API AudioSource : public Actor, IAssetReference
{
DECLARE_SCENE_OBJECT(AudioSource);
friend class AudioStreamingHandler;
@@ -76,6 +76,16 @@ public:
API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Audio Source\")")
AssetReference<AudioClip> Clip;
/// <summary>
/// Event fired when the audio clip starts.
/// </summary>
API_EVENT() Action ClipStarted;
/// <summary>
/// Event fired when the audio clip finishes.
/// </summary>
API_EVENT() Action ClipFinished;
/// <summary>
/// Gets the velocity of the source. Determines pitch in relation to AudioListener's position. Only relevant for spatial (3D) sources.
/// </summary>
@@ -293,8 +303,10 @@ public:
void RequestStreamingBuffersUpdate();
private:
void OnClipChanged();
void OnClipLoaded();
// [IAssetReference]
void OnAssetChanged(Asset* asset, void* caller) override;
void OnAssetLoaded(Asset* asset, void* caller) override;
void OnAssetUnloaded(Asset* asset, void* caller) override;
/// <summary>
/// Plays the audio source. Should have buffer(s) binded before.
@@ -9,6 +9,7 @@
#include "Engine/Tools/AudioTool/AudioTool.h"
#include "Engine/Engine/Units.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Audio/Audio.h"
#include "Engine/Audio/AudioListener.h"
#include "Engine/Audio/AudioSource.h"
@@ -321,6 +322,8 @@ void AudioBackendOAL::Listener_ReinitializeAll()
uint32 AudioBackendOAL::Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{
PROFILE_MEM(Audio);
uint32 sourceID = 0;
ALC::Source::Rebuild(sourceID, position, orientation, volume, pitch, pan, loop, spatial, attenuation, minDistance, doppler);
@@ -516,6 +519,7 @@ void AudioBackendOAL::Buffer_Delete(uint32 bufferID)
void AudioBackendOAL::Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info)
{
PROFILE_CPU();
PROFILE_MEM(Audio);
// Pick the format for the audio data (it might not be supported natively)
ALenum format = GetOpenALBufferFormat(info.NumChannels, info.BitDepth);
@@ -625,6 +629,8 @@ AudioBackend::FeatureFlags AudioBackendOAL::Base_Features()
void AudioBackendOAL::Base_OnActiveDeviceChanged()
{
PROFILE_MEM(Audio);
// Cleanup
Array<ALC::AudioSourceState> states;
states.EnsureCapacity(Audio::Sources.Count());
@@ -9,6 +9,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Audio/Audio.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Profiler/ProfilerMemory.h"
#if PLATFORM_WINDOWS
// Tweak Win ver
@@ -232,6 +233,7 @@ void AudioBackendXAudio2::Listener_ReinitializeAll()
uint32 AudioBackendXAudio2::Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{
PROFILE_MEM(Audio);
ScopeLock lock(XAudio2::Locker);
// Get first free source
@@ -580,6 +582,7 @@ void AudioBackendXAudio2::Source_DequeueProcessedBuffers(uint32 sourceID)
uint32 AudioBackendXAudio2::Buffer_Create()
{
PROFILE_MEM(Audio);
uint32 bufferID;
ScopeLock lock(XAudio2::Locker);
@@ -618,6 +621,7 @@ void AudioBackendXAudio2::Buffer_Delete(uint32 bufferID)
void AudioBackendXAudio2::Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info)
{
PROFILE_MEM(Audio);
CHECK(info.NumChannels <= MAX_INPUT_CHANNELS);
XAudio2::Locker.Lock();
+129 -71
View File
@@ -9,6 +9,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Core/LogContext.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Threading/ThreadLocal.h"
@@ -34,15 +35,18 @@ bool ContentDeprecated::Clear(bool newValue)
#endif
AssetReferenceBase::AssetReferenceBase(IAssetReference* owner)
: _owner(owner)
{
}
AssetReferenceBase::~AssetReferenceBase()
{
Asset* asset = _asset;
if (asset)
{
_asset = nullptr;
asset->OnLoaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
asset->OnUnloaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
asset->RemoveReference();
asset->RemoveReference(this);
}
}
@@ -51,52 +55,60 @@ String AssetReferenceBase::ToString() const
return _asset ? _asset->ToString() : TEXT("<null>");
}
void AssetReferenceBase::OnAssetChanged(Asset* asset, void* caller)
{
if (_owner)
_owner->OnAssetChanged(asset, this);
}
void AssetReferenceBase::OnAssetLoaded(Asset* asset, void* caller)
{
if (_asset != asset)
return;
Loaded();
if (_owner)
_owner->OnAssetLoaded(asset, this);
}
void AssetReferenceBase::OnAssetUnloaded(Asset* asset, void* caller)
{
if (_asset != asset)
return;
Unload();
OnSet(nullptr);
if (_owner)
_owner->OnAssetUnloaded(asset, this);
}
void AssetReferenceBase::OnSet(Asset* asset)
{
auto e = _asset;
if (e != asset)
{
if (e)
{
e->OnLoaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
e->OnUnloaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
e->RemoveReference();
}
e->RemoveReference(this);
_asset = e = asset;
if (e)
{
e->AddReference();
e->OnLoaded.Bind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
e->OnUnloaded.Bind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
}
e->AddReference(this);
Changed();
if (_owner)
_owner->OnAssetChanged(asset, this);
if (e && e->IsLoaded())
{
Loaded();
if (_owner)
_owner->OnAssetLoaded(asset, this);
}
}
}
void AssetReferenceBase::OnLoaded(Asset* asset)
{
if (_asset != asset)
return;
Loaded();
}
void AssetReferenceBase::OnUnloaded(Asset* asset)
{
if (_asset != asset)
return;
Unload();
OnSet(nullptr);
}
WeakAssetReferenceBase::~WeakAssetReferenceBase()
{
Asset* asset = _asset;
if (asset)
{
_asset = nullptr;
asset->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
asset->RemoveReference(this, true);
}
}
@@ -105,36 +117,43 @@ String WeakAssetReferenceBase::ToString() const
return _asset ? _asset->ToString() : TEXT("<null>");
}
void WeakAssetReferenceBase::OnAssetChanged(Asset* asset, void* caller)
{
}
void WeakAssetReferenceBase::OnAssetLoaded(Asset* asset, void* caller)
{
}
void WeakAssetReferenceBase::OnAssetUnloaded(Asset* asset, void* caller)
{
if (_asset != asset)
return;
Unload();
asset->RemoveReference(this, true);
_asset = nullptr;
}
void WeakAssetReferenceBase::OnSet(Asset* asset)
{
auto e = _asset;
if (e != asset)
{
if (e)
e->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
e->RemoveReference(this, true);
_asset = e = asset;
if (e)
e->OnUnloaded.Bind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
e->AddReference(this, true);
}
}
void WeakAssetReferenceBase::OnUnloaded(Asset* asset)
{
if (_asset != asset)
return;
Unload();
asset->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
_asset = nullptr;
}
SoftAssetReferenceBase::~SoftAssetReferenceBase()
{
Asset* asset = _asset;
if (asset)
{
_asset = nullptr;
asset->OnUnloaded.Unbind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
asset->RemoveReference();
asset->RemoveReference(this);
}
#if !BUILD_RELEASE
_id = Guid::Empty;
@@ -146,22 +165,34 @@ String SoftAssetReferenceBase::ToString() const
return _asset ? _asset->ToString() : (_id.IsValid() ? _id.ToString() : TEXT("<null>"));
}
void SoftAssetReferenceBase::OnAssetChanged(Asset* asset, void* caller)
{
}
void SoftAssetReferenceBase::OnAssetLoaded(Asset* asset, void* caller)
{
}
void SoftAssetReferenceBase::OnAssetUnloaded(Asset* asset, void* caller)
{
if (_asset != asset)
return;
_asset->RemoveReference(this);
_asset = nullptr;
_id = Guid::Empty;
Changed();
}
void SoftAssetReferenceBase::OnSet(Asset* asset)
{
if (_asset == asset)
return;
if (_asset)
{
_asset->OnUnloaded.Unbind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
_asset->RemoveReference();
}
_asset->RemoveReference(this);
_asset = asset;
_id = asset ? asset->GetID() : Guid::Empty;
if (asset)
{
asset->AddReference();
asset->OnUnloaded.Bind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
}
asset->AddReference(this);
Changed();
}
@@ -170,10 +201,7 @@ void SoftAssetReferenceBase::OnSet(const Guid& id)
if (_id == id)
return;
if (_asset)
{
_asset->OnUnloaded.Unbind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
_asset->RemoveReference();
}
_asset->RemoveReference(this);
_asset = nullptr;
_id = id;
Changed();
@@ -184,21 +212,7 @@ void SoftAssetReferenceBase::OnResolve(const ScriptingTypeHandle& type)
ASSERT(!_asset);
_asset = ::LoadAsset(_id, type);
if (_asset)
{
_asset->OnUnloaded.Bind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
_asset->AddReference();
}
}
void SoftAssetReferenceBase::OnUnloaded(Asset* asset)
{
if (_asset != asset)
return;
_asset->RemoveReference();
_asset->OnUnloaded.Unbind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
_asset = nullptr;
_id = Guid::Empty;
Changed();
_asset->AddReference(this);
}
Asset::Asset(const SpawnParams& params, const AssetInfo* info)
@@ -216,6 +230,39 @@ int32 Asset::GetReferencesCount() const
return (int32)Platform::AtomicRead(const_cast<int64 volatile*>(&_refCount));
}
void Asset::AddReference()
{
Platform::InterlockedIncrement(&_refCount);
}
void Asset::AddReference(IAssetReference* ref, bool week)
{
if (!week)
Platform::InterlockedIncrement(&_refCount);
if (ref)
{
//PROFILE_MEM(EngineDelegate); // Include references tracking memory within Delegate memory
ScopeLock lock(_referencesLocker);
_references.Add(ref);
}
}
void Asset::RemoveReference()
{
Platform::InterlockedDecrement(&_refCount);
}
void Asset::RemoveReference(IAssetReference* ref, bool week)
{
if (ref)
{
ScopeLock lock(_referencesLocker);
_references.Remove(ref);
}
if (!week)
Platform::InterlockedDecrement(&_refCount);
}
String Asset::ToString() const
{
return String::Format(TEXT("{0}, {1}, {2}"), GetTypeName(), GetID(), GetPath());
@@ -232,7 +279,7 @@ void Asset::OnDeleteObject()
const bool wasMarkedToDelete = _deleteFileOnUnload != 0;
#if USE_EDITOR
const String path = wasMarkedToDelete ? GetPath() : String::Empty;
const String path = wasMarkedToDelete ? String(GetPath()) : String::Empty;
#endif
const Guid id = GetID();
@@ -354,6 +401,7 @@ uint64 Asset::GetMemoryUsage() const
if (Platform::AtomicRead(&_loadingTask))
result += sizeof(ContentLoadTask);
result += (OnLoaded.Capacity() + OnReloading.Capacity() + OnUnloaded.Capacity()) * sizeof(EventType::FunctionType);
result += _references.Capacity() * sizeof(HashSet<IAssetReference*>::Bucket);
Locker.Unlock();
return result;
}
@@ -444,6 +492,9 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
}
PROFILE_CPU();
ZoneColor(TracyWaitZoneColor);
const StringView path(GetPath());
ZoneText(*path, path.Length());
Content::WaitForTask(loadingTask, timeoutInMilliseconds);
@@ -528,6 +579,7 @@ ContentLoadTask* Asset::createLoadingTask()
void Asset::startLoading()
{
PROFILE_MEM(ContentAssets);
ASSERT(!IsLoaded());
ASSERT(Platform::AtomicRead(&_loadingTask) == 0);
auto loadingTask = createLoadingTask();
@@ -614,7 +666,7 @@ void Asset::onLoaded()
{
onLoaded_MainThread();
}
else if (OnLoaded.IsBinded())
else if (OnLoaded.IsBinded() || _references.HasItems())
{
Function<void()> action;
action.Bind<Asset, &Asset::onLoaded>(this);
@@ -627,6 +679,9 @@ void Asset::onLoaded_MainThread()
ASSERT(IsInMainThread());
// Send event
ScopeLock lock(_referencesLocker);
for (const auto& e : _references)
e.Item->OnAssetLoaded(this, this);
OnLoaded(this);
}
@@ -640,6 +695,9 @@ void Asset::onUnload_MainThread()
CancelStreaming();
// Send event
ScopeLock lock(_referencesLocker);
for (const auto& e : _references)
e.Item->OnAssetUnloaded(this, this);
OnUnloaded(this);
}
+30 -9
View File
@@ -18,6 +18,20 @@
public: \
explicit type(const SpawnParams& params, const AssetInfo* info)
// Utility interface for objects that reference asset and want to get notified about asset reference changes.
class FLAXENGINE_API IAssetReference
{
public:
virtual ~IAssetReference() = default;
// Asset reference got changed.
virtual void OnAssetChanged(Asset* asset, void* caller) = 0;
// Asset got loaded.
virtual void OnAssetLoaded(Asset* asset, void* caller) = 0;
// Asset gets unloaded.
virtual void OnAssetUnloaded(Asset* asset, void* caller) = 0;
};
/// <summary>
/// Asset objects base class.
/// </summary>
@@ -48,6 +62,9 @@ protected:
int8 _deleteFileOnUnload : 1; // Indicates that asset source file should be removed on asset unload
int8 _isVirtual : 1; // Indicates that asset is pure virtual (generated or temporary, has no storage so won't be saved)
HashSet<IAssetReference*> _references;
CriticalSection _referencesLocker; // TODO: convert into a single interlocked exchange for the current thread owning lock
public:
/// <summary>
/// Initializes a new instance of the <see cref="Asset"/> class.
@@ -88,24 +105,28 @@ public:
/// <summary>
/// Adds reference to that asset.
/// </summary>
FORCE_INLINE void AddReference()
{
Platform::InterlockedIncrement(&_refCount);
}
void AddReference();
/// <summary>
/// Adds reference to that asset.
/// </summary>
void AddReference(IAssetReference* ref, bool week = false);
/// <summary>
/// Removes reference from that asset.
/// </summary>
FORCE_INLINE void RemoveReference()
{
Platform::InterlockedDecrement(&_refCount);
}
void RemoveReference();
/// <summary>
/// Removes reference from that asset.
/// </summary>
void RemoveReference(IAssetReference* ref, bool week = false);
public:
/// <summary>
/// Gets the path to the asset storage file. In Editor, it reflects the actual file, in cooked Game, it fakes the Editor path to be informative for developers.
/// </summary>
API_PROPERTY() virtual const String& GetPath() const = 0;
API_PROPERTY() virtual StringView GetPath() const = 0;
/// <summary>
/// Gets the asset type name.
+30 -3
View File
@@ -7,10 +7,11 @@
/// <summary>
/// Asset reference utility. Keeps reference to the linked asset object and handles load/unload events.
/// </summary>
class FLAXENGINE_API AssetReferenceBase
class FLAXENGINE_API AssetReferenceBase : public IAssetReference
{
protected:
Asset* _asset = nullptr;
IAssetReference* _owner = nullptr;
public:
/// <summary>
@@ -36,6 +37,12 @@ public:
/// </summary>
AssetReferenceBase() = default;
/// <summary>
/// Initializes a new instance of the <see cref="AssetReferenceBase"/> class.
/// </summary>
/// <param name="owner">The reference owner to keep notified about asset changes.</param>
AssetReferenceBase(IAssetReference* owner);
/// <summary>
/// Finalizes an instance of the <see cref="AssetReferenceBase"/> class.
/// </summary>
@@ -63,10 +70,14 @@ public:
/// </summary>
String ToString() const;
public:
// [IAssetReference]
void OnAssetChanged(Asset* asset, void* caller) override;
void OnAssetLoaded(Asset* asset, void* caller) override;
void OnAssetUnloaded(Asset* asset, void* caller) override;
protected:
void OnSet(Asset* asset);
void OnLoaded(Asset* asset);
void OnUnloaded(Asset* asset);
};
/// <summary>
@@ -87,6 +98,13 @@ public:
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetReference"/> class.
/// </summary>
explicit AssetReference(decltype(__nullptr))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetReference"/> class.
/// </summary>
@@ -96,6 +114,15 @@ public:
OnSet((Asset*)asset);
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetReference"/> class.
/// </summary>
/// <param name="owner">The reference owner to keep notified about asset changes.</param>
explicit AssetReference(IAssetReference* owner)
: AssetReferenceBase(owner)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AssetReference"/> class.
/// </summary>
+3 -2
View File
@@ -9,6 +9,7 @@
#include "Engine/Animations/Animations.h"
#include "Engine/Animations/SceneAnimations/SceneAnimation.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Serialization/MemoryReadStream.h"
#if USE_EDITOR
@@ -598,7 +599,7 @@ void Animation::OnScriptingDispose()
Asset::LoadResult Animation::load()
{
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
PROFILE_MEM(AnimationsData);
// Get stream with animations data
const auto dataChunk = GetChunk(0);
@@ -730,7 +731,7 @@ Asset::LoadResult Animation::load()
void Animation::unload(bool isReloading)
{
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
ScopeWriteLock systemScope(Animations::SystemLocker);
#if USE_EDITOR
if (_registeredForScriptingReload)
{
@@ -9,6 +9,7 @@
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Animations/Animations.h"
#include "Engine/Threading/Threading.h"
@@ -25,7 +26,7 @@ AnimationGraph::AnimationGraph(const SpawnParams& params, const AssetInfo* info)
Asset::LoadResult AnimationGraph::load()
{
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
PROFILE_MEM(AnimationsData);
// Get stream with graph data
const auto surfaceChunk = GetChunk(0);
@@ -51,7 +52,7 @@ Asset::LoadResult AnimationGraph::load()
void AnimationGraph::unload(bool isReloading)
{
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
ScopeWriteLock systemScope(Animations::SystemLocker);
Graph.Clear();
}
@@ -83,7 +84,7 @@ bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, b
Log::ArgumentNullException();
return true;
}
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
PROFILE_MEM(AnimationsData);
// Create Graph data
MemoryWriteStream writeStream(512);
@@ -169,7 +170,6 @@ bool AnimationGraph::SaveSurface(const BytesContainer& data)
{
if (OnCheckSave())
return true;
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
ScopeLock lock(Locker);
if (IsVirtual())
@@ -8,6 +8,7 @@
#include "Engine/Serialization/MemoryWriteStream.h"
#endif
#include "Engine/Animations/Animations.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Threading/Threading.h"
@@ -20,7 +21,7 @@ AnimationGraphFunction::AnimationGraphFunction(const SpawnParams& params, const
Asset::LoadResult AnimationGraphFunction::load()
{
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
PROFILE_MEM(AnimationsData);
// Get graph data from chunk
const auto surfaceChunk = GetChunk(0);
@@ -47,7 +48,7 @@ Asset::LoadResult AnimationGraphFunction::load()
void AnimationGraphFunction::unload(bool isReloading)
{
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
ScopeWriteLock systemScope(Animations::SystemLocker);
GraphData.Release();
Inputs.Clear();
Outputs.Clear();
@@ -96,7 +97,6 @@ bool AnimationGraphFunction::SaveSurface(const BytesContainer& data) const
{
if (OnCheckSave())
return true;
ConcurrentSystemLocker::WriteScope systemScope(Animations::SystemLocker);
ScopeLock lock(Locker);
// Set Visject Surface data
+53 -8
View File
@@ -165,9 +165,13 @@ Asset::LoadResult Material::load()
MaterialGenerator generator;
generator.Error.Bind(&OnGeneratorError);
if (_shaderHeader.Material.GraphVersion != MATERIAL_GRAPH_VERSION)
{
LOG(Info, "Converting material \'{0}\', from version {1} to {2}...", name, _shaderHeader.Material.GraphVersion, MATERIAL_GRAPH_VERSION);
}
else
{
LOG(Info, "Updating material \'{0}\'...", name);
}
// Load or create material surface
MaterialLayer* layer;
@@ -186,16 +190,55 @@ Asset::LoadResult Material::load()
// Load layer
layer = MaterialLayer::Load(GetID(), &stream, _shaderHeader.Material.Info, name);
if (ContentDeprecated::Clear())
const bool upgradeOldSpecular = _shaderHeader.Material.GraphVersion < 177;
if (ContentDeprecated::Clear() || upgradeOldSpecular)
{
// If encountered any deprecated data when loading graph then serialize it
MaterialGraph graph;
MemoryWriteStream writeStream(1024);
stream.SetPosition(0);
if (!graph.Load(&stream, true) && !graph.Save(&writeStream, true))
if (!graph.Load(&stream, true))
{
surfaceChunk->Data.Copy(ToSpan(writeStream));
ContentDeprecated::Clear();
if (upgradeOldSpecular)
{
// [Deprecated in 1.11]
// Specular calculations were changed to support up to 16% of reflectance via ^2 curve instead of linear up to 8%
// Insert Custom Code node that converts old materials into a new system to ensure they look the same
MaterialGraph::Node* rootNode = nullptr;
for (auto& e : graph.Nodes)
{
if (e.Type == ROOT_NODE_TYPE)
{
rootNode = &e;
break;
}
}
const auto& specularBoxInfo = MaterialGenerator::GetMaterialRootNodeBox(MaterialGraphBoxes::Specular);
auto specularBox = rootNode ? rootNode->GetBox(specularBoxInfo.ID) : nullptr;
if (specularBox && specularBox->HasConnection())
{
auto& customCodeNode = graph.Nodes.AddOne();
customCodeNode.ID = graph.Nodes.Count() + 1000;
customCodeNode.Type = GRAPH_NODE_MAKE_TYPE(1, 8);
customCodeNode.Boxes.Resize(2);
customCodeNode.Boxes[0] = MaterialGraphBox(&customCodeNode, 0, VariantType::Float4); // Input0
customCodeNode.Boxes[1] = MaterialGraphBox(&customCodeNode, 8, VariantType::Float4); // Output0
customCodeNode.Values.Resize(1);
customCodeNode.Values[0] = TEXT("// Convert old Specular value to a new range\nOutput0.x = min(Input0.x * 0.5f, 0.6f);");
auto specularSourceBox = specularBox->Connections[0];
specularBox->Connections.Clear();
specularSourceBox->Connections.Clear();
#define CONNECT(boxA, boxB) boxA->Connections.Add(boxB); boxB->Connections.Add(boxA)
CONNECT(specularSourceBox, (&customCodeNode.Boxes[0])); // Specular -> Input0
CONNECT((&customCodeNode.Boxes[1]), specularBox); // Output0 -> Specular
#undef CONNECT
}
}
if (!graph.Save(&writeStream, true))
{
surfaceChunk->Data.Copy(ToSpan(writeStream));
ContentDeprecated::Clear();
}
}
}
}
@@ -410,16 +453,18 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options)
// Prepare
auto& info = _shaderHeader.Material.Info;
const bool isSurfaceOrTerrainOrDeformable = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain || info.Domain == MaterialDomain::Deformable;
const bool isOpaque = info.BlendMode == MaterialBlendMode::Opaque;
const bool useCustomData = info.ShadingModel == MaterialShadingModel::Subsurface || info.ShadingModel == MaterialShadingModel::Foliage;
const bool useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && info.BlendMode != MaterialBlendMode::Opaque) || info.Domain == MaterialDomain::Particle;
const bool useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && !isOpaque) || info.Domain == MaterialDomain::Particle;
const bool useTess =
info.TessellationMode != TessellationMethod::None &&
RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrainOrDeformable;
const bool useDistortion =
(info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable || info.Domain == MaterialDomain::Particle) &&
info.BlendMode != MaterialBlendMode::Opaque &&
!isOpaque &&
EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseRefraction) &&
(info.FeaturesFlags & MaterialFeaturesFlags::DisableDistortion) == MaterialFeaturesFlags::None;
const MaterialShadingModel shadingModel = info.ShadingModel == MaterialShadingModel::CustomLit ? MaterialShadingModel::Unlit : info.ShadingModel;
// @formatter:off
static const char* Numbers[] =
@@ -431,7 +476,7 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options)
// Setup shader macros
options.Macros.Add({ "MATERIAL_DOMAIN", Numbers[(int32)info.Domain] });
options.Macros.Add({ "MATERIAL_BLEND", Numbers[(int32)info.BlendMode] });
options.Macros.Add({ "MATERIAL_SHADING_MODEL", Numbers[(int32)info.ShadingModel] });
options.Macros.Add({ "MATERIAL_SHADING_MODEL", Numbers[(int32)shadingModel] });
options.Macros.Add({ "MATERIAL_MASKED", Numbers[EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseMask) ? 1 : 0] });
options.Macros.Add({ "DECAL_BLEND_MODE", Numbers[(int32)info.DecalBlendingMode] });
options.Macros.Add({ "USE_EMISSIVE", Numbers[EnumHasAnyFlags(info.UsageFlags, MaterialUsageFlags::UseEmissive) ? 1 : 0] });
@@ -488,7 +533,7 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options)
options.Macros.Add({ "IS_PARTICLE", Numbers[info.Domain == MaterialDomain::Particle ? 1 : 0] });
options.Macros.Add({ "IS_DEFORMABLE", Numbers[info.Domain == MaterialDomain::Deformable ? 1 : 0] });
options.Macros.Add({ "USE_FORWARD", Numbers[useForward ? 1 : 0] });
options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrainOrDeformable && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] });
options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrainOrDeformable && isOpaque ? 1 : 0] });
options.Macros.Add({ "USE_DISTORTION", Numbers[useDistortion ? 1 : 0] });
#endif
}
@@ -90,9 +90,11 @@ void MaterialInstance::OnBaseParamsChanged()
// Get the newest parameters
baseParams->Clone(Params);
#if 0
// Override all public parameters by default
for (auto& param : Params)
param.SetIsOverride(param.IsPublic());
#endif
// Copy previous parameters values
for (int32 i = 0; i < oldParams.Count(); i++)
@@ -216,10 +218,14 @@ Asset::LoadResult MaterialInstance::load()
Guid baseMaterialId;
headerStream.Read(baseMaterialId);
auto baseMaterial = Content::LoadAsync<MaterialBase>(baseMaterialId);
if (baseMaterial)
baseMaterial->AddReference();
// Load parameters
if (Params.Load(&headerStream))
{
if (baseMaterial)
baseMaterial->RemoveReference();
LOG(Warning, "Cannot load material parameters.");
return LoadResult::CannotLoadData;
}
@@ -237,6 +243,8 @@ Asset::LoadResult MaterialInstance::load()
ParamsChanged();
}
if (baseMaterial)
baseMaterial->RemoveReference();
return LoadResult::Ok;
}
+11 -1
View File
@@ -18,6 +18,7 @@
#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Renderer/DrawCall.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Tools/ModelTool/ModelTool.h"
@@ -32,12 +33,14 @@ class StreamModelSDFTask : public GPUUploadTextureMipTask
{
private:
WeakAssetReference<Model> _asset;
FlaxStorageReference _dataRef;
FlaxStorage::LockData _dataLock;
public:
StreamModelSDFTask(Model* model, GPUTexture* texture, const Span<byte>& data, int32 mipIndex, int32 rowPitch, int32 slicePitch)
: GPUUploadTextureMipTask(texture, mipIndex, data, rowPitch, slicePitch, false)
, _asset(model)
, _dataRef(model->Storage)
, _dataLock(model->Storage->Lock())
{
}
@@ -58,6 +61,7 @@ public:
void OnEnd() override
{
_dataLock.Release();
_dataRef = FlaxStorageReference();
// Base
GPUUploadTextureMipTask::OnEnd();
@@ -258,6 +262,7 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, f
LOG(Warning, "Cannot generate SDF for virtual models on a main thread.");
return true;
}
auto chunkLocks = Storage ? Storage->Lock() : FlaxStorage::LockData();
lodIndex = Math::Clamp(lodIndex, HighestResidentLODIndex(), LODs.Count() - 1);
// Generate SDF
@@ -304,6 +309,7 @@ bool Model::Init(const Span<int32>& meshesCountPerLod)
Log::ArgumentOutOfRangeException();
return true;
}
PROFILE_MEM(GraphicsMeshes);
// Dispose previous data and disable streaming (will start data uploading tasks manually)
StopStreaming();
@@ -343,6 +349,7 @@ bool Model::Init(const Span<int32>& meshesCountPerLod)
bool Model::LoadHeader(ReadStream& stream, byte& headerVersion)
{
PROFILE_MEM(GraphicsMeshes);
if (ModelBase::LoadHeader(stream, headerVersion))
return true;
@@ -509,6 +516,7 @@ bool Model::Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk
void Model::SetupMaterialSlots(int32 slotsCount)
{
PROFILE_MEM(GraphicsMeshes);
ModelBase::SetupMaterialSlots(slotsCount);
// Adjust meshes indices for slots
@@ -584,6 +592,8 @@ int32 Model::GetAllocatedResidency() const
Asset::LoadResult Model::load()
{
PROFILE_MEM(GraphicsMeshes);
// Get header chunk
auto chunk0 = GetChunk(0);
if (chunk0 == nullptr || chunk0->IsMissing())
@@ -613,7 +623,7 @@ Asset::LoadResult Model::load()
{
String name;
#if !BUILD_RELEASE
name = GetPath() + TEXT(".SDF");
name = String(GetPath()) + TEXT(".SDF");
#endif
SDF.Texture = GPUDevice::Instance->CreateTexture(name);
}
+40 -3
View File
@@ -3,12 +3,15 @@
#include "ModelBase.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Transform.h"
#include "Engine/Core/Config/BuildSettings.h"
#include "Engine/Content/WeakAssetReference.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Graphics/Config.h"
#include "Engine/Graphics/Models/MeshBase.h"
#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
#include "Engine/Threading/Threading.h"
#if GPU_ENABLE_ASYNC_RESOURCES_CREATION
#include "Engine/Threading/ThreadPoolTask.h"
#define STREAM_TASK_BASE ThreadPoolTask
@@ -51,13 +54,14 @@ public:
AssetReference<ModelBase> model = _model.Get();
if (model == nullptr)
return true;
PROFILE_MEM(GraphicsMeshes);
// Get data
BytesContainer data;
model->GetLODData(_lodIndex, data);
if (data.IsInvalid())
{
LOG(Warning, "Missing data chunk");
LOG(Warning, "Missing data chunk with LOD{} for model '{}'", _lodIndex, model->ToString());
return true;
}
MemoryReadStream stream(data.Get(), data.Length());
@@ -230,6 +234,7 @@ bool ModelBase::Save(bool withMeshDataFromGpu, const StringView& path)
LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data).");
return true;
}
auto chunkLocks = Storage ? Storage->Lock() : FlaxStorage::LockData();
ScopeLock lock(Locker);
// Use a temporary chunks for data storage for virtual assets
@@ -334,6 +339,8 @@ bool ModelBase::LoadHeader(ReadStream& stream, byte& headerVersion)
bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly)
{
PROFILE_MEM(GraphicsMeshes);
// Load descriptor
static_assert(MODEL_MESH_VERSION == 2, "Update code");
uint32 vertices, triangles;
@@ -661,11 +668,42 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
return true;
}
// Process mesh data if need to decide vertex buffer on format dynamically
auto positionFormat = modelData.PositionFormat;
if (positionFormat == ModelData::PositionFormats::Automatic)
{
const float maxPositionError = BuildSettings::Get()->MaxMeshPositionError; // In world-units
const float maxPositionErrorSq = maxPositionError * maxPositionError;
if (maxPositionErrorSq > 0.0f)
{
positionFormat = ModelData::PositionFormats::Float16;
const Float3* positions = mesh.Positions.Get();
for (int32 i = 0; i < mesh.Positions.Count(); i++)
{
// Encode to Half3 and decode back to see the position error
Float3 position = positions[i];
Half3 encoded(position);
Float3 decoded = encoded.ToFloat3();
if (Float3::DistanceSquared(position, decoded) > maxPositionErrorSq)
{
// Cannot use lower quality so go back to full precision
positionFormat = ModelData::PositionFormats::Float32;
break;
}
}
}
else
{
// Full precision as default
positionFormat = ModelData::PositionFormats::Float32;
}
}
// Define vertex buffers layout and packing
Array<GPUVertexLayout::Elements, FixedAllocation<MODEL_MAX_VB>> vbElements;
const bool useSeparatePositions = !isSkinned;
const bool useSeparateColors = !isSkinned;
PixelFormat positionsFormat = modelData.PositionFormat == ModelData::PositionFormats::Float32 ? PixelFormat::R32G32B32_Float : PixelFormat::R16G16B16A16_Float;
PixelFormat positionsFormat = positionFormat == ModelData::PositionFormats::Float32 ? PixelFormat::R32G32B32_Float : PixelFormat::R16G16B16A16_Float;
PixelFormat texCoordsFormat = modelData.TexCoordFormat == ModelData::TexCoordFormats::Float16 ? PixelFormat::R16G16_Float : PixelFormat::R8G8_UNorm;
PixelFormat blendIndicesFormat = PixelFormat::R8G8B8A8_UInt;
PixelFormat blendWeightsFormat = PixelFormat::R8G8B8A8_UNorm;
@@ -679,7 +717,6 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l
}
{
byte vbIndex = 0;
// TODO: add option to quantize vertex attributes (eg. 8-bit blend weights, 8-bit texcoords)
// Position
if (useSeparatePositions)
+9 -1
View File
@@ -4,6 +4,10 @@
#include "Engine/Core/Log.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Shaders/GPUShader.h"
#if GPU_ENABLE_RESOURCE_NAMING && !USE_EDITOR
#include "Engine/Content/Content.h"
#include "Engine/Content/Cache/AssetsCache.h"
#endif
#include "Engine/Content/Upgraders/ShaderAssetUpgrader.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Serialization/MemoryReadStream.h"
@@ -14,7 +18,11 @@ Shader::Shader(const SpawnParams& params, const AssetInfo* info)
: ShaderAssetTypeBase<BinaryAsset>(params, info)
{
ASSERT(GPUDevice::Instance);
_shader = GPUDevice::Instance->CreateShader(info->Path);
StringView name = info->Path;
#if GPU_ENABLE_RESOURCE_NAMING && !USE_EDITOR
name = Content::GetRegistry()->GetEditorAssetPath(info->ID);
#endif
_shader = GPUDevice::Instance->CreateShader(name);
ASSERT(_shader);
GPU = _shader;
}
@@ -6,6 +6,7 @@
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Content/Upgraders/SkeletonMaskUpgrader.h"
#include "Engine/Threading/Threading.h"
REGISTER_BINARY_ASSET_WITH_UPGRADER(SkeletonMask, "FlaxEngine.SkeletonMask", SkeletonMaskUpgrader, true);
@@ -18,6 +18,7 @@
#include "Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h"
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Renderer/DrawCall.h"
#if USE_EDITOR
#include "Engine/Graphics/Models/ModelData.h"
@@ -458,6 +459,7 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
Log::ArgumentOutOfRangeException();
return true;
}
PROFILE_MEM(GraphicsMeshes);
// Dispose previous data and disable streaming (will start data uploading tasks manually)
StopStreaming();
@@ -501,6 +503,7 @@ void BlendShape::LoadHeader(ReadStream& stream, byte headerVersion)
void BlendShape::Load(ReadStream& stream, byte meshVersion)
{
PROFILE_MEM(GraphicsMeshes);
UseNormals = stream.ReadBool();
stream.ReadUint32(&MinVertexIndex);
stream.ReadUint32(&MaxVertexIndex);
@@ -531,6 +534,7 @@ void BlendShape::Save(WriteStream& stream) const
bool SkinnedModel::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly)
{
PROFILE_MEM(GraphicsMeshes);
if (ModelBase::LoadMesh(stream, meshVersion, mesh, dataIfReadOnly))
return true;
static_assert(MODEL_MESH_VERSION == 2, "Update code");
@@ -560,6 +564,7 @@ bool SkinnedModel::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase
bool SkinnedModel::LoadHeader(ReadStream& stream, byte& headerVersion)
{
PROFILE_MEM(GraphicsMeshes);
if (ModelBase::LoadHeader(stream, headerVersion))
return true;
static_assert(MODEL_HEADER_VERSION == 2, "Update code");
@@ -861,6 +866,7 @@ uint64 SkinnedModel::GetMemoryUsage() const
void SkinnedModel::SetupMaterialSlots(int32 slotsCount)
{
PROFILE_MEM(GraphicsMeshes);
ModelBase::SetupMaterialSlots(slotsCount);
// Adjust meshes indices for slots
@@ -954,6 +960,7 @@ Asset::LoadResult SkinnedModel::load()
if (chunk0 == nullptr || chunk0->IsMissing())
return LoadResult::MissingDataChunk;
MemoryReadStream headerStream(chunk0->Get(), chunk0->Size());
PROFILE_MEM(GraphicsMeshes);
// Load asset data (anything but mesh contents that use streaming)
byte headerVersion;
+1 -1
View File
@@ -36,7 +36,7 @@ bool Texture::Save(const StringView& path)
bool Texture::Save(const StringView& path, const InitData* customData)
{
if (OnCheckSave())
if (OnCheckSave(path))
return true;
ScopeLock lock(Locker);
@@ -18,6 +18,7 @@
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/JsonWriter.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Utilities/StringConverter.h"
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Level/SceneObject.h"
@@ -37,10 +38,12 @@ namespace
void PrintStack(LogType type)
{
#if LOG_ENABLE
const String stack = VisualScripting::GetStackTrace();
Log::Logger::Write(type, TEXT("Visual Script stack trace:"));
Log::Logger::Write(type, stack);
Log::Logger::Write(type, TEXT(""));
#endif
}
bool SerializeValue(const Variant& a, const Variant& b)
@@ -1340,6 +1343,8 @@ bool VisualScript::Save(const StringView& path)
Asset::LoadResult VisualScript::load()
{
PROFILE_MEM(ScriptingVisual);
// Build Visual Script typename that is based on asset id
String typeName = _id.ToString();
StringUtils::ConvertUTF162ANSI(typeName.Get(), _typenameChars, 32);
@@ -1532,6 +1537,7 @@ Asset::LoadResult VisualScript::load()
void VisualScript::unload(bool isReloading)
{
PROFILE_MEM(ScriptingVisual);
#if USE_EDITOR
if (isReloading)
{
@@ -1588,6 +1594,7 @@ AssetChunksFlag VisualScript::getChunksToPreload() const
void VisualScript::CacheScriptingType()
{
PROFILE_MEM(ScriptingVisual);
ScopeLock lock(VisualScriptingBinaryModule::Locker);
auto& binaryModule = VisualScriptingModule;
@@ -1723,6 +1730,7 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri
VisualScript* visualScript = VisualScriptingModule.Scripts[params.Type.TypeIndex];
// Initialize instance data
PROFILE_MEM(ScriptingVisual);
ScopeLock lock(visualScript->Locker);
auto& instanceParams = visualScript->_instances[object->GetID()].Params;
instanceParams.Resize(visualScript->Graph.Parameters.Count());
@@ -1747,6 +1755,8 @@ ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const Scri
void VisualScriptingBinaryModule::OnScriptsReloading()
{
PROFILE_MEM(ScriptingVisual);
// Clear any cached types from that module across all loaded Visual Scripts
for (auto& script : Scripts)
{
@@ -1795,6 +1805,7 @@ void VisualScriptingBinaryModule::OnScriptsReloading()
void VisualScriptingBinaryModule::OnEvent(ScriptingObject* object, Span<Variant> parameters, ScriptingTypeHandle eventType, StringView eventName)
{
PROFILE_MEM(ScriptingVisual);
if (object)
{
// Object event
@@ -1900,9 +1911,13 @@ bool VisualScriptingBinaryModule::InvokeMethod(void* method, const Variant& inst
if (!instanceObject || instanceObject->GetTypeHandle() != vsMethod->Script->GetScriptingType())
{
if (!instanceObject)
{
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) without object instance", String(vsMethod->Script->GetScriptTypeName()), String(vsMethod->Name), vsMethod->ParamNames.Count());
}
else
{
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) with invalid object instance of type '{3}'", String(vsMethod->Script->GetScriptTypeName()), String(vsMethod->Name), vsMethod->ParamNames.Count(), String(instanceObject->GetType().Fullname));
}
return true;
}
}
@@ -1952,6 +1967,7 @@ bool VisualScriptingBinaryModule::GetFieldValue(void* field, const Variant& inst
bool VisualScriptingBinaryModule::SetFieldValue(void* field, const Variant& instance, Variant& value)
{
PROFILE_MEM(ScriptingVisual);
const auto vsFiled = (VisualScript::Field*)field;
const auto instanceObject = (ScriptingObject*)instance;
if (!instanceObject)
@@ -2038,6 +2054,7 @@ void VisualScriptingBinaryModule::SerializeObject(JsonWriter& stream, ScriptingO
void VisualScriptingBinaryModule::DeserializeObject(ISerializable::DeserializeStream& stream, ScriptingObject* object, ISerializeModifier* modifier)
{
PROFILE_MEM(ScriptingVisual);
ASSERT(stream.IsObject());
Locker.Lock();
const auto asset = Scripts[object->GetTypeHandle().TypeIndex].Get();
@@ -2161,6 +2178,7 @@ const Variant& VisualScript::GetScriptInstanceParameterValue(const StringView& n
void VisualScript::SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, const Variant& value)
{
PROFILE_MEM(ScriptingVisual);
CHECK(instance);
for (int32 paramIndex = 0; paramIndex < Graph.Parameters.Count(); paramIndex++)
{
@@ -2182,6 +2200,7 @@ void VisualScript::SetScriptInstanceParameterValue(const StringView& name, Scrip
void VisualScript::SetScriptInstanceParameterValue(const StringView& name, ScriptingObject* instance, Variant&& value)
{
PROFILE_MEM(ScriptingVisual);
CHECK(instance);
for (int32 paramIndex = 0; paramIndex < Graph.Parameters.Count(); paramIndex++)
{
@@ -2379,6 +2398,7 @@ VisualScriptingBinaryModule* VisualScripting::GetBinaryModule()
Variant VisualScripting::Invoke(VisualScript::Method* method, ScriptingObject* instance, Span<Variant> parameters)
{
PROFILE_MEM(ScriptingVisual);
CHECK_RETURN(method && method->Script->IsLoaded(), Variant::Zero);
PROFILE_CPU_SRC_LOC(method->ProfilerData);
@@ -2419,6 +2439,7 @@ bool VisualScripting::Evaluate(VisualScript* script, ScriptingObject* instance,
const auto box = node->GetBox(boxId);
if (!box)
return false;
PROFILE_MEM(ScriptingVisual);
// Add to the calling stack
ScopeContext scope;
+4 -2
View File
@@ -18,7 +18,7 @@ public:
/// <param name="id">The asset id.</param>
/// <returns>Loaded asset of null.</returns>
template<typename T>
T* LoadAsync(const Guid& id)
T* Load(const Guid& id)
{
for (auto& e : *this)
{
@@ -26,8 +26,10 @@ public:
return (T*)e.Get();
}
auto asset = (T*)::LoadAsset(id, T::TypeInitializer);
if (asset)
if (asset && !asset->WaitForLoaded())
Add(asset);
else
asset = nullptr;
return asset;
}
+4 -2
View File
@@ -10,6 +10,7 @@
#include "Engine/Serialization/JsonTools.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Threading/ThreadPoolTask.h"
#include "Engine/Profiler/ProfilerMemory.h"
#if USE_EDITOR
#include "Engine/Platform/FileSystem.h"
#include "Engine/Threading/Threading.h"
@@ -463,10 +464,10 @@ void BinaryAsset::OnDeleteObject()
#endif
const String& BinaryAsset::GetPath() const
StringView BinaryAsset::GetPath() const
{
#if USE_EDITOR
return Storage ? Storage->GetPath() : String::Empty;
return Storage ? StringView(Storage->GetPath()) : StringView::Empty;
#else
// In build all assets are packed into packages so use ID for original path lookup
return Content::GetRegistry()->GetEditorAssetPath(_id);
@@ -527,6 +528,7 @@ protected:
auto storage = ref->Storage;
auto factory = (BinaryAssetFactoryBase*)Content::GetAssetFactory(ref->GetTypeName());
ASSERT(factory);
PROFILE_MEM(ContentAssets);
// Here we should open storage and extract AssetInitData
// This would also allow to convert/upgrade data
+1 -1
View File
@@ -292,7 +292,7 @@ public:
#if USE_EDITOR
void OnDeleteObject() override;
#endif
const String& GetPath() const final override;
StringView GetPath() const final override;
uint64 GetMemoryUsage() const override;
protected:
+47 -27
View File
@@ -10,15 +10,28 @@
#include "Engine/Serialization/FileWriteStream.h"
#include "Engine/Serialization/FileReadStream.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Storage/JsonStorageProxy.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Engine/Globals.h"
#include "FlaxEngine.Gen.h"
#if ASSETS_CACHE_EDITABLE
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Storage/JsonStorageProxy.h"
#include "Engine/Threading/Threading.h"
#define ASSETS_CACHE_LOCK() ScopeLock lock(_locker)
#else
#define ASSETS_CACHE_LOCK()
#endif
int32 AssetsCache::Size() const
{
ASSETS_CACHE_LOCK();
const int32 result = _registry.Count();
return result;
}
void AssetsCache::Init()
{
PROFILE_CPU();
Entry e;
int32 count;
Stopwatch stopwatch;
@@ -71,7 +84,7 @@ void AssetsCache::Init()
return;
}
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
_isDirty = false;
// Load elements count
@@ -126,6 +139,16 @@ void AssetsCache::Init()
_pathsMapping.Add(mappedPath, id);
}
#if !USE_EDITOR && !BUILD_RELEASE
// Build inverse path mapping in development builds for faster GetEditorAssetPath (eg. used by PROFILE_CPU_ASSET)
_pathsMappingInv.Clear();
_pathsMappingInv.EnsureCapacity(count);
for (auto& mapping : _pathsMapping)
{
_pathsMappingInv.Add(mapping.Value, StringView(mapping.Key));
}
#endif
// Check errors
const bool hasError = stream->HasError();
deleteStream.Delete();
@@ -153,7 +176,7 @@ bool AssetsCache::Save()
if (!_isDirty && FileSystem::FileExists(_path))
return false;
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
if (Save(_path, _registry, _pathsMapping))
return true;
@@ -222,12 +245,16 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa
return false;
}
const String& AssetsCache::GetEditorAssetPath(const Guid& id) const
StringView AssetsCache::GetEditorAssetPath(const Guid& id) const
{
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
#if USE_EDITOR
auto e = _registry.TryGet(id);
return e ? e->Info.Path : String::Empty;
#elif !BUILD_RELEASE
StringView result;
_pathsMappingInv.TryGet(id, result);
return result;
#else
for (auto& e : _pathsMapping)
{
@@ -241,10 +268,8 @@ const String& AssetsCache::GetEditorAssetPath(const Guid& id) const
bool AssetsCache::FindAsset(const StringView& path, AssetInfo& info)
{
PROFILE_CPU();
bool result = false;
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
// Check if asset has direct mapping to id (used for some cooked assets)
Guid id;
@@ -293,7 +318,7 @@ bool AssetsCache::FindAsset(const Guid& id, AssetInfo& info)
{
PROFILE_CPU();
bool result = false;
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
auto e = _registry.TryGet(id);
if (e != nullptr)
{
@@ -315,14 +340,14 @@ bool AssetsCache::FindAsset(const Guid& id, AssetInfo& info)
void AssetsCache::GetAll(Array<Guid>& result) const
{
PROFILE_CPU();
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
_registry.GetKeys(result);
}
void AssetsCache::GetAllByTypeName(const StringView& typeName, Array<Guid>& result) const
{
PROFILE_CPU();
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
for (auto i = _registry.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value.Info.TypeName == typeName)
@@ -330,6 +355,8 @@ void AssetsCache::GetAllByTypeName(const StringView& typeName, Array<Guid>& resu
}
}
#if ASSETS_CACHE_EDITABLE
void AssetsCache::RegisterAssets(FlaxStorage* storage)
{
PROFILE_CPU();
@@ -341,7 +368,7 @@ void AssetsCache::RegisterAssets(FlaxStorage* storage)
storage->GetEntries(entries);
ASSERT(entries.HasItems());
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
auto storagePath = storage->GetPath();
// Remove all old entries from that location
@@ -439,7 +466,7 @@ void AssetsCache::RegisterAssets(const FlaxStorageReference& storage)
void AssetsCache::RegisterAsset(const Guid& id, const String& typeName, const StringView& path)
{
PROFILE_CPU();
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
// Check if asset has been already added to the registry
bool isMissing = true;
@@ -491,8 +518,7 @@ void AssetsCache::RegisterAsset(const Guid& id, const String& typeName, const St
bool AssetsCache::DeleteAsset(const StringView& path, AssetInfo* info)
{
bool result = false;
_locker.Lock();
ASSETS_CACHE_LOCK();
for (auto i = _registry.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value.Info.Path == path)
@@ -505,16 +531,13 @@ bool AssetsCache::DeleteAsset(const StringView& path, AssetInfo* info)
break;
}
}
_locker.Unlock();
return result;
}
bool AssetsCache::DeleteAsset(const Guid& id, AssetInfo* info)
{
bool result = false;
_locker.Lock();
ASSETS_CACHE_LOCK();
const auto e = _registry.TryGet(id);
if (e != nullptr)
{
@@ -524,16 +547,13 @@ bool AssetsCache::DeleteAsset(const Guid& id, AssetInfo* info)
_isDirty = true;
result = true;
}
_locker.Unlock();
return result;
}
bool AssetsCache::RenameAsset(const StringView& oldPath, const StringView& newPath)
{
bool result = false;
_locker.Lock();
ASSETS_CACHE_LOCK();
for (auto i = _registry.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value.Info.Path == oldPath)
@@ -544,11 +564,11 @@ bool AssetsCache::RenameAsset(const StringView& oldPath, const StringView& newPa
break;
}
}
_locker.Unlock();
return result;
}
#endif
bool AssetsCache::IsEntryValid(Entry& e)
{
#if ENABLE_ASSETS_DISCOVERY
+13 -9
View File
@@ -16,6 +16,9 @@ struct AssetHeader;
struct FlaxStorageReference;
class FlaxStorage;
// In cooked game all assets are there and all access to registry is read-only so can be multithreaded
#define ASSETS_CACHE_EDITABLE (USE_EDITOR)
/// <summary>
/// Assets cache flags.
/// </summary>
@@ -75,22 +78,21 @@ public:
private:
bool _isDirty = false;
#if ASSETS_CACHE_EDITABLE
CriticalSection _locker;
#endif
Registry _registry;
PathsMapping _pathsMapping;
#if !USE_EDITOR && !BUILD_RELEASE
Dictionary<Guid, StringView> _pathsMappingInv;
#endif
String _path;
public:
/// <summary>
/// Gets amount of registered assets.
/// </summary>
int32 Size() const
{
_locker.Lock();
const int32 result = _registry.Count();
_locker.Unlock();
return result;
}
int32 Size() const;
public:
/// <summary>
@@ -116,11 +118,11 @@ public:
public:
/// <summary>
/// Finds the asset path by id. In editor it returns the actual asset path, at runtime it returns the mapped asset path.
/// Finds the asset path by id. In editor, it returns the actual asset path, at runtime it returns the mapped asset path.
/// </summary>
/// <param name="id">The asset id.</param>
/// <returns>The asset path, or empty if failed to find.</returns>
const String& GetEditorAssetPath(const Guid& id) const;
StringView GetEditorAssetPath(const Guid& id) const;
/// <summary>
/// Finds the asset info by path.
@@ -173,6 +175,7 @@ public:
/// <param name="result">The result array.</param>
void GetAllByTypeName(const StringView& typeName, Array<Guid, HeapAllocation>& result) const;
#if ASSETS_CACHE_EDITABLE
/// <summary>
/// Register assets in the cache
/// </summary>
@@ -223,6 +226,7 @@ public:
/// <param name="newPath">New path</param>
/// <returns>True if has been deleted, otherwise false</returns>
bool RenameAsset(const StringView& oldPath, const StringView& newPath);
#endif
/// <summary>
/// Determines whether cached asset entry is valid.
+6 -1
View File
@@ -4,8 +4,13 @@
#include "Engine/Core/Config.h"
// Amount of content loading threads per single physical CPU core
// Amount of content loading threads per single logical CPU core
#ifndef LOADING_THREAD_PER_LOGICAL_CORE
#define LOADING_THREAD_PER_LOGICAL_CORE 0.5f
#endif
// Enables pinning loading threads to the logical CPU cores with affinity mask
//#define LOADING_THREAD_AFFINITY_MASK(thread) (1 << (thread + 1))
// Enables additional assets metadata verification
#define ASSETS_LOADING_EXTRA_VERIFICATION (BUILD_DEBUG || USE_EDITOR)
+62 -5
View File
@@ -28,7 +28,9 @@
#include "Engine/Engine/Globals.h"
#include "Engine/Level/Types.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/Internal/InternalCalls.h"
#include "Engine/Scripting/Scripting.h"
#if USE_EDITOR
#include "Editor/Editor.h"
@@ -117,6 +119,8 @@ ContentService ContentServiceInstance;
bool ContentService::Init()
{
PROFILE_MEM(Content);
// Load assets registry
Cache.Init();
@@ -126,17 +130,17 @@ bool ContentService::Init()
LOG(Info, "Creating {0} content loading threads...", count);
MainLoadThread = New<LoadingThread>();
ThisLoadThread = MainLoadThread;
LoadThreads.EnsureCapacity(count);
LoadThreads.Resize(count);
for (int32 i = 0; i < count; i++)
{
auto thread = New<LoadingThread>();
LoadThreads[i] = thread;
if (thread->Start(String::Format(TEXT("Load Thread {0}"), i)))
{
LOG(Fatal, "Cannot spawn content thread {0}/{1}", i, count);
Delete(thread);
return true;
}
LoadThreads.Add(thread);
}
return false;
@@ -159,6 +163,7 @@ void ContentService::Update()
void ContentService::LateUpdate()
{
PROFILE_CPU();
PROFILE_MEM(Content);
// Check if need to perform an update of unloading assets
const TimeSpan timeNow = Time::Update.UnscaledTime;
@@ -324,6 +329,7 @@ String LoadingThread::ToString() const
int32 LoadingThread::Run()
{
PROFILE_MEM(Content);
#if USE_EDITOR && PLATFORM_WINDOWS
// Initialize COM
// TODO: maybe add sth to Thread::Create to indicate that thread will use COM stuff
@@ -334,21 +340,28 @@ int32 LoadingThread::Run()
return -1;
}
#endif
#ifdef LOADING_THREAD_AFFINITY_MASK
Platform::SetThreadAffinityMask(LOADING_THREAD_AFFINITY_MASK(LoadThreads.Find(this)));
#endif
ContentLoadTask* task;
ThisLoadThread = this;
MONO_THREAD_INFO_TYPE* monoThreadInfo = nullptr;
while (Platform::AtomicRead(&_exitFlag) == 0)
{
if (LoadTasks.try_dequeue(task))
{
Run(task);
MONO_THREAD_INFO_GET(monoThreadInfo);
}
else
{
MONO_ENTER_GC_SAFE_WITH_INFO(monoThreadInfo);
LoadTasksMutex.Lock();
LoadTasksSignal.Wait(LoadTasksMutex);
LoadTasksMutex.Unlock();
MONO_EXIT_GC_SAFE_WITH_INFO;
}
}
@@ -416,6 +429,7 @@ bool Content::GetAssetInfo(const Guid& id, AssetInfo& info)
if (Cache.FindAsset(id, info))
return true;
PROFILE_CPU();
PROFILE_MEM(Content);
// Locking injects some stalls but we need to make it safe (only one thread can pass though it at once)
ScopeLock lock(WorkspaceDiscoveryLocker);
@@ -465,6 +479,7 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
if (!FileSystem::FileExists(path))
return false;
PROFILE_CPU();
PROFILE_MEM(Content);
const auto extension = FileSystem::GetExtension(path).ToLower();
@@ -511,7 +526,7 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
#endif
}
String Content::GetEditorAssetPath(const Guid& id)
StringView Content::GetEditorAssetPath(const Guid& id)
{
return Cache.GetEditorAssetPath(id);
}
@@ -593,6 +608,7 @@ Asset* Content::LoadAsyncInternal(const StringView& internalPath, const MClass*
Asset* Content::LoadAsyncInternal(const StringView& internalPath, const ScriptingTypeHandle& type)
{
PROFILE_MEM(Content);
#if USE_EDITOR
const String path = Globals::EngineContentFolder / internalPath + ASSET_FILES_EXTENSION_WITH_DOT;
if (!FileSystem::FileExists(path))
@@ -635,6 +651,8 @@ Asset* Content::LoadAsync(const StringView& path, const MClass* type)
Asset* Content::LoadAsync(const StringView& path, const ScriptingTypeHandle& type)
{
PROFILE_MEM(Content);
// Ensure path is in a valid format
String pathNorm(path);
ContentStorageManager::FormatPath(pathNorm);
@@ -687,7 +705,7 @@ Asset* Content::GetAsset(const StringView& outputPath)
{
if (outputPath.IsEmpty())
return nullptr;
PROFILE_CPU();
ScopeLock lock(AssetsLocker);
for (auto i = Assets.Begin(); i.IsNotEnd(); ++i)
{
@@ -737,6 +755,7 @@ void Content::DeleteAsset(const StringView& path)
return;
}
#if USE_EDITOR
ScopeLock locker(AssetsLocker);
// Remove from registry
@@ -753,6 +772,7 @@ void Content::DeleteAsset(const StringView& path)
// Delete file
deleteFileSafety(path, info.ID);
#endif
}
void Content::deleteFileSafety(const StringView& path, const Guid& id)
@@ -791,6 +811,23 @@ void Content::deleteFileSafety(const StringView& path, const Guid& id)
#endif
}
#if !COMPILE_WITHOUT_CSHARP
#include "Engine/Scripting/ManagedCLR/MUtils.h"
void* Content::GetAssetsInternal()
{
AssetsLocker.Lock();
MArray* result = MCore::Array::New(Asset::TypeInitializer.GetClass(), Assets.Count());
int32 i = 0;
for (const auto& e : Assets)
MCore::GC::WriteArrayRef(result, e.Value->GetOrCreateManagedInstance(), i++);
AssetsLocker.Unlock();
return result;
}
#endif
#if USE_EDITOR
bool Content::RenameAsset(const StringView& oldPath, const StringView& newPath)
@@ -977,7 +1014,7 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
FileSystem::DeleteFile(tmpPath);
// Reload storage
if (auto storage = ContentStorageManager::GetStorage(dstPath))
if (auto storage = ContentStorageManager::GetStorage(dstPath, false))
{
storage->Reload();
}
@@ -1023,6 +1060,7 @@ Asset* Content::CreateVirtualAsset(const MClass* type)
Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type)
{
PROFILE_CPU();
PROFILE_MEM(Content);
auto& assetType = type.GetType();
// Init mock asset info
@@ -1045,7 +1083,9 @@ Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type)
}
// Create asset object
PROFILE_MEM_BEGIN(ContentAssets);
auto asset = factory->NewVirtual(info);
PROFILE_MEM_END();
if (asset == nullptr)
{
LOG(Error, "Cannot create virtual asset object.");
@@ -1054,7 +1094,9 @@ Asset* Content::CreateVirtualAsset(const ScriptingTypeHandle& type)
asset->RegisterObject();
// Call initializer function
PROFILE_MEM_BEGIN(ContentAssets);
asset->InitAsVirtual();
PROFILE_MEM_END();
// Register asset
AssetsLocker.Lock();
@@ -1076,11 +1118,21 @@ void Content::WaitForTask(ContentLoadTask* loadingTask, double timeoutInMillisec
const double timeoutInSeconds = timeoutInMilliseconds * 0.001;
const double startTime = Platform::GetTimeSeconds();
int32 loopCounter = 0;
Task* task = loadingTask;
Array<ContentLoadTask*, InlinedAllocation<64>> localQueue;
#define CHECK_CONDITIONS() (!Engine::ShouldExit() && (timeoutInSeconds <= 0.0 || Platform::GetTimeSeconds() - startTime < timeoutInSeconds))
do
{
// Give opportunity for other threads to use the current core
if (loopCounter == 0)
; // First run is fast
else if (loopCounter < 10)
Platform::Yield();
else
Platform::Sleep(1);
loopCounter++;
// Try to execute content tasks
while (task->IsQueued() && CHECK_CONDITIONS())
{
@@ -1097,6 +1149,8 @@ void Content::WaitForTask(ContentLoadTask* loadingTask, double timeoutInMillisec
localQueue.Clear();
}
PROFILE_CPU_NAMED("Inline");
ZoneColor(0xffaaaaaa);
thread->Run(tmp);
}
else
@@ -1209,6 +1263,7 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
{
if (!id.IsValid())
return nullptr;
PROFILE_MEM(Content);
// Check if asset has been already loaded
Asset* result = nullptr;
@@ -1277,7 +1332,9 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
}
// Create asset object
PROFILE_MEM_BEGIN(ContentAssets);
result = factory->New(assetInfo);
PROFILE_MEM_END();
if (result == nullptr)
{
LOG(Error, "Cannot create asset object. Info: {0}", assetInfo.ToString());
+28
View File
@@ -1,5 +1,6 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEngine.Interop;
using System;
using System.Runtime.CompilerServices;
@@ -7,6 +8,33 @@ namespace FlaxEngine
{
partial class Content
{
/// <summary>
/// Gets the assets (loaded or during load).
/// </summary>
public static Asset[] Assets
{
get
{
IntPtr ptr = Internal_GetAssetsInternal();
ManagedArray array = Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(ptr).Target);
return NativeInterop.GCHandleArrayToManagedArray<Asset>(array);
}
}
/// <summary>
/// Gets the assets (loaded or during load).
/// </summary>
/// <param name="buffer">Output buffer to fill with asset pointers. Can be provided by a user to avoid memory allocation. Buffer might be larger than actual list size. Use <paramref name="count"/> for actual item count.></param>
/// <param name="count">Amount of valid items inside <paramref name="buffer"/>.</param>
public static void GetAssets(ref Asset[] buffer, out int count)
{
count = 0;
IntPtr ptr = Internal_GetAssetsInternal();
ManagedArray array = Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(ptr).Target);
buffer = NativeInterop.GCHandleArrayToManagedArray<Asset>(array, buffer);
count = buffer.Length;
}
/// <summary>
/// Loads asset to the Content Pool and holds it until it won't be referenced by any object. Returns null if asset is missing. Actual asset data loading is performed on a other thread in async.
/// </summary>
+7 -2
View File
@@ -75,7 +75,7 @@ public:
/// </summary>
/// <param name="id">The asset id.</param>
/// <returns>The asset path, or empty if failed to find.</returns>
API_FUNCTION() static String GetEditorAssetPath(const Guid& id);
API_FUNCTION() static StringView GetEditorAssetPath(const Guid& id);
/// <summary>
/// Finds all the asset IDs. Uses asset registry.
@@ -122,7 +122,7 @@ public:
/// Gets the assets (loaded or during load).
/// </summary>
/// <returns>The collection of assets.</returns>
API_PROPERTY() static Array<Asset*, HeapAllocation> GetAssets();
static Array<Asset*, HeapAllocation> GetAssets();
/// <summary>
/// Gets the raw dictionary of assets (loaded or during load).
@@ -368,4 +368,9 @@ private:
static void onAssetUnload(Asset* asset);
static void onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId);
static void deleteFileSafety(const StringView& path, const Guid& id);
// Internal bindings
#if !COMPILE_WITHOUT_CSHARP
API_FUNCTION(NoProxy) static void* GetAssetsInternal();
#endif
};
+9 -2
View File
@@ -20,6 +20,7 @@
#include "Engine/Core/Cache.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MField.h"
@@ -39,6 +40,7 @@ String JsonAssetBase::GetData() const
if (Data == nullptr)
return String::Empty;
PROFILE_CPU_NAMED("JsonAsset.GetData");
PROFILE_MEM(ContentAssets);
rapidjson_flax::StringBuffer buffer;
OnGetData(buffer);
return String((const char*)buffer.GetString(), (int32)buffer.GetSize());
@@ -49,6 +51,7 @@ void JsonAssetBase::SetData(const StringView& value)
if (!IsLoaded())
return;
PROFILE_CPU_NAMED("JsonAsset.SetData");
PROFILE_MEM(ContentAssets);
const StringAnsi dataJson(value);
ScopeLock lock(Locker);
const StringView dataTypeName = DataTypeName;
@@ -60,6 +63,7 @@ void JsonAssetBase::SetData(const StringView& value)
bool JsonAssetBase::Init(const StringView& dataTypeName, const StringAnsiView& dataJson)
{
PROFILE_MEM(ContentAssets);
unload(true);
DataTypeName = dataTypeName;
DataEngineBuild = FLAXENGINE_VERSION_BUILD;
@@ -87,7 +91,7 @@ void JsonAssetBase::OnGetData(rapidjson_flax::StringBuffer& buffer) const
Data->Accept(writerObj.GetWriter());
}
const String& JsonAssetBase::GetPath() const
StringView JsonAssetBase::GetPath() const
{
#if USE_EDITOR
return _path;
@@ -176,7 +180,7 @@ bool JsonAssetBase::Save(const StringView& path)
_isResaving = false;
// Save json to file
if (File::WriteAllBytes(path.HasChars() ? path : StringView(GetPath()), (byte*)buffer.GetString(), (int32)buffer.GetSize()))
if (File::WriteAllBytes(path.HasChars() ? path : GetPath(), (byte*)buffer.GetString(), (int32)buffer.GetSize()))
{
LOG(Error, "Cannot save \'{0}\'", ToString());
return true;
@@ -239,6 +243,7 @@ Asset::LoadResult JsonAssetBase::loadAsset()
{
if (IsVirtual() || _isVirtualDocument)
return LoadResult::Ok;
PROFILE_MEM(ContentAssets);
// Load data (raw json file in editor, cooked asset in build game)
#if USE_EDITOR
@@ -305,6 +310,7 @@ Asset::LoadResult JsonAssetBase::loadAsset()
void JsonAssetBase::unload(bool isReloading)
{
PROFILE_MEM(ContentAssets);
ISerializable::SerializeDocument tmp;
Document.Swap(tmp);
Data = nullptr;
@@ -453,6 +459,7 @@ bool JsonAsset::CreateInstance()
ScopeLock lock(Locker);
if (Instance)
return false;
PROFILE_MEM(ContentAssets);
// Try to scripting type for this data
const StringAsANSI<> dataTypeNameAnsi(DataTypeName.Get(), DataTypeName.Length());
+1 -1
View File
@@ -88,7 +88,7 @@ protected:
public:
// [Asset]
const String& GetPath() const override;
StringView GetPath() const override;
uint64 GetMemoryUsage() const override;
#if USE_EDITOR
void GetReferences(Array<Guid, HeapAllocation>& assets, Array<String, HeapAllocation>& files) const override;
@@ -19,6 +19,15 @@ API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference
OnSet(asset);
}
explicit JsonAssetReference(decltype(__nullptr))
{
}
explicit JsonAssetReference(IAssetReference* owner)
: AssetReference<JsonAsset>(owner)
{
}
/// <summary>
/// Gets the deserialized native object instance of the given type. Returns null if asset is not loaded or loaded object has different type.
/// </summary>
@@ -36,7 +36,7 @@ public:
// [ContentLoadTask]
String ToString() const override
{
return String::Format(TEXT("Load Asset Data Task ({}, {}, {})"), (int32)GetState(), _chunks, _asset ? _asset->GetPath() : String::Empty);
return String::Format(TEXT("Load Asset Data Task ({}, {}, {})"), (int32)GetState(), _chunks, _asset ? _asset->GetPath() : StringView::Empty);
}
bool HasReference(Object* obj) const override
{
@@ -8,6 +8,7 @@
#include "Engine/Content/WeakAssetReference.h"
#include "Engine/Core/Log.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
/// <summary>
/// Asset loading task object.
@@ -44,6 +45,7 @@ protected:
Result run() override
{
PROFILE_CPU();
PROFILE_MEM(ContentAssets);
// Keep valid ref to the asset
AssetReference<::Asset> ref = Asset.Get();
+14 -2
View File
@@ -7,7 +7,7 @@
/// <summary>
/// The asset soft reference. Asset gets referenced (loaded) on actual use (ID reference is resolving it).
/// </summary>
class FLAXENGINE_API SoftAssetReferenceBase
class FLAXENGINE_API SoftAssetReferenceBase : public IAssetReference
{
protected:
Asset* _asset = nullptr;
@@ -46,11 +46,16 @@ public:
/// </summary>
String ToString() const;
public:
// [IAssetReference]
void OnAssetChanged(Asset* asset, void* caller) override;
void OnAssetLoaded(Asset* asset, void* caller) override;
void OnAssetUnloaded(Asset* asset, void* caller) override;
protected:
void OnSet(Asset* asset);
void OnSet(const Guid& id);
void OnResolve(const ScriptingTypeHandle& type);
void OnUnloaded(Asset* asset);
};
/// <summary>
@@ -71,6 +76,13 @@ public:
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SoftAssetReference"/> class.
/// </summary>
explicit SoftAssetReference(decltype(__nullptr))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SoftAssetReference"/> class.
/// </summary>
@@ -15,14 +15,9 @@
namespace
{
CriticalSection Locker;
#if USE_EDITOR
Array<FlaxFile*> Files(1024);
Array<FlaxPackage*> Packages;
#else
Array<FlaxFile*> Files;
Array<FlaxPackage*> Packages(64);
#endif
Dictionary<String, FlaxStorage*> StorageMap(2048);
Array<FlaxPackage*> Packages;
Dictionary<String, FlaxStorage*> StorageMap;
}
class ContentStorageService : public EngineService
@@ -231,6 +226,12 @@ void ContentStorageManager::GetStorage(Array<FlaxStorage*>& result)
bool ContentStorageService::Init()
{
#if USE_EDITOR
Files.EnsureCapacity(1024);
#else
Packages.EnsureCapacity(64);
#endif
StorageMap.EnsureCapacity(2048);
System = New<ContentStorageSystem>();
Engine::UpdateGraph->AddSystem(System);
return false;
+7 -8
View File
@@ -87,6 +87,10 @@ public:
/// </summary>
double LastAccessTime = 0.0;
/// <summary>
/// Flag set to indicate that chunk is during loading (atomic access to sync multiple reading threads).
/// </summary>
int64 IsLoading = 0;
/// <summary>
/// The chunk data.
/// </summary>
@@ -146,7 +150,7 @@ public:
/// </summary>
FORCE_INLINE bool IsLoaded() const
{
return Data.IsValid();
return Data.IsValid() && Platform::AtomicRead(&IsLoading) == 0;
}
/// <summary>
@@ -154,7 +158,7 @@ public:
/// </summary>
FORCE_INLINE bool IsMissing() const
{
return Data.IsInvalid();
return !IsLoaded();
}
/// <summary>
@@ -182,10 +186,5 @@ public:
/// Clones this chunk data (doesn't copy location in file).
/// </summary>
/// <returns>The cloned chunk.</returns>
FlaxChunk* Clone() const
{
auto chunk = New<FlaxChunk>();
chunk->Data.Copy(Data);
return chunk;
}
FlaxChunk* Clone() const;
};
+56 -18
View File
@@ -5,9 +5,11 @@
#include "FlaxPackage.h"
#include "ContentStorageManager.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/ScopeExit.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Platform/File.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Serialization/FileWriteStream.h"
#include "Engine/Content/Asset.h"
#include "Engine/Content/Content.h"
@@ -63,9 +65,15 @@ void FlaxChunk::RegisterUsage()
LastAccessTime = Platform::GetTimeSeconds();
}
const int32 FlaxStorage::MagicCode = 1180124739;
FlaxChunk* FlaxChunk::Clone() const
{
PROFILE_MEM(ContentFiles);
auto chunk = New<FlaxChunk>();
chunk->Data.Copy(Data);
return chunk;
}
FlaxStorage::LockData FlaxStorage::LockData::Invalid(nullptr);
const int32 FlaxStorage::MagicCode = 1180124739;
struct Header
{
@@ -237,6 +245,7 @@ FlaxStorage::~FlaxStorage()
ASSERT(IsDisposed());
CHECK(_chunksLock == 0);
CHECK(_refCount == 0);
CHECK(_isUnloadingData == 0);
ASSERT(_chunks.IsEmpty());
#if USE_EDITOR
@@ -252,6 +261,22 @@ FlaxStorage::~FlaxStorage()
#endif
}
void FlaxStorage::LockChunks()
{
RETRY:
Platform::InterlockedIncrement(&_chunksLock);
if (Platform::AtomicRead(&_isUnloadingData) != 0)
{
// Someone else is closing file handles or freeing chunks so wait for it to finish and retry
Platform::InterlockedDecrement(&_chunksLock);
do
{
Platform::Sleep(1);
} while (Platform::AtomicRead(&_isUnloadingData) != 0);
goto RETRY;
}
}
FlaxStorage::LockData FlaxStorage::LockSafe()
{
auto lock = LockData(this);
@@ -281,19 +306,12 @@ uint32 FlaxStorage::GetMemoryUsage() const
bool FlaxStorage::Load()
{
// Check if was already loaded
if (IsLoaded())
{
return false;
}
// Prevent loading by more than one thread
PROFILE_MEM(ContentFiles);
ScopeLock lock(_loadLocker);
if (IsLoaded())
{
// Other thread loaded it
return false;
}
ASSERT(GetEntriesCount() == 0);
// Open file
@@ -687,16 +705,19 @@ bool FlaxStorage::LoadAssetHeader(const Guid& id, AssetInitData& data)
return true;
}
// Load header
return LoadAssetHeader(e, data);
}
bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk)
{
PROFILE_MEM(ContentFiles);
ASSERT(IsLoaded());
ASSERT(chunk != nullptr && _chunks.Contains(chunk));
// Check if already loaded
// Protect against loading the same chunk from multiple threads at once
while (Platform::InterlockedCompareExchange(&chunk->IsLoading, 1, 0) != 0)
Platform::Sleep(1);
SCOPE_EXIT{ Platform::AtomicStore(&chunk->IsLoading, 0); };
if (chunk->IsLoaded())
return false;
@@ -773,12 +794,10 @@ bool FlaxStorage::LoadAssetChunk(FlaxChunk* chunk)
// Raw data
chunk->Data.Read(stream, size);
}
ASSERT(chunk->IsLoaded());
chunk->RegisterUsage();
}
UnlockChunks();
return failed;
}
@@ -866,6 +885,7 @@ FlaxChunk* FlaxStorage::AllocateChunk()
{
if (AllowDataModifications())
{
PROFILE_MEM(ContentFiles);
auto chunk = New<FlaxChunk>();
_chunks.Add(chunk);
return chunk;
@@ -1125,6 +1145,7 @@ bool FlaxStorage::Save(const AssetInitData& data, bool silentMode)
bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data)
{
PROFILE_MEM(ContentFiles);
ASSERT(IsLoaded());
auto lock = Lock();
@@ -1396,6 +1417,8 @@ FileReadStream* FlaxStorage::OpenFile()
auto& stream = _file.Get();
if (stream == nullptr)
{
PROFILE_MEM(ContentFiles);
// Open file
auto file = File::Open(_path, FileMode::OpenExisting, FileAccess::Read, FileShare::Read);
if (file == nullptr)
@@ -1413,11 +1436,14 @@ FileReadStream* FlaxStorage::OpenFile()
bool FlaxStorage::CloseFileHandles()
{
// Guard the whole process so if new thread wants to lock the chunks will need to wait for this to end
Platform::InterlockedIncrement(&_isUnloadingData);
SCOPE_EXIT{ Platform::InterlockedDecrement(&_isUnloadingData); };
if (Platform::AtomicRead(&_chunksLock) == 0 && Platform::AtomicRead(&_files) == 0)
{
return false;
}
return false; // Early out when no files are opened
PROFILE_CPU();
PROFILE_MEM(ContentFiles);
// Note: this is usually called by the content manager when this file is not used or on exit
// In those situations all the async tasks using this storage should be cancelled externally
@@ -1488,9 +1514,21 @@ void FlaxStorage::Tick(double time)
{
auto chunk = _chunks.Get()[i];
const bool wasUsed = (time - chunk->LastAccessTime) < unusedDataChunksLifetime;
if (!wasUsed && chunk->IsLoaded() && EnumHasNoneFlags(chunk->Flags, FlaxChunkFlags::KeepInMemory))
if (!wasUsed &&
chunk->IsLoaded() &&
EnumHasNoneFlags(chunk->Flags, FlaxChunkFlags::KeepInMemory) &&
Platform::AtomicRead(&chunk->IsLoading) == 0)
{
// Guard the unloading so if other thread wants to lock the chunks will need to wait for this to end
Platform::InterlockedIncrement(&_isUnloadingData);
if (Platform::AtomicRead(&_chunksLock) != 0 || Platform::AtomicRead(&chunk->IsLoading) != 0)
{
// Someone started loading so skip ticking
Platform::InterlockedDecrement(&_isUnloadingData);
return;
}
chunk->Unload();
Platform::InterlockedDecrement(&_isUnloadingData);
}
wasAnyUsed |= wasUsed;
}
+7 -5
View File
@@ -90,6 +90,7 @@ protected:
int64 _refCount = 0;
int64 _chunksLock = 0;
int64 _files = 0;
int64 _isUnloadingData = 0;
double _lastRefLostTime;
CriticalSection _loadLocker;
@@ -129,10 +130,7 @@ public:
/// <summary>
/// Locks the storage chunks data to prevent disposing them. Also ensures that file handles won't be closed while chunks are locked.
/// </summary>
FORCE_INLINE void LockChunks()
{
Platform::InterlockedIncrement(&_chunksLock);
}
void LockChunks();
/// <summary>
/// Unlocks the storage chunks data.
@@ -148,7 +146,6 @@ public:
struct LockData
{
friend FlaxStorage;
static LockData Invalid;
private:
FlaxStorage* _storage;
@@ -161,6 +158,11 @@ public:
}
public:
LockData()
: _storage(nullptr)
{
}
LockData(const LockData& other)
: _storage(other._storage)
{
@@ -13,6 +13,11 @@ private:
FlaxStorage* _storage;
public:
FlaxStorageReference()
: _storage(nullptr)
{
}
FlaxStorageReference(FlaxStorage* storage)
: _storage(storage)
{
+14 -4
View File
@@ -7,7 +7,7 @@
/// <summary>
/// Asset reference utility that doesn't add reference to that asset. Handles asset unload event.
/// </summary>
API_CLASS(InBuild) class WeakAssetReferenceBase
API_CLASS(InBuild) class WeakAssetReferenceBase : public IAssetReference
{
public:
typedef Delegate<> EventType;
@@ -56,9 +56,14 @@ public:
/// </summary>
String ToString() const;
public:
// [IAssetReference]
void OnAssetChanged(Asset* asset, void* caller) override;
void OnAssetLoaded(Asset* asset, void* caller) override;
void OnAssetUnloaded(Asset* asset, void* caller) override;
protected:
void OnSet(Asset* asset);
void OnUnloaded(Asset* asset);
};
/// <summary>
@@ -72,7 +77,13 @@ public:
/// Initializes a new instance of the <see cref="WeakAssetReference"/> class.
/// </summary>
WeakAssetReference()
: WeakAssetReferenceBase()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="WeakAssetReference"/> class.
/// </summary>
explicit WeakAssetReference(decltype(__nullptr))
{
}
@@ -81,7 +92,6 @@ public:
/// </summary>
/// <param name="asset">The asset to set.</param>
WeakAssetReference(T* asset)
: WeakAssetReferenceBase()
{
OnSet(asset);
}
@@ -13,6 +13,7 @@
#include "Engine/Engine/EngineService.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/Platform.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Engine/Globals.h"
#include "ImportTexture.h"
#include "ImportModel.h"
@@ -151,6 +152,7 @@ bool CreateAssetContext::AllocateChunk(int32 index)
}
// Create new chunk
PROFILE_MEM(ContentFiles);
Data.Header.Chunks[index] = New<FlaxChunk>();
return false;
}
@@ -93,14 +93,15 @@ namespace
};
template<typename T>
void AddInput(MaterialLayer* layer, Meta11 meta, MaterialGraphBoxes box, const Guid& texture, const T& value, const T& defaultValue, const Float2& pos, ShaderGraphNode<>** outTextureNode = nullptr)
void AddInput(MaterialLayer* layer, Meta11 meta, MaterialGraphBoxes box, const Guid& texture, const T& value, const T& defaultValue, const Float2& pos, ShaderGraphNode<>** outTextureNode = nullptr, uint8 channel = MAX_uint8)
{
auto textureNode = AddTextureNode(layer, texture);
auto valueNode = AddValueNode<T>(layer, value, defaultValue);
auto textureNodeBox = channel == MAX_uint8 ? 1 : channel + 2; // Color or specific channel (RGBA)
if (textureNode && valueNode)
{
auto diffuseMultiply = AddMultiplyNode(layer);
CONNECT(diffuseMultiply->Boxes[0], textureNode->Boxes[1]);
CONNECT(diffuseMultiply->Boxes[0], textureNode->Boxes[textureNodeBox]);
CONNECT(diffuseMultiply->Boxes[1], valueNode->Boxes[0]);
CONNECT(layer->Root->Boxes[static_cast<int32>(box)], diffuseMultiply->Boxes[2]);
SET_POS(valueNode, pos + Float2(-467.7404, 91.41332));
@@ -109,7 +110,7 @@ namespace
}
else if (textureNode)
{
CONNECT(layer->Root->Boxes[static_cast<int32>(box)], textureNode->Boxes[1]);
CONNECT(layer->Root->Boxes[static_cast<int32>(box)], textureNode->Boxes[textureNodeBox]);
SET_POS(textureNode, pos + Float2(-293.5272f, -2.926111f));
}
else if (valueNode)
@@ -178,8 +179,9 @@ CreateAssetResult CreateMaterial::Create(CreateAssetContext& context)
// Opacity
AddInput(layer, meta, MaterialGraphBoxes::Opacity, options.Opacity.Texture, options.Opacity.Value, 1.0f, Float2(0, 400));
// Opacity
AddInput(layer, meta, MaterialGraphBoxes::Roughness, options.Roughness.Texture, options.Roughness.Value, 0.5f, Float2(200, 400));
// Roughness + Metalness
AddInput(layer, meta, MaterialGraphBoxes::Roughness, options.Roughness.Texture, options.Roughness.Value, 0.5f, Float2(200, 400), nullptr, options.Roughness.Channel);
AddInput(layer, meta, MaterialGraphBoxes::Metalness, options.Metalness.Texture, options.Metalness.Value, 0.0f, Float2(200, 600), nullptr, options.Metalness.Channel);
// Normal
auto normalMap = AddTextureNode(layer, options.Normals.Texture, true);
@@ -40,9 +40,17 @@ public:
struct
{
float Value = 0.5f;
uint8 Channel = 0;
Guid Texture = Guid::Empty;
} Roughness;
struct
{
float Value = 0.0f;
uint8 Channel = 0;
Guid Texture = Guid::Empty;
} Metalness;
struct
{
Guid Texture = Guid::Empty;
@@ -26,6 +26,7 @@
#include "Engine/Utilities/RectPack.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Editor/Utilities/EditorUtilities.h"
#include "AssetsImportingManager.h"
bool ImportModel::TryGetImportOptions(const StringView& path, Options& options)
@@ -281,13 +282,19 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
// Import all of the objects recursive but use current model data to skip loading file again
options.Cached = &cached;
Function<bool(Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData)> splitImport = [&context, &autoImportOutput](Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData)
HashSet<String> objectNames;
Function<bool(Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData)> splitImport = [&context, &autoImportOutput, &objectNames](Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData)
{
// Recursive importing of the split object
String postFix = objectName;
const int32 splitPos = postFix.FindLast(TEXT('|'));
if (splitPos != -1)
if (splitPos != -1 && splitPos + 1 < postFix.Length())
postFix = postFix.Substring(splitPos + 1);
EditorUtilities::ValidatePathChars(postFix); // Ensure name is valid path
int32 duplicate = 0;
String postFixPre = postFix;
while (!objectNames.Add(postFix)) // Ensure name is unique
postFix = String::Format(TEXT("{} {}"), postFixPre, duplicate++);
// TODO: check for name collisions with material/texture assets
outputPath = autoImportOutput / String(StringUtils::GetFileNameWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax");
splitOptions.SubAssetFolder = TEXT(" "); // Use the same folder as asset as they all are imported to the subdir for the prefab (see SubAssetFolder usage above)
+1 -1
View File
@@ -3,7 +3,7 @@
#include "Cache.h"
#include "FlaxEngine.Gen.h"
CollectionPoolCache<ISerializeModifier, Cache::ISerializeModifierClearCallback> Cache::ISerializeModifier;
Cache::ISerializeModifierCache Cache::ISerializeModifier;
void Cache::ISerializeModifierClearCallback(::ISerializeModifier* obj)
{
+2 -1
View File
@@ -15,11 +15,12 @@ public:
static void ISerializeModifierClearCallback(ISerializeModifier* obj);
public:
typedef CollectionPoolCache<ISerializeModifier, ISerializeModifierClearCallback> ISerializeModifierCache;
/// <summary>
/// Gets the ISerializeModifier lookup cache. Safe allocation, per thread, uses caching.
/// </summary>
static CollectionPoolCache<ISerializeModifier, ISerializeModifierClearCallback> ISerializeModifier;
static ISerializeModifierCache ISerializeModifier;
public:
+14 -5
View File
@@ -20,6 +20,7 @@ API_CLASS(InBuild) class Array
public:
using ItemType = T;
using AllocationData = typename AllocationType::template Data<T>;
using AllocationTag = typename AllocationType::Tag;
private:
int32 _count;
@@ -36,6 +37,17 @@ public:
{
}
/// <summary>
/// Initializes an empty <see cref="Array"/> without reserving any space.
/// </summary>
/// <param name="tag">The custom allocation tag.</param>
Array(AllocationTag tag)
: _count(0)
, _capacity(0)
, _allocation(tag)
{
}
/// <summary>
/// Initializes <see cref="Array"/> by reserving space.
/// </summary>
@@ -647,11 +659,8 @@ public:
T* data = _allocation.Get();
if (index < _count)
{
T* dst = data + index;
T* src = data + (index + 1);
const int32 count = _count - index;
for (int32 i = 0; i < count; ++i)
dst[i] = MoveTemp(src[i]);
for (int32 i = index; i < _count; i++)
data[i] = MoveTemp(data[i + 1]);
}
Memory::DestructItems(data + _count, 1);
}
+11 -2
View File
@@ -163,6 +163,15 @@ public:
{
}
/// <summary>
/// Initializes an empty <see cref="Dictionary"/> without reserving any space.
/// </summary>
/// <param name="tag">The custom allocation tag.</param>
Dictionary(typename Base::AllocationTag tag)
: Base(tag)
{
}
/// <summary>
/// Initializes <see cref="Dictionary"/> by reserving space.
/// </summary>
@@ -549,7 +558,7 @@ public:
/// Removes element with a specified key.
/// </summary>
/// <param name="key">The element key to remove.</param>
/// <returns>True if cannot remove item from the collection because cannot find it, otherwise false.</returns>
/// <returns>True if item was removed from collection, otherwise false.</returns>
template<typename KeyComparableType>
bool Remove(const KeyComparableType& key)
{
@@ -569,7 +578,7 @@ public:
/// Removes element at specified iterator.
/// </summary>
/// <param name="i">The element iterator to remove.</param>
/// <returns>True if cannot remove item from the collection because cannot find it, otherwise false.</returns>
/// <returns>True if item was removed from collection, otherwise false.</returns>
bool Remove(const Iterator& i)
{
ASSERT(i._collection == this);
+11 -2
View File
@@ -140,6 +140,15 @@ public:
{
}
/// <summary>
/// Initializes an empty <see cref="HashSet"/> without reserving any space.
/// </summary>
/// <param name="tag">The custom allocation tag.</param>
HashSet(typename Base::AllocationTag tag)
: Base(tag)
{
}
/// <summary>
/// Initializes <see cref="HashSet"/> by reserving space.
/// </summary>
@@ -442,7 +451,7 @@ public:
/// Removes the specified element from the collection.
/// </summary>
/// <param name="item">The element to remove.</param>
/// <returns>True if cannot remove item from the collection because cannot find it, otherwise false.</returns>
/// <returns>True if item was removed from collection, otherwise false.</returns>
template<typename ItemType>
bool Remove(const ItemType& item)
{
@@ -462,7 +471,7 @@ public:
/// Removes an element at specified iterator position.
/// </summary>
/// <param name="i">The element iterator to remove.</param>
/// <returns>True if cannot remove item from the collection because cannot find it, otherwise false.</returns>
/// <returns>True if item was removed from collection, otherwise false.</returns>
bool Remove(const Iterator& i)
{
ASSERT(i._collection == this);
@@ -59,6 +59,7 @@ class HashSetBase
public:
// Type of allocation data used to store hash set buckets.
using AllocationData = typename AllocationType::template Data<BucketType>;
using AllocationTag = typename AllocationType::Tag;
protected:
int32 _elementsCount = 0;
@@ -70,6 +71,11 @@ protected:
{
}
HashSetBase(AllocationTag tag)
: _allocation(tag)
{
}
void MoveToEmpty(HashSetBase&& other)
{
_elementsCount = other._elementsCount;
+5 -6
View File
@@ -362,9 +362,10 @@ public:
uint32 histogram[RADIXSORT_HISTOGRAM_SIZE];
uint16 shift = 0;
int32 pass = 0;
for (; pass < 6; pass++)
constexpr int32 passCount = sizeof(T) >= sizeof(uint64) ? 6 : 3;
for (; pass < passCount; pass++)
{
Platform::MemoryClear(histogram, sizeof(uint32) * RADIXSORT_HISTOGRAM_SIZE);
Platform::MemoryClear(histogram, sizeof(histogram));
bool sorted = true;
T key = keys[0];
@@ -372,16 +373,14 @@ public:
for (int32 i = 0; i < count; i++)
{
key = keys[i];
const uint16 index = (key >> shift) & RADIXSORT_BIT_MASK;
const uint16 index = (key >> (T)shift) & RADIXSORT_BIT_MASK;
++histogram[index];
sorted &= prevKey <= key;
prevKey = key;
}
if (sorted)
{
goto end;
}
uint32 offset = 0;
for (int32 i = 0; i < RADIXSORT_HISTOGRAM_SIZE; ++i)
@@ -394,7 +393,7 @@ public:
for (int32 i = 0; i < count; i++)
{
const T k = keys[i];
const uint16 index = (k >> shift) & RADIXSORT_BIT_MASK;
const uint16 index = (k >> (T)shift) & RADIXSORT_BIT_MASK;
const uint32 dest = histogram[index]++;
tempKeys[dest] = k;
tempValues[dest] = values[i];
+4
View File
@@ -30,13 +30,17 @@
#endif
// Enable logging service (saving log to file, can be disabled using -nolog command line)
#ifndef LOG_ENABLE
#define LOG_ENABLE 1
#endif
// Enable crash reporting service (stack trace and crash dump collecting)
#define CRASH_LOG_ENABLE (!BUILD_RELEASE)
// Enable/disable assertion
#ifndef ENABLE_ASSERTION
#define ENABLE_ASSERTION (!BUILD_RELEASE)
#endif
// Enable/disable assertion for Engine low layers
#define ENABLE_ASSERTION_LOW_LAYERS ENABLE_ASSERTION && (BUILD_DEBUG || FLAX_TESTS)
@@ -89,6 +89,12 @@ public:
API_FIELD(Attributes="EditorOrder(2100), EditorDisplay(\"Content\")")
bool SkipDefaultFonts = false;
/// <summary>
/// The maximum acceptable mesh vertex position error (in world units) for data quantization. Use 0 to disable this feature. Affects meshes during import (or reimpport).
/// </summary>
API_FIELD(Attributes="EditorOrder(2200), EditorDisplay(\"Content\"), ValueCategory(Utils.ValueCategory.Distance)")
float MaxMeshPositionError = 0.5f;
/// <summary>
/// If checked, .NET Runtime won't be packaged with a game and will be required by user to be installed on system upon running game build. Available only on supported platforms such as Windows, Linux and macOS.
/// </summary>
@@ -221,6 +221,7 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo
ProductName = JsonTools::GetString(stream, "ProductName");
CompanyName = JsonTools::GetString(stream, "CompanyName");
CopyrightNotice = JsonTools::GetString(stream, "CopyrightNotice");
Version = JsonTools::GetString(stream, "Version");
Icon = JsonTools::GetGuid(stream, "Icon");
FirstScene = JsonTools::GetGuid(stream, "FirstScene");
NoSplashScreen = JsonTools::GetBool(stream, "NoSplashScreen", NoSplashScreen);
+6
View File
@@ -33,6 +33,12 @@ public:
API_FIELD(Attributes="EditorOrder(15), EditorDisplay(\"General\")")
String CopyrightNotice;
/// <summary>
/// The game version number. Usually in format: MAJOR.MINOR.BUILD.REVISION
/// </summary>
API_FIELD(Attributes="EditorOrder(20), EditorDisplay(\"General\")")
String Version;
/// <summary>
/// The default application icon.
/// </summary>
+49 -4
View File
@@ -12,6 +12,9 @@
#include "Engine/Threading/Threading.h"
#include "Engine/Core/Collections/HashSet.h"
#endif
#if COMPILE_WITH_PROFILER
#include "Engine/Profiler/ProfilerMemory.h"
#endif
/// <summary>
/// The function object that supports binding static, member and lambda functions.
@@ -59,7 +62,7 @@ private:
struct Lambda
{
int64 Refs;
void (*Dtor)(void*);
void (*Dtor)(void* callee, Lambda* lambda);
};
void* _callee;
@@ -75,8 +78,7 @@ private:
{
if (Platform::InterlockedDecrement(&_lambda->Refs) == 0)
{
((Lambda*)_lambda)->Dtor(_callee);
Allocator::Free(_lambda);
_lambda->Dtor(_callee, _lambda);
}
}
@@ -186,9 +188,10 @@ public:
LambdaDtor();
_lambda = (Lambda*)Allocator::Allocate(sizeof(Lambda) + sizeof(T));
_lambda->Refs = 1;
_lambda->Dtor = [](void* callee) -> void
_lambda->Dtor = [](void* callee, Lambda* lambda) -> void
{
static_cast<T*>(callee)->~T();
Allocator::Free(lambda);
};
_function = [](void* callee, Params... params) -> ReturnType
{
@@ -198,6 +201,45 @@ public:
new(_callee) T(lambda);
}
/// <summary>
/// Binds a lambda with a custom memory allocator.
/// </summary>
/// <param name="tag">The custom allocation tag.</param>
/// <param name="lambda">The lambda.</param>
template<typename AllocationType, typename T>
void Bind(typename AllocationType::Tag tag, const T& lambda)
{
if (_lambda)
LambdaDtor();
using AllocationData = typename AllocationType::template Data<byte>;
static_assert(AllocationType::HasSwap, "Function lambda binding supports only custom allocators with swap operation.");
// Allocate lambda (using temp data)
AllocationData tempAlloc(tag);
tempAlloc.Allocate(sizeof(Lambda) + sizeof(AllocationData) + sizeof(T));
// Move temp data into the one allocated
AllocationData* dataAlloc = (AllocationData*)(tempAlloc.Get() + sizeof(Lambda));
new(dataAlloc) AllocationData();
dataAlloc->Swap(tempAlloc);
// Initialize lambda
_lambda = (Lambda*)dataAlloc->Get();
_lambda->Refs = 1;
_lambda->Dtor = [](void* callee, Lambda* lambda) -> void
{
static_cast<T*>(callee)->~T();
AllocationData* dataAlloc = (AllocationData*)((byte*)lambda + sizeof(Lambda));
dataAlloc->Free();
};
_function = [](void* callee, Params... params) -> ReturnType
{
return (*static_cast<T*>(callee))(Forward<Params>(params)...);
};
_callee = (byte*)_lambda + sizeof(AllocationData) + sizeof(Lambda);
new(_callee) T(lambda);
}
/// <summary>
/// Unbinds the function.
/// </summary>
@@ -457,6 +499,9 @@ public:
/// <param name="f">The function to bind.</param>
void Bind(const FunctionType& f)
{
#if COMPILE_WITH_PROFILER
PROFILE_MEM(EngineDelegate);
#endif
#if DELEGATE_USE_ATOMIC
const intptr size = Platform::AtomicRead(&_size);
FunctionType* bindings = (FunctionType*)Platform::AtomicRead(&_ptr);
+7
View File
@@ -1,6 +1,7 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#include "Log.h"
#if LOG_ENABLE
#include "Engine/Engine/CommandLine.h"
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Collections/Array.h"
@@ -8,6 +9,7 @@
#include "Engine/Engine/Globals.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/CriticalSection.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Serialization/FileWriteStream.h"
#include "Engine/Debug/Exceptions/Exceptions.h"
#if USE_EDITOR
@@ -42,6 +44,7 @@ bool Log::Logger::Init()
// Skip if disabled
if (!IsLogEnabled())
return false;
PROFILE_MEM(Engine);
// Create logs directory (if is missing)
#if USE_EDITOR
@@ -119,6 +122,7 @@ void Log::Logger::Write(const StringView& msg)
const auto length = msg.Length();
if (length <= 0)
return;
PROFILE_MEM(Engine);
LogLocker.Lock();
if (IsDuringLog)
@@ -258,6 +262,7 @@ void Log::Logger::Write(LogType type, const StringView& msg)
{
if (msg.Length() <= 0)
return;
PROFILE_MEM(Engine);
const bool isError = IsError(type);
// Create message for the log file
@@ -306,3 +311,5 @@ const Char* ToString(LogType e)
}
return result;
}
#endif
+34 -21
View File
@@ -7,27 +7,6 @@
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Types/StringView.h"
// Enable/disable auto flush function
#define LOG_ENABLE_AUTO_FLUSH 1
/// <summary>
/// Sends a formatted message to the log file (message type - describes level of the log (see LogType enum))
/// </summary>
#define LOG(messageType, format, ...) Log::Logger::Write(LogType::messageType, ::String::Format(TEXT(format), ##__VA_ARGS__))
/// <summary>
/// Sends a string message to the log file (message type - describes level of the log (see LogType enum))
/// </summary>
#define LOG_STR(messageType, str) Log::Logger::Write(LogType::messageType, str)
#if LOG_ENABLE_AUTO_FLUSH
// Noop as log is auto-flushed on write
#define LOG_FLUSH()
#else
// Flushes the log file buffer
#define LOG_FLUSH() Log::Logger::Flush()
#endif
/// <summary>
/// The log message types.
/// </summary>
@@ -54,6 +33,31 @@ API_ENUM() enum class LogType
Fatal = 8,
};
#if LOG_ENABLE
// Enable/disable auto flush function
#define LOG_ENABLE_AUTO_FLUSH 1
/// <summary>
/// Sends a formatted message to the log file (message type - describes level of the log (see LogType enum))
/// </summary>
#define LOG(messageType, format, ...) Log::Logger::Write(LogType::messageType, ::String::Format(TEXT(format), ##__VA_ARGS__))
/// <summary>
/// Sends a string message to the log file (message type - describes level of the log (see LogType enum))
/// </summary>
#define LOG_STR(messageType, str) Log::Logger::Write(LogType::messageType, str)
#if LOG_ENABLE_AUTO_FLUSH
// Noop as log is auto-flushed on write
#define LOG_FLUSH()
#else
// Flushes the log file buffer
#define LOG_FLUSH() Log::Logger::Flush()
#endif
#define LOG_FLOOR() Log::Logger::WriteFloor()
extern const Char* ToString(LogType e);
namespace Log
@@ -186,3 +190,12 @@ namespace Log
static void ProcessLogMessage(LogType type, const StringView& msg, fmt_flax::memory_buffer& w);
};
}
#else
#define LOG(messageType, format, ...) {}
#define LOG_STR(messageType, str) {}
#define LOG_FLUSH() {}
#define LOG_FLOOR() {}
#endif
+2
View File
@@ -47,6 +47,7 @@ ThreadLocal<LogContextThreadData> GlobalLogContexts;
void LogContext::Print(LogType verbosity)
{
#if LOG_ENABLE
auto& stack = GlobalLogContexts.Get();
if (stack.Count == 0)
return;
@@ -102,6 +103,7 @@ void LogContext::Print(LogType verbosity)
// Print message
Log::Logger::Write(verbosity, msg.ToStringView());
}
#endif
}
void LogContext::Push(const Guid& id)
+10 -3
View File
@@ -63,9 +63,16 @@ void BoundingFrustum::SetMatrix(const Matrix& matrix)
Plane BoundingFrustum::GetPlane(int32 index) const
{
if (index > 5)
return Plane();
return _planes[index];
switch (index)
{
case 0: return _pLeft;
case 1: return _pRight;
case 2: return _pTop;
case 3: return _pBottom;
case 4: return _pNear;
case 5: return _pFar;
default: return Plane();
}
}
static Vector3 Get3PlanesInterPoint(const Plane& p1, const Plane& p2, const Plane& p3)
+1 -1
View File
@@ -182,7 +182,7 @@ namespace FlaxEngine
/// <summary>
/// Returns one of the 6 planes related to this frustum.
/// </summary>
/// <param name="index">Plane index where 0 fro Left, 1 for Right, 2 for Top, 3 for Bottom, 4 for Near, 5 for Far</param>
/// <param name="index">Plane index where 0 for Left, 1 for Right, 2 for Top, 3 for Bottom, 4 for Near, 5 for Far</param>
/// <returns>The frustum plane.</returns>
public Plane GetPlane(int index)
{
+2 -2
View File
@@ -148,13 +148,13 @@ public:
Plane GetPlane(int32 index) const;
/// <summary>
/// Gets the the 8 corners of the frustum: Near1 (near right down corner), Near2 (near right top corner), Near3 (near Left top corner), Near4 (near Left down corner), Far1 (far right down corner), Far2 (far right top corner), Far3 (far left top corner), Far4 (far left down corner).
/// Gets the 8 corners of the frustum: Near1 (near right down corner), Near2 (near right top corner), Near3 (near Left top corner), Near4 (near Left down corner), Far1 (far right down corner), Far2 (far right top corner), Far3 (far left top corner), Far4 (far left down corner).
/// </summary>
/// <param name="corners">The corners.</param>
void GetCorners(Float3 corners[8]) const;
/// <summary>
/// Gets the the 8 corners of the frustum: Near1 (near right down corner), Near2 (near right top corner), Near3 (near Left top corner), Near4 (near Left down corner), Far1 (far right down corner), Far2 (far right top corner), Far3 (far left top corner), Far4 (far left down corner).
/// Gets the 8 corners of the frustum: Near1 (near right down corner), Near2 (near right top corner), Near3 (near Left top corner), Near4 (near Left down corner), Far1 (far right down corner), Far2 (far right top corner), Far3 (far left top corner), Far4 (far left down corner).
/// </summary>
/// <param name="corners">The corners.</param>
void GetCorners(Double3 corners[8]) const;
+2 -7
View File
@@ -620,14 +620,9 @@ bool Collision::RayIntersectsTriangle(const Ray& ray, const Vector3& a, const Ve
Real rayDistance = edge2.X * distanceCrossEdge1.X + edge2.Y * distanceCrossEdge1.Y + edge2.Z * distanceCrossEdge1.Z;
rayDistance *= inverseDeterminant;
// Check if the triangle is behind the ray origin
if (rayDistance < 0.0f)
{
return false;
}
// Check if the triangle is in front the ray origin
distance = rayDistance;
return true;
return rayDistance >= 0.0f;
}
bool CollisionsHelper::RayIntersectsTriangle(const Ray& ray, const Vector3& vertex1, const Vector3& vertex2, const Vector3& vertex3, Real& distance, Vector3& normal)
-5
View File
@@ -12,11 +12,6 @@ String Ray::ToString() const
return String::Format(TEXT("{}"), *this);
}
Vector3 Ray::GetPoint(Real distance) const
{
return Position + Direction * distance;
}
Ray Ray::GetPickRay(float x, float y, const Viewport& viewport, const Matrix& vp)
{
Vector3 nearPoint(x, y, 0.0f);
+4 -1
View File
@@ -79,7 +79,10 @@ public:
/// </summary>
/// <param name="distance">The distance from ray origin.</param>
/// <returns>The calculated point.</returns>
Vector3 GetPoint(Real distance) const;
FORCE_INLINE Vector3 GetPoint(Real distance) const
{
return Position + Direction * distance;
}
/// <summary>
/// Determines if there is an intersection between ray and a point.
+134
View File
@@ -0,0 +1,134 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#include "ArenaAllocation.h"
#include "../Math/Math.h"
#include "Engine/Profiler/ProfilerMemory.h"
void ArenaAllocator::Free()
{
// Free all pages
Page* page = _first;
while (page)
{
#if COMPILE_WITH_PROFILER
ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, -(int64)page->Size, -1);
#endif
Page* next = page->Next;
Allocator::Free(page);
page = next;
}
// Unlink
_first = nullptr;
}
void* ArenaAllocator::Allocate(uint64 size, uint64 alignment)
{
if (size == 0)
return nullptr;
if (alignment < PLATFORM_MEMORY_ALIGNMENT)
alignment = PLATFORM_MEMORY_ALIGNMENT;
// Find the first page that has some space left
Page* page = _first;
while (page && page->Offset + size + alignment > page->Size)
page = page->Next;
// Create a new page if need to
if (!page)
{
uint64 pageSize = Math::Max<uint64>(_pageSize, size + alignment + sizeof(Page));
#if COMPILE_WITH_PROFILER
ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, (int64)pageSize, 1);
#endif
page = (Page*)Allocator::Allocate(pageSize);
page->Next = _first;
page->Offset = sizeof(Page);
page->Size = (uint32)pageSize;
_first = page;
}
// Allocate within a page
page->Offset = Math::AlignUp(page->Offset, (uint32)alignment);
void* mem = (byte*)page + page->Offset;
page->Offset += (uint32)size;
return mem;
}
void ConcurrentArenaAllocator::Free()
{
_locker.Lock();
// Free all pages
Page* page = (Page*)_first;
while (page)
{
#if COMPILE_WITH_PROFILER
ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, -(int64)page->Size, -1);
#endif
Page* next = page->Next;
if (_free1)
_free1(page);
else
_free2(page, page->Size);
page = next;
}
// Unlink
_first = 0;
_totalBytes = 0;
_locker.Unlock();
}
void* ConcurrentArenaAllocator::Allocate(uint64 size, uint64 alignment)
{
if (size == 0)
return nullptr;
if (alignment < PLATFORM_MEMORY_ALIGNMENT)
alignment = PLATFORM_MEMORY_ALIGNMENT;
RETRY:
// Check if the current page has some space left
Page* page = (Page*)Platform::AtomicRead(&_first);
if (page)
{
int64 offset = Platform::AtomicRead(&page->Offset);
int64 offsetAligned = Math::AlignUp(offset, (int64)alignment);
int64 end = offsetAligned + size;
if (end <= page->Size)
{
// Try to allocate within a page
if (Platform::InterlockedCompareExchange(&page->Offset, end, offset) != offset)
{
// Someone else changed allocated so retry (new offset might mismatch alignment)
goto RETRY;
}
Platform::InterlockedAdd(&_totalBytes, (int64)size);
return (byte*)page + offsetAligned;
}
}
// Page allocation is thread-synced
_locker.Lock();
// Check if page was unchanged by any other thread
if ((Page*)Platform::AtomicRead(&_first) == page)
{
uint64 pageSize = Math::Max<uint64>(_pageSize, size + alignment + sizeof(Page));
#if COMPILE_WITH_PROFILER
ProfilerMemory::OnGroupUpdate(ProfilerMemory::Groups::MallocArena, (int64)pageSize, 1);
#endif
page = (Page*)(_allocate1 ? _allocate1(pageSize, 16) : _allocate2(pageSize));
page->Next = (Page*)_first;
page->Offset = sizeof(Page);
page->Size = (int64)pageSize;
Platform::AtomicStore(&_first, (intptr)page);
}
_locker.Unlock();
// Use a single code for allocation
goto RETRY;
}
+29 -11
View File
@@ -18,9 +18,7 @@ namespace AllocationUtils
capacity |= capacity >> 8;
capacity |= capacity >> 16;
uint64 capacity64 = (uint64)(capacity + 1) * 2;
if (capacity64 > MAX_int32)
capacity64 = MAX_int32;
return (int32)capacity64;
return capacity64 >= MAX_int32 ? MAX_int32 : (int32)capacity64 / 2;
}
// Aligns the input value to the next power of 2 to be used as bigger memory allocation block.
@@ -36,6 +34,17 @@ namespace AllocationUtils
capacity++;
return capacity;
}
inline int32 CalculateCapacityGrow(int32 capacity, int32 minCapacity)
{
if (capacity < minCapacity)
capacity = minCapacity;
if (capacity < 8)
capacity = 8;
else
capacity = RoundUpToPowerOf2(capacity);
return capacity;
}
}
/// <summary>
@@ -46,6 +55,7 @@ class FixedAllocation
{
public:
enum { HasSwap = false };
typedef void* Tag;
template<typename T>
class alignas(sizeof(void*)) Data
@@ -58,6 +68,10 @@ public:
{
}
FORCE_INLINE Data(Tag tag)
{
}
FORCE_INLINE ~Data()
{
}
@@ -106,6 +120,7 @@ class HeapAllocation
{
public:
enum { HasSwap = true };
typedef void* Tag;
template<typename T>
class Data
@@ -118,6 +133,10 @@ public:
{
}
FORCE_INLINE Data(Tag tag)
{
}
FORCE_INLINE ~Data()
{
Allocator::Free(_data);
@@ -135,13 +154,7 @@ public:
FORCE_INLINE int32 CalculateCapacityGrow(int32 capacity, const int32 minCapacity) const
{
if (capacity < minCapacity)
capacity = minCapacity;
if (capacity < 8)
capacity = 8;
else
capacity = AllocationUtils::RoundUpToPowerOf2(capacity);
return capacity;
return AllocationUtils::CalculateCapacityGrow(capacity, minCapacity);
}
FORCE_INLINE void Allocate(const int32 capacity)
@@ -184,6 +197,7 @@ class InlinedAllocation
{
public:
enum { HasSwap = false };
typedef void* Tag;
template<typename T>
class alignas(sizeof(void*)) Data
@@ -192,7 +206,7 @@ public:
typedef typename FallbackAllocation::template Data<T> FallbackData;
bool _useFallback = false;
byte _data[Capacity * sizeof(T)];
alignas(sizeof(void*)) byte _data[Capacity * sizeof(T)];
FallbackData _fallback;
public:
@@ -200,6 +214,10 @@ public:
{
}
FORCE_INLINE Data(Tag tag)
{
}
FORCE_INLINE ~Data()
{
}
+231
View File
@@ -0,0 +1,231 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#pragma once
#include "Allocation.h"
#include "Engine/Platform/CriticalSection.h"
/// <summary>
/// Allocator that uses pages for stack-based allocs without freeing memory during it's lifetime.
/// </summary>
class ArenaAllocator
{
private:
struct Page
{
Page* Next;
uint32 Offset, Size;
};
int32 _pageSize;
Page* _first = nullptr;
public:
ArenaAllocator(int32 pageSizeBytes = 1024 * 1024) // 1 MB by default
: _pageSize(pageSizeBytes)
{
}
~ArenaAllocator()
{
Free();
}
// Allocates a chunk of unitialized memory.
void* Allocate(uint64 size, uint64 alignment = 1);
// Frees all memory allocations within allocator.
void Free();
// Creates a new object within the arena allocator.
template<class T, class... Args>
inline T* New(Args&&...args)
{
T* ptr = (T*)Allocate(sizeof(T));
new(ptr) T(Forward<Args>(args)...);
return ptr;
}
// Invokes destructor on values in an array and clears it.
template<typename Value, typename Allocator>
static void ClearDelete(Array<Value, Allocator>& collection)
{
Value* ptr = collection.Get();
for (int32 i = 0; i < collection.Count(); i++)
Memory::DestructItem(ptr[i]);
collection.Clear();
}
// Invokes destructor on values in a dictionary and clears it.
template<typename Key, typename Value, typename Allocator>
static void ClearDelete(Dictionary<Key, Value, Allocator>& collection)
{
for (auto it = collection.Begin(); it.IsNotEnd(); ++it)
Memory::DestructItem(it->Value);
collection.Clear();
}
};
/// <summary>
/// Allocator that uses pages for stack-based allocs without freeing memory during it's lifetime. Thread-safe to allocate memory from multiple threads at once.
/// </summary>
class ConcurrentArenaAllocator
{
private:
struct Page
{
Page* Next;
volatile int64 Offset;
int64 Size;
};
volatile int64 _first = 0;
volatile int64 _totalBytes = 0;
void*(*_allocate1)(uint64 size, uint64 alignment) = nullptr;
void(*_free1)(void* ptr) = nullptr;
void*(*_allocate2)(uint64 size) = nullptr;
void(*_free2)(void* ptr, uint64 size) = nullptr;
CriticalSection _locker;
int32 _pageSize;
public:
ConcurrentArenaAllocator(int32 pageSizeBytes, void* (*customAllocate)(uint64 size, uint64 alignment), void(*customFree)(void* ptr))
: _allocate1(customAllocate)
, _free1(customFree)
, _pageSize(pageSizeBytes)
{
}
ConcurrentArenaAllocator(int32 pageSizeBytes, void* (*customAllocate)(uint64 size), void(*customFree)(void* ptr, uint64 size))
: _allocate2(customAllocate)
, _free2(customFree)
, _pageSize(pageSizeBytes)
{
}
ConcurrentArenaAllocator(int32 pageSizeBytes = 1024 * 1024) // 1 MB by default
: ConcurrentArenaAllocator(pageSizeBytes, Allocator::Allocate, Allocator::Free)
{
}
~ConcurrentArenaAllocator()
{
Free();
}
// Gets the total amount of bytes allocated in arena (excluding alignment).
int64 GetTotalBytes() const
{
return Platform::AtomicRead(&_totalBytes);
}
// Allocates a chunk of unitialized memory.
void* Allocate(uint64 size, uint64 alignment = PLATFORM_MEMORY_ALIGNMENT);
// Frees all memory allocations within allocator.
void Free();
// Allocates a chunk of unitialized memory.
template<class T>
inline T* Allocate(uint64 count)
{
return (T*)Allocate(count * sizeof(T), alignof(T));
}
// Creates a new object within the arena allocator.
template<class T, class... Args>
inline T* New(Args&&...args)
{
T* ptr = (T*)Allocate(sizeof(T));
new(ptr) T(Forward<Args>(args)...);
return ptr;
}
};
/// <summary>
/// The memory allocation policy that uses a part of shared page allocator. Allocations are performed in stack-manner, and free is no-op.
/// </summary>
template<typename ArenaType>
class ArenaAllocationBase
{
public:
enum { HasSwap = true };
typedef ArenaType* Tag;
template<typename T>
class Data
{
private:
T* _data = nullptr;
ArenaType* _arena = nullptr;
public:
FORCE_INLINE Data()
{
}
FORCE_INLINE Data(Tag tag)
{
_arena = tag;
}
FORCE_INLINE ~Data()
{
}
FORCE_INLINE T* Get()
{
return _data;
}
FORCE_INLINE const T* Get() const
{
return _data;
}
FORCE_INLINE int32 CalculateCapacityGrow(int32 capacity, const int32 minCapacity) const
{
return AllocationUtils::CalculateCapacityGrow(capacity, minCapacity);
}
FORCE_INLINE void Allocate(const int32 capacity)
{
ASSERT_LOW_LAYER(!_data && _arena);
_data = (T*)_arena->Allocate(capacity * sizeof(T), alignof(T));
}
FORCE_INLINE void Relocate(const int32 capacity, int32 oldCount, int32 newCount)
{
ASSERT_LOW_LAYER(_arena);
T* newData = capacity != 0 ? (T*)_arena->Allocate(capacity * sizeof(T), alignof(T)) : nullptr;
if (oldCount)
{
if (newCount > 0)
Memory::MoveItems(newData, _data, newCount);
Memory::DestructItems(_data, oldCount);
}
_data = newData;
}
FORCE_INLINE void Free()
{
_data = nullptr;
}
FORCE_INLINE void Swap(Data& other)
{
::Swap(_data, other._data);
_arena = other._arena; // TODO: find a better way to move allocation with AllocationUtils::MoveToEmpty to preserve/maintain allocation tag ownership
}
};
};
/// <summary>
/// The memory allocation policy that uses a part of shared page allocator. Allocations are performed in stack-manner, and free is no-op.
/// </summary>
typedef ArenaAllocationBase<ArenaAllocator> ArenaAllocation;
/// <summary>
/// The memory allocation policy that uses a part of shared page allocator. Allocations are performed in stack-manner, and free is no-op.
/// </summary>
typedef ArenaAllocationBase<ConcurrentArenaAllocator> ConcurrentArenaAllocation;
+1 -2
View File
@@ -11,14 +11,13 @@
class CrtAllocator
{
public:
/// <summary>
/// Allocates memory on a specified alignment boundary.
/// </summary>
/// <param name="size">The size of the allocation (in bytes).</param>
/// <param name="alignment">The memory alignment (in bytes). Must be an integer power of 2.</param>
/// <returns>The pointer to the allocated chunk of the memory. The pointer is a multiple of alignment.</returns>
FORCE_INLINE static void* Allocate(uint64 size, uint64 alignment = 16)
FORCE_INLINE static void* Allocate(uint64 size, uint64 alignment = PLATFORM_MEMORY_ALIGNMENT)
{
return Platform::Allocate(size, alignment);
}
+18 -45
View File
@@ -104,12 +104,6 @@ public:
{
new(dst) T();
}
/// <summary>
/// Constructs the item in the memory.
/// </summary>
/// <remarks>The optimized version is noop.</remarks>
/// <param name="dst">The address of the memory location to construct.</param>
template<typename T>
FORCE_INLINE static typename TEnableIf<TIsTriviallyConstructible<T>::Value>::Type ConstructItem(T* dst)
{
@@ -132,13 +126,6 @@ public:
++(T*&)dst;
}
}
/// <summary>
/// Constructs the range of items in the memory.
/// </summary>
/// <remarks>The optimized version is noop.</remarks>
/// <param name="dst">The address of the first memory location to construct.</param>
/// <param name="count">The number of element to construct. Can be equal 0.</param>
template<typename T>
FORCE_INLINE static typename TEnableIf<TIsTriviallyConstructible<T>::Value>::Type ConstructItems(T* dst, int32 count)
{
@@ -163,14 +150,6 @@ public:
++src;
}
}
/// <summary>
/// Constructs the range of items in the memory from the set of arguments.
/// </summary>
/// <remarks>The optimized version uses low-level memory copy.</remarks>
/// <param name="dst">The address of the first memory location to construct.</param>
/// <param name="src">The address of the first memory location to pass to the constructor.</param>
/// <param name="count">The number of element to construct. Can be equal 0.</param>
template<typename T, typename U>
FORCE_INLINE static typename TEnableIf<TIsBitwiseConstructible<T, U>::Value>::Type ConstructItems(T* dst, const U* src, int32 count)
{
@@ -187,12 +166,6 @@ public:
{
dst->~T();
}
/// <summary>
/// Destructs the item in the memory.
/// </summary>
/// <remarks>The optimized version is noop.</remarks>
/// <param name="dst">The address of the memory location to destruct.</param>
template<typename T>
FORCE_INLINE static typename TEnableIf<TIsTriviallyDestructible<T>::Value>::Type DestructItem(T* dst)
{
@@ -213,13 +186,6 @@ public:
++dst;
}
}
/// <summary>
/// Destructs the range of items in the memory.
/// </summary>
/// <remarks>The optimized version is noop.</remarks>
/// <param name="dst">The address of the first memory location to destruct.</param>
/// <param name="count">The number of element to destruct. Can be equal 0.</param>
template<typename T>
FORCE_INLINE static typename TEnableIf<TIsTriviallyDestructible<T>::Value>::Type DestructItems(T* dst, int32 count)
{
@@ -242,15 +208,7 @@ public:
++src;
}
}
/// <summary>
/// Copies the range of items using the assignment operator.
/// </summary>
/// <remarks>The optimized version is low-level memory copy.</remarks>
/// <param name="dst">The address of the first memory location to start assigning to.</param>
/// <param name="src">The address of the first memory location to assign from.</param>
/// <param name="count">The number of element to assign. Can be equal 0.</param>
template<typename T>
template<typename T, typename U>
FORCE_INLINE static typename TEnableIf<TIsTriviallyCopyAssignable<T>::Value>::Type CopyItems(T* dst, const T* src, int32 count)
{
Platform::MemoryCopy(dst, src, count * sizeof(T));
@@ -273,16 +231,31 @@ public:
++src;
}
}
template<typename T, typename U>
FORCE_INLINE static typename TEnableIf<TIsBitwiseConstructible<T, U>::Value>::Type MoveItems(T* dst, U* src, int32 count)
{
Platform::MemoryCopy(dst, src, count * sizeof(U));
}
/// <summary>
/// Moves the range of items in the memory from the set of arguments.
/// Moves the range of items using the assignment operator.
/// </summary>
/// <remarks>The optimized version uses low-level memory copy.</remarks>
/// <param name="dst">The address of the first memory location to move.</param>
/// <param name="src">The address of the first memory location to pass to the move constructor.</param>
/// <param name="count">The number of element to move. Can be equal 0.</param>
template<typename T, typename U>
FORCE_INLINE static typename TEnableIf<TIsBitwiseConstructible<T, U>::Value>::Type MoveItems(T* dst, U* src, int32 count)
FORCE_INLINE static typename TEnableIf<!TIsBitwiseConstructible<T, U>::Value>::Type MoveAssignItems(T* dst, U* src, int32 count)
{
while (count--)
{
*dst = MoveTemp(*src);
++(T*&)dst;
++src;
}
}
template<typename T, typename U>
FORCE_INLINE static typename TEnableIf<TIsBitwiseConstructible<T, U>::Value>::Type MoveAssignItems(T* dst, U* src, int32 count)
{
Platform::MemoryCopy(dst, src, count * sizeof(U));
}
@@ -11,6 +11,7 @@ class SimpleHeapAllocation
{
public:
enum { HasSwap = true };
typedef void* Tag;
template<typename T>
class Data
@@ -23,6 +24,10 @@ public:
{
}
FORCE_INLINE Data(Tag tag)
{
}
FORCE_INLINE ~Data()
{
if (_data)
+3 -2
View File
@@ -19,7 +19,7 @@ namespace
CriticalSection PoolLocker;
double LastUpdate;
float LastUpdateGameTime;
Dictionary<Object*, float> Pool(8192);
Dictionary<Object*, float> Pool;
uint64 PoolCounter = 0;
}
@@ -114,6 +114,7 @@ void ObjectsRemovalService::Flush(float dt, float gameDelta)
bool ObjectsRemoval::Init()
{
Pool.EnsureCapacity(8192);
LastUpdate = Platform::GetTimeSeconds();
LastUpdateGameTime = 0;
return false;
@@ -156,7 +157,7 @@ Object::~Object()
{
#if BUILD_DEBUG && 0
// Prevent removing object that is still reverenced by the removal service
ASSERT(!ObjectsRemovalService::IsInPool(this));
//ASSERT(!ObjectsRemovalService::IsInPool(this));
#endif
}
+1 -1
View File
@@ -43,7 +43,7 @@ public:
/// <summary>
/// Gets the total number of milliseconds.
/// </summary>
FORCE_INLINE double GetTotalMilliseconds() const
FORCE_INLINE float GetTotalMilliseconds() const
{
return (float)((_end - _start) * 1000.0);
}
+5
View File
@@ -215,6 +215,11 @@ public:
return String(_data.Get(), _data.Count());
}
StringAnsi ToStringAnsi() const
{
return StringAnsi(_data.Get(), _data.Count());
}
StringView ToStringView() const;
};
+8
View File
@@ -208,6 +208,14 @@ public:
return StringUtils::CompareIgnoreCase(&(*this)[Length() - suffix.Length()], *suffix) == 0;
return StringUtils::Compare(&(*this)[Length() - suffix.Length()], *suffix) == 0;
}
bool Contains(const T* subStr, StringSearchCase searchCase = StringSearchCase::CaseSensitive) const
{
const int32 length = Length();
if (subStr == nullptr || length == 0)
return false;
return (searchCase == StringSearchCase::IgnoreCase ? StringUtils::FindIgnoreCase(_data, subStr) : StringUtils::Find(_data, subStr)) != nullptr;
}
};
/// <summary>
+3 -1
View File
@@ -118,7 +118,7 @@ VariantType::VariantType(Types type, const MClass* klass)
#if USE_CSHARP
if (klass)
{
const StringAnsi& typeName = klass->GetFullName();
const StringAnsiView typeName = klass->GetFullName();
const int32 length = typeName.Length();
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
Platform::MemoryCopy(TypeName, typeName.Get(), length);
@@ -632,6 +632,7 @@ Variant::Variant(ScriptingObject* v)
AsObject = v;
if (v)
{
// TODO: optimize VariantType to support statically linked typename of ScriptingType (via 1 bit flag within Types enum, only in game as editor might hot-reload types)
Type.SetTypeName(v->GetType().Fullname);
v->Deleted.Bind<Variant, &Variant::OnObjectDeleted>(this);
}
@@ -643,6 +644,7 @@ Variant::Variant(Asset* v)
AsAsset = v;
if (v)
{
// TODO: optimize VariantType to support statically linked typename of ScriptingType (via 1 bit flag within Types enum, only in game as editor might hot-reload types)
Type.SetTypeName(v->GetType().Fullname);
v->AddReference();
v->OnUnloaded.Bind<Variant, &Variant::OnAssetUnloaded>(this);
+42 -7
View File
@@ -186,6 +186,34 @@ namespace
Task* AsyncTask = nullptr;
Array<CommandData> Commands;
void BuildName(CommandData& cmd, const MClass* mclass, const StringAnsiView& itemName)
{
StringAnsiView mclassName = mclass->GetName();
StringAnsiView mclassFullname = mclass->GetFullName();
StringAnsiView mclassNamespace = mclass->GetNamespace();
Array<Char, InlinedAllocation<200>> buffer; // Stack-based but with option to alloc in case of very long commands
buffer.Resize(mclassFullname.Length() - mclassNamespace.Length() + itemName.Length() + 1);
Char* bufferPtr = buffer.Get();
// Check if class is nested, then include outer class name for hierarchy
if (mclassNamespace.Length() + mclassName.Length() + 1 != mclassFullname.Length())
{
StringAnsiView outerName(mclassFullname.Get() + mclassNamespace.Length() + 1, mclassFullname.Length() - mclassNamespace.Length() - 2 - mclassName.Length());
StringUtils::Copy(bufferPtr, outerName.Get(), outerName.Length());
bufferPtr += outerName.Length();
*bufferPtr++ = '.';
}
StringUtils::Copy(bufferPtr, mclassName.Get(), mclassName.Length());
bufferPtr += mclassName.Length();
*bufferPtr++ = '.';
StringUtils::Copy(bufferPtr, itemName.Get(), itemName.Length());
bufferPtr += itemName.Length();
*bufferPtr = 0;
cmd.Name.Set(buffer.Get(), (int32)(bufferPtr - buffer.Get()));
}
void FindDebugCommands(BinaryModule* module)
{
if (module == GetBinaryModuleCorlib())
@@ -206,8 +234,6 @@ namespace
mclass->IsEnum())
continue;
const bool useClass = mclass->HasAttribute(attribute);
// TODO: optimize this via stack-based format buffer and then convert Ansi to UTF16
#define BUILD_NAME(commandData, itemName) commandData.Name = String(mclass->GetName()) + TEXT(".") + String(itemName)
// Process methods
const auto& methods = mclass->GetMethods();
@@ -215,7 +241,7 @@ namespace
{
if (!method->IsStatic())
continue;
const StringAnsi& name = method->GetName();
const StringAnsiView name = method->GetName();
if (name.Contains("Internal_") ||
mclass->GetFullName().Contains(".Interop."))
continue;
@@ -231,7 +257,7 @@ namespace
continue;
auto& commandData = Commands.AddOne();
BUILD_NAME(commandData, method->GetName());
BuildName(commandData, mclass, method->GetName());
commandData.Module = module;
commandData.Method = method;
}
@@ -248,7 +274,7 @@ namespace
continue;
auto& commandData = Commands.AddOne();
BUILD_NAME(commandData, field->GetName());
BuildName(commandData, mclass, field->GetName());
commandData.Module = module;
commandData.Field = field;
}
@@ -265,13 +291,12 @@ namespace
continue;
auto& commandData = Commands.AddOne();
BUILD_NAME(commandData, property->GetName());
BuildName(commandData, mclass, property->GetName());
commandData.Module = module;
commandData.MethodGet = property->GetGetMethod();
commandData.MethodSet = property->GetSetMethod();
}
}
#undef BUILD_NAME
}
else
#endif
@@ -435,9 +460,19 @@ void DebugCommands::InitAsync()
AsyncTask = Task::StartNew(InitCommands);
}
void DebugCommands::GetAllCommands(Array<StringView>& commands)
{
EnsureInited();
ScopeLock lock(Locker);
for (const auto& command : Commands)
commands.Add(command.Name);
}
DebugCommands::CommandFlags DebugCommands::GetCommandFlags(StringView command)
{
CommandFlags result = CommandFlags::None;
if (command.FindLast(' ') != -1)
command = command.Left(command.Find(' '));
// TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
String commandCopy = command;
command = commandCopy;
+6
View File
@@ -46,6 +46,12 @@ public:
/// </summary>
API_FUNCTION() static void InitAsync();
/// <summary>
/// Gets all available commands.
/// </summary>
/// <param name="commands">The output list of all commands (unsorted).</param>
API_FUNCTION() static void GetAllCommands(API_PARAM(Out) Array<StringView, HeapAllocation>& commands);
/// <summary>
/// Returns flags of the command.
/// </summary>
+21 -7
View File
@@ -358,7 +358,8 @@ struct DebugDrawContext
DebugDrawData DebugDrawDefault;
DebugDrawData DebugDrawDepthTest;
Float3 LastViewPos = Float3::Zero;
Matrix LastViewProj = Matrix::Identity;
Matrix LastViewProjection = Matrix::Identity;
BoundingFrustum LastViewFrustum;
inline int32 Count() const
{
@@ -526,6 +527,7 @@ DebugDrawService DebugDrawServiceInstance;
bool DebugDrawService::Init()
{
PROFILE_MEM(Graphics);
Context = &GlobalContext;
// Init wireframe sphere cache
@@ -644,6 +646,7 @@ void DebugDrawService::Update()
}
PROFILE_CPU();
PROFILE_MEM(Graphics);
// Update lists
float deltaTime = Time::Update.DeltaTime.GetTotalSeconds();
@@ -778,9 +781,23 @@ Vector3 DebugDraw::GetViewPos()
return Context->LastViewPos;
}
BoundingFrustum DebugDraw::GetViewFrustum()
{
return Context->LastViewFrustum;
}
void DebugDraw::SetView(const RenderView& view)
{
Context->LastViewPos = view.Position;
Context->LastViewProjection = view.Projection;
Context->LastViewFrustum = view.Frustum;
}
void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTextureView* depthBuffer, bool enableDepthTest)
{
PROFILE_GPU_CPU("Debug Draw");
const RenderView& view = renderContext.View;
SetView(view);
// Ensure to have shader loaded and any lines to render
const int32 debugDrawDepthTestCount = Context->DebugDrawDepthTest.Count();
@@ -790,7 +807,6 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
if (renderContext.Buffers == nullptr || !DebugDrawVB)
return;
auto context = GPUDevice::Instance->GetMainContext();
const RenderView& view = renderContext.View;
if (Context->Origin != view.Origin)
{
// Teleport existing debug shapes to maintain their location
@@ -799,8 +815,6 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
Context->DebugDrawDepthTest.Teleport(delta);
Context->Origin = view.Origin;
}
Context->LastViewPos = view.Position;
Context->LastViewProj = view.Projection;
TaaJitterRemoveContext taaJitterRemove(view);
// Fallback to task buffers
@@ -839,7 +853,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
auto vb = DebugDrawVB->GetBuffer();
// Draw with depth test
if (depthTestLines.VertexCount + depthTestTriangles.VertexCount + depthTestWireTriangles.VertexCount > 0)
if (depthTestLines.VertexCount + depthTestTriangles.VertexCount + depthTestWireTriangles.VertexCount + Context->DebugDrawDepthTest.GeometryBuffers.Count() > 0)
{
if (data.EnableDepthTest)
context->BindSR(0, renderContext.Buffers->DepthBuffer);
@@ -895,7 +909,7 @@ void DebugDraw::Draw(RenderContext& renderContext, GPUTextureView* target, GPUTe
}
// Draw without depth
if (defaultLines.VertexCount + defaultTriangles.VertexCount + defaultWireTriangles.VertexCount > 0)
if (defaultLines.VertexCount + defaultTriangles.VertexCount + defaultWireTriangles.VertexCount + Context->DebugDrawDefault.GeometryBuffers.Count() > 0)
{
context->SetRenderTarget(target);
@@ -1383,7 +1397,7 @@ void DebugDraw::DrawWireSphere(const BoundingSphere& sphere, const Color& color,
int32 index;
const Float3 centerF = sphere.Center - Context->Origin;
const float radiusF = (float)sphere.Radius;
const float screenRadiusSquared = RenderTools::ComputeBoundsScreenRadiusSquared(centerF, radiusF, Context->LastViewPos, Context->LastViewProj);
const float screenRadiusSquared = RenderTools::ComputeBoundsScreenRadiusSquared(centerF, radiusF, Context->LastViewPos, Context->LastViewProjection);
if (screenRadiusSquared > DEBUG_DRAW_SPHERE_LOD0_SCREEN_SIZE * DEBUG_DRAW_SPHERE_LOD0_SCREEN_SIZE * 0.25f)
index = 0;
else if (screenRadiusSquared > DEBUG_DRAW_SPHERE_LOD1_SCREEN_SIZE * DEBUG_DRAW_SPHERE_LOD1_SCREEN_SIZE * 0.25f)
+5
View File
@@ -76,6 +76,11 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
// Gets the last view position when rendering the current context. Can be used for custom culling or LODing when drawing more complex shapes.
static Vector3 GetViewPos();
// Gets the last view frustum when rendering the current context. Can be used for custom culling or LODing when drawing more complex shapes.
static BoundingFrustum GetViewFrustum();
// Sets the rendering view information beforehand.
API_FUNCTION() static void SetView(API_PARAM(ref) const RenderView& view);
/// <summary>
/// Draws the collected debug shapes to the output.
+2
View File
@@ -4,6 +4,8 @@
Log::Exception::~Exception()
{
#if LOG_ENABLE
// Always write exception to the log
Logger::Write(_level, ToString());
#endif
}
+1 -1
View File
@@ -211,7 +211,7 @@ void GameBaseImpl::OnPostRender(GPUContext* context, RenderContext& renderContex
}
// Wait for texture loaded before showing splash screen
if (!SplashScreen->IsLoaded() || SplashScreen.Get()->GetTexture()->MipLevels() != SplashScreen.Get()->StreamingTexture()->TotalMipLevels())
if (!SplashScreen->IsLoaded() || SplashScreen.Get()->GetResidentMipLevels() != SplashScreen.Get()->GetMipLevels())
{
return;
}
+1 -1
View File
@@ -40,7 +40,7 @@ namespace FlaxEngine
{
if (message == null)
return;
#if BUILD_RELEASE
#if BUILD_RELEASE || !FLAX_EDITOR
string stackTrace = null;
#else
string stackTrace = Environment.StackTrace;
+32 -2
View File
@@ -76,9 +76,15 @@ FatalErrorType Engine::FatalError = FatalErrorType::None;
bool Engine::IsRequestingExit = false;
int32 Engine::ExitCode = 0;
Window* Engine::MainWindow = nullptr;
double EngineIdleTime = 0;
int32 Engine::Main(const Char* cmdLine)
{
#if COMPILE_WITH_PROFILER
extern void InitProfilerMemory(const Char* cmdLine, int32 stage);
InitProfilerMemory(cmdLine, 0);
#endif
PROFILE_MEM_BEGIN(Engine);
EngineImpl::CommandLine = cmdLine;
Globals::MainThreadID = Platform::GetCurrentThreadID();
StartupTime = DateTime::Now();
@@ -104,6 +110,9 @@ int32 Engine::Main(const Char* cmdLine)
Platform::Fatal(TEXT("Cannot init platform."));
return -1;
}
#if COMPILE_WITH_PROFILER
InitProfilerMemory(cmdLine, 1);
#endif
Platform::SetHighDpiAwarenessEnabled(!CommandLine::Options.LowDPI.IsTrue());
Time::StartupTime = DateTime::Now();
@@ -142,7 +151,9 @@ int32 Engine::Main(const Char* cmdLine)
{
// End
LOG(Warning, "Loading project cancelled. Closing...");
#if LOG_ENABLE
Log::Logger::Dispose();
#endif
return 0;
}
#endif
@@ -160,10 +171,11 @@ int32 Engine::Main(const Char* cmdLine)
#if !USE_EDITOR && (PLATFORM_WINDOWS || PLATFORM_LINUX || PLATFORM_MAC)
EngineImpl::RunInBackground = PlatformSettings::Get()->RunInBackground;
#endif
Log::Logger::WriteFloor();
LOG_FLOOR();
LOG_FLUSH();
Time::Synchronize();
EngineImpl::IsReady = true;
PROFILE_MEM_END();
// Main engine loop
const bool useSleep = true; // TODO: this should probably be a platform setting
@@ -179,7 +191,10 @@ int32 Engine::Main(const Char* cmdLine)
if (timeToTick > 0.002)
{
PROFILE_CPU_NAMED("Idle");
auto sleepStart = Platform::GetTimeSeconds();
Platform::Sleep(1);
auto sleepEnd = Platform::GetTimeSeconds();
EngineIdleTime += sleepEnd - sleepStart;
}
}
@@ -204,6 +219,10 @@ int32 Engine::Main(const Char* cmdLine)
{
PROFILE_CPU_NAMED("Platform.Tick");
Platform::Tick();
#if COMPILE_WITH_PROFILER
extern void TickProfilerMemory();
TickProfilerMemory();
#endif
}
// Update game logic
@@ -212,6 +231,7 @@ int32 Engine::Main(const Char* cmdLine)
OnUpdate();
OnLateUpdate();
Time::OnEndUpdate();
EngineIdleTime = 0;
}
// Start physics simulation
@@ -227,7 +247,6 @@ int32 Engine::Main(const Char* cmdLine)
{
OnDraw();
Time::OnEndDraw();
FrameMark;
}
}
@@ -377,6 +396,11 @@ void Engine::OnLateUpdate()
void Engine::OnDraw()
{
#if COMPILE_WITH_PROFILER
// Auto-enable GPU events when Tracy got connected
if (!ProfilerGPU::EventsEnabled && TracyIsConnected)
ProfilerGPU::EventsEnabled = true;
#endif
PROFILE_CPU_NAMED("Draw");
// Begin frame rendering
@@ -391,6 +415,7 @@ void Engine::OnDraw()
device->Draw();
// End frame rendering
FrameMark;
#if COMPILE_WITH_PROFILER
ProfilerGPU::EndFrame();
#endif
@@ -540,16 +565,20 @@ void Engine::OnExit()
#if COMPILE_WITH_PROFILER
ProfilerCPU::Dispose();
ProfilerGPU::Dispose();
ProfilerMemory::Enabled = false;
#endif
#if LOG_ENABLE
// Close logging service
Log::Logger::Dispose();
#endif
Platform::Exit();
}
void EngineImpl::InitLog()
{
#if LOG_ENABLE
// Initialize logger
Log::Logger::Init();
@@ -603,6 +632,7 @@ void EngineImpl::InitLog()
Platform::LogInfo();
LOG_FLUSH();
#endif
}
void EngineImpl::InitPaths()

Some files were not shown because too many files have changed in this diff Show More