Merge branch 'FlaxEngine:master' into flax-msdf-font
This commit is contained in:
@@ -336,11 +336,13 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
SkeletonData* animResultSkeleton = &skeleton;
|
||||
|
||||
// Retarget animation when using output pose from other skeleton
|
||||
AnimGraphImpulse retargetNodes;
|
||||
if (_graph.BaseModel != data.NodesSkeleton)
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("Retarget");
|
||||
auto& targetSkeleton = data.NodesSkeleton->Skeleton;
|
||||
if (context.PoseCacheSize == context.PoseCache.Count())
|
||||
context.PoseCache.AddOne();
|
||||
auto& retargetNodes = context.PoseCache[context.PoseCacheSize++];
|
||||
retargetNodes = *animResult;
|
||||
retargetNodes.Nodes.Resize(targetSkeleton.Nodes.Count());
|
||||
Transform* targetNodes = retargetNodes.Nodes.Get();
|
||||
|
||||
@@ -109,86 +109,84 @@ namespace
|
||||
nodes->RootMotion.Orientation.Normalize();
|
||||
}
|
||||
}
|
||||
|
||||
Matrix ComputeWorldMatrixRecursive(const SkeletonData& skeleton, int32 index, Matrix localMatrix)
|
||||
{
|
||||
const auto& node = skeleton.Nodes[index];
|
||||
index = node.ParentIndex;
|
||||
while (index != -1)
|
||||
{
|
||||
const auto& parent = skeleton.Nodes[index];
|
||||
localMatrix *= parent.LocalTransform.GetWorld();
|
||||
index = parent.ParentIndex;
|
||||
}
|
||||
return localMatrix;
|
||||
}
|
||||
|
||||
Matrix ComputeInverseParentMatrixRecursive(const SkeletonData& skeleton, int32 index)
|
||||
{
|
||||
Matrix inverseParentMatrix = Matrix::Identity;
|
||||
const auto& node = skeleton.Nodes[index];
|
||||
if (node.ParentIndex != -1)
|
||||
{
|
||||
inverseParentMatrix = ComputeWorldMatrixRecursive(skeleton, index, inverseParentMatrix);
|
||||
inverseParentMatrix = Matrix::Invert(inverseParentMatrix);
|
||||
}
|
||||
return inverseParentMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 targetIndex)
|
||||
// Utility for retargeting animation poses between skeletons.
|
||||
struct Retargeting
|
||||
{
|
||||
// sourceSkeleton - skeleton of Anim Graph (Base Locomotion pack)
|
||||
// targetSkeleton - visual mesh skeleton (City Characters pack)
|
||||
// target - anim graph input/output transformation of that node
|
||||
const auto& targetNode = targetSkeleton.Nodes[targetIndex];
|
||||
const int32 sourceIndex = sourceMapping.NodesMapping[targetIndex];
|
||||
if (sourceIndex == -1)
|
||||
private:
|
||||
const Matrix* _sourcePosePtr, * _targetPosePtr;
|
||||
const SkeletonData* _sourceSkeleton, *_targetSkeleton;
|
||||
const SkinnedModel::SkeletonMapping* _sourceMapping;
|
||||
|
||||
public:
|
||||
void Init(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping)
|
||||
{
|
||||
// Use T-pose
|
||||
node = targetNode.LocalTransform;
|
||||
return;
|
||||
ASSERT_LOW_LAYER(targetSkeleton.Nodes.Count() == sourceMapping.NodesMapping.Length());
|
||||
|
||||
// Cache world-space poses for source and target skeletons to avoid redundant calculations during retargeting
|
||||
_sourcePosePtr = sourceSkeleton.GetNodesPose().Get();
|
||||
_targetPosePtr = targetSkeleton.GetNodesPose().Get();
|
||||
|
||||
_sourceSkeleton = &sourceSkeleton;
|
||||
_targetSkeleton = &targetSkeleton;
|
||||
_sourceMapping = &sourceMapping;
|
||||
}
|
||||
const auto& sourceNode = sourceSkeleton.Nodes[sourceIndex];
|
||||
|
||||
// [Reference: https://wickedengine.net/2022/09/animation-retargeting/comment-page-1/]
|
||||
|
||||
// Calculate T-Pose of source node, target node and target parent node
|
||||
Matrix bindMatrix = ComputeWorldMatrixRecursive(sourceSkeleton, sourceIndex, sourceNode.LocalTransform.GetWorld());
|
||||
Matrix inverseBindMatrix = Matrix::Invert(bindMatrix);
|
||||
Matrix targetMatrix = ComputeWorldMatrixRecursive(targetSkeleton, targetIndex, targetNode.LocalTransform.GetWorld());
|
||||
Matrix inverseParentMatrix = ComputeInverseParentMatrixRecursive(targetSkeleton, targetIndex);
|
||||
|
||||
// Target node animation is world-space difference of the animated source node inside the target's parent node world-space
|
||||
Matrix localMatrix = inverseBindMatrix * ComputeWorldMatrixRecursive(sourceSkeleton, sourceIndex, node.GetWorld());
|
||||
localMatrix = targetMatrix * localMatrix * inverseParentMatrix;
|
||||
|
||||
// Extract local node transformation
|
||||
localMatrix.Decompose(node);
|
||||
}
|
||||
|
||||
void RetargetSkeletonPose(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, const Transform* sourceNodes, Transform* targetNodes)
|
||||
{
|
||||
// TODO: cache source and target skeletons world-space poses for faster retargeting (use some pooled memory)
|
||||
ASSERT_LOW_LAYER(targetSkeleton.Nodes.Count() == mapping.NodesMapping.Length());
|
||||
for (int32 targetIndex = 0; targetIndex < targetSkeleton.Nodes.Count(); targetIndex++)
|
||||
void RetargetNode(const Transform& source, Transform& target, int32 sourceIndex, int32 targetIndex)
|
||||
{
|
||||
auto& targetNode = targetSkeleton.Nodes.Get()[targetIndex];
|
||||
const int32 sourceIndex = mapping.NodesMapping.Get()[targetIndex];
|
||||
Transform node;
|
||||
// sourceSkeleton - skeleton of Anim Graph
|
||||
// targetSkeleton - visual mesh skeleton
|
||||
// target - anim graph input/output transformation of that node
|
||||
const SkeletonNode& targetNode = _targetSkeleton->Nodes.Get()[targetIndex];
|
||||
if (sourceIndex == -1)
|
||||
{
|
||||
// Use T-pose
|
||||
node = targetNode.LocalTransform;
|
||||
target = targetNode.LocalTransform;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Retarget
|
||||
node = sourceNodes[sourceIndex];
|
||||
RetargetSkeletonNode(sourceSkeleton, targetSkeleton, mapping, node, targetIndex);
|
||||
// [Reference: https://wickedengine.net/2022/09/animation-retargeting/comment-page-1/]
|
||||
|
||||
// Calculate T-Pose of source node, target node and target parent node
|
||||
const Matrix* sourcePosePtr = _sourcePosePtr;
|
||||
const Matrix* targetPosePtr = _targetPosePtr;
|
||||
const Matrix& bindMatrix = sourcePosePtr[sourceIndex];
|
||||
const Matrix& targetMatrix = targetPosePtr[targetIndex];
|
||||
Matrix inverseParentMatrix;
|
||||
if (targetNode.ParentIndex != -1)
|
||||
Matrix::Invert(targetPosePtr[targetNode.ParentIndex], inverseParentMatrix);
|
||||
else
|
||||
inverseParentMatrix = Matrix::Identity;
|
||||
|
||||
// Target node animation is world-space difference of the animated source node inside the target's parent node world-space
|
||||
const SkeletonNode& sourceNode = _sourceSkeleton->Nodes.Get()[sourceIndex];
|
||||
Matrix localMatrix = source.GetWorld();
|
||||
if (sourceNode.ParentIndex != -1)
|
||||
localMatrix = localMatrix * sourcePosePtr[sourceNode.ParentIndex];
|
||||
localMatrix = Matrix::Invert(bindMatrix) * localMatrix;
|
||||
localMatrix = targetMatrix * localMatrix * inverseParentMatrix;
|
||||
|
||||
// Extract local node transformation
|
||||
localMatrix.Decompose(target);
|
||||
}
|
||||
targetNodes[targetIndex] = node;
|
||||
}
|
||||
|
||||
FORCE_INLINE void RetargetPose(const Transform* sourceNodes, Transform* targetNodes)
|
||||
{
|
||||
for (int32 targetIndex = 0; targetIndex < _targetSkeleton->Nodes.Count(); targetIndex++)
|
||||
{
|
||||
const int32 sourceIndex = _sourceMapping->NodesMapping.Get()[targetIndex];
|
||||
RetargetNode(sourceNodes[sourceIndex], targetNodes[targetIndex], sourceIndex, targetIndex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void RetargetSkeletonPose(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, const Transform* sourceNodes, Transform* targetNodes)
|
||||
{
|
||||
Retargeting retargeting;
|
||||
retargeting.Init(sourceSkeleton, targetSkeleton, mapping);
|
||||
retargeting.RetargetPose(sourceNodes, targetNodes);
|
||||
}
|
||||
|
||||
AnimGraphTraceEvent& AnimGraphContext::AddTraceEvent(const AnimGraphNode* node)
|
||||
@@ -431,9 +429,13 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
const bool weighted = weight < 1.0f;
|
||||
const bool retarget = mapping.SourceSkeleton && mapping.SourceSkeleton != mapping.TargetSkeleton;
|
||||
const auto emptyNodes = GetEmptyNodes();
|
||||
Retargeting retargeting;
|
||||
SkinnedModel::SkeletonMapping sourceMapping;
|
||||
if (retarget)
|
||||
{
|
||||
sourceMapping = _graph.BaseModel->GetSkeletonMapping(mapping.SourceSkeleton);
|
||||
retargeting.Init(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, mapping);
|
||||
}
|
||||
for (int32 nodeIndex = 0; nodeIndex < nodes->Nodes.Count(); nodeIndex++)
|
||||
{
|
||||
const int32 nodeToChannel = mapping.NodesMapping[nodeIndex];
|
||||
@@ -447,7 +449,8 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
// Optionally retarget animation into the skeleton used by the Anim Graph
|
||||
if (retarget)
|
||||
{
|
||||
RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, nodeIndex);
|
||||
const int32 sourceIndex = sourceMapping.NodesMapping[nodeIndex];
|
||||
retargeting.RetargetNode(srcNode, srcNode, sourceIndex, nodeIndex);
|
||||
}
|
||||
|
||||
// Mark node as used
|
||||
@@ -958,6 +961,21 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Set Parameter
|
||||
case 5:
|
||||
{
|
||||
// Set parameter value
|
||||
int32 paramIndex;
|
||||
const auto param = _graph.GetParameter((Guid)node->Values[0], paramIndex);
|
||||
if (param)
|
||||
{
|
||||
context.Data->Parameters[paramIndex].Value = tryGetValue(node->GetBox(1), 1, Value::Null);
|
||||
}
|
||||
|
||||
// Pass over the pose
|
||||
value = tryGetValue(node->GetBox(2), Value::Null);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -132,9 +132,7 @@ int32 AudioClip::GetFirstBufferIndex(float time, float& offset) const
|
||||
if (_buffersStartTimes[i + 1] > time)
|
||||
{
|
||||
offset = time - _buffersStartTimes[i];
|
||||
#if BUILD_DEBUG
|
||||
ASSERT(Math::Abs(GetBufferStartTime(i) + offset - time) < 0.001f);
|
||||
#endif
|
||||
ASSERT_LOW_LAYER(Math::Abs(GetBufferStartTime(i) + offset - time) < 0.001f);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class AudioSource;
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API AudioClip : public BinaryAsset, public StreamableResource
|
||||
{
|
||||
DECLARE_BINARY_ASSET_HEADER(AudioClip, 2);
|
||||
friend class AudioBackendOAL;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -37,15 +37,17 @@ void AudioListener::OnEnable()
|
||||
{
|
||||
_prevPos = GetPosition();
|
||||
_velocity = Vector3::Zero;
|
||||
|
||||
ASSERT(!Audio::Listeners.Contains(this));
|
||||
if (Audio::Listeners.Count() >= AUDIO_MAX_LISTENERS)
|
||||
{
|
||||
LOG(Error, "Unsupported amount of the audio listeners!");
|
||||
if IF_CONSTEXPR (AUDIO_MAX_LISTENERS == 1)
|
||||
LOG(Warning, "There is more than one Audio Listener active. Please make sure only exactly one is active at any given time.");
|
||||
else
|
||||
LOG(Warning, "Too many Audio Listener active.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT(!Audio::Listeners.Contains(this));
|
||||
if (Audio::Listeners.Count() > 0)
|
||||
LOG(Warning, "There is more than one Audio Listener active. Please make sure only exactly one is active at any given time.");
|
||||
Audio::Listeners.Add(this);
|
||||
AudioBackend::Listener::Reset();
|
||||
AudioBackend::Listener::TransformChanged(GetPosition(), GetOrientation());
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
#include "Engine/Audio/AudioListener.h"
|
||||
#include "Engine/Audio/AudioSource.h"
|
||||
#include "Engine/Audio/AudioSettings.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Level/Level.h"
|
||||
#include "Engine/Video/VideoPlayer.h"
|
||||
|
||||
// Include OpenAL library
|
||||
// Source: https://github.com/kcat/openal-soft
|
||||
@@ -73,6 +76,7 @@ namespace ALC
|
||||
ALCdevice* Device = nullptr;
|
||||
ALCcontext* Context = nullptr;
|
||||
AudioBackend::FeatureFlags Features = AudioBackend::FeatureFlags::None;
|
||||
bool Inited = false;
|
||||
CriticalSection Locker;
|
||||
Dictionary<uint32, SourceData> SourcesData;
|
||||
|
||||
@@ -164,12 +168,9 @@ namespace ALC
|
||||
float Time;
|
||||
};
|
||||
|
||||
void RebuildContext(const Array<AudioSourceState>& states)
|
||||
void RebuildContext()
|
||||
{
|
||||
LOG(Info, "Rebuilding audio contexts");
|
||||
|
||||
ClearContext();
|
||||
|
||||
if (Device == nullptr)
|
||||
return;
|
||||
|
||||
@@ -182,10 +183,16 @@ namespace ALC
|
||||
|
||||
Context = alcCreateContext(Device, attrList);
|
||||
alcMakeContextCurrent(Context);
|
||||
|
||||
}
|
||||
|
||||
void RebuildListeners()
|
||||
{
|
||||
for (AudioListener* listener : Audio::Listeners)
|
||||
Listener::Rebuild(listener);
|
||||
|
||||
}
|
||||
|
||||
void RebuildSources(const Array<AudioSourceState>& states)
|
||||
{
|
||||
for (int32 i = 0; i < states.Count(); i++)
|
||||
{
|
||||
AudioSource* source = Audio::Sources[i];
|
||||
@@ -205,6 +212,13 @@ namespace ALC
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildContext(const Array<AudioSourceState>& states)
|
||||
{
|
||||
RebuildContext();
|
||||
RebuildListeners();
|
||||
RebuildSources(states);
|
||||
}
|
||||
|
||||
void RebuildContext(bool isChangingDevice)
|
||||
{
|
||||
Array<AudioSourceState> states;
|
||||
@@ -400,7 +414,7 @@ void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop)
|
||||
void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
|
||||
{
|
||||
ALC::Locker.Lock();
|
||||
const bool pan = ALC::SourcesData[sourceID].Spatial;
|
||||
const float pan = ALC::SourcesData[sourceID].Pan;
|
||||
ALC::Locker.Unlock();
|
||||
if (spatial)
|
||||
{
|
||||
@@ -629,6 +643,7 @@ AudioBackend::FeatureFlags AudioBackendOAL::Base_Features()
|
||||
|
||||
void AudioBackendOAL::Base_OnActiveDeviceChanged()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(Audio);
|
||||
|
||||
// Cleanup
|
||||
@@ -659,9 +674,53 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged()
|
||||
LOG(Fatal, "Failed to open OpenAL device ({0}).", String(name));
|
||||
return;
|
||||
}
|
||||
if (ALC::Inited)
|
||||
LOG(Info, "Changed audio device to: {}", String(Audio::GetActiveDevice()->Name));
|
||||
|
||||
// Setup
|
||||
ALC::RebuildContext(states);
|
||||
// Rebuild context
|
||||
ALC::RebuildContext();
|
||||
if (ALC::Inited)
|
||||
{
|
||||
// Reload all audio clips to recreate their buffers
|
||||
for (AudioClip* audioClip : Content::GetAssets<AudioClip>())
|
||||
{
|
||||
audioClip->WaitForLoaded();
|
||||
ScopeLock lock(audioClip->Locker);
|
||||
|
||||
// Clear old buffer IDs
|
||||
for (uint32& bufferID : audioClip->Buffers)
|
||||
bufferID = 0;
|
||||
|
||||
if (audioClip->IsStreamable())
|
||||
{
|
||||
// Let the streaming recreate missing buffers
|
||||
audioClip->RequestStreamingUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reload audio clip
|
||||
auto assetLock = audioClip->Storage->Lock();
|
||||
audioClip->LoadChunk(0);
|
||||
audioClip->Buffers[0] = AudioBackend::Buffer::Create();
|
||||
audioClip->WriteBuffer(0);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Reload all videos to recreate their buffers
|
||||
for (VideoPlayer* videoPlayer : Level::GetActors<VideoPlayer>(true))
|
||||
{
|
||||
VideoBackendPlayer& player = videoPlayer->_player;
|
||||
|
||||
// Clear audio state
|
||||
for (uint32& bufferID : player.AudioBuffers)
|
||||
bufferID = 0;
|
||||
player.NextAudioBuffer = 0;
|
||||
player.AudioSource = 0;
|
||||
}
|
||||
}
|
||||
ALC::RebuildListeners();
|
||||
ALC::RebuildSources(states);
|
||||
}
|
||||
|
||||
void AudioBackendOAL::Base_SetDopplerFactor(float value)
|
||||
@@ -782,6 +841,7 @@ bool AudioBackendOAL::Base_Init()
|
||||
if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize"))
|
||||
ALC::Features = EnumAddFlags(ALC::Features, FeatureFlags::SpatialMultiChannel);
|
||||
#endif
|
||||
ALC::Inited = true;
|
||||
|
||||
// Log service info
|
||||
LOG(Info, "{0} ({1})", String(alGetString(AL_RENDERER)), String(alGetString(AL_VERSION)));
|
||||
|
||||
@@ -672,7 +672,7 @@ bool AudioBackendXAudio2::Base_Init()
|
||||
HRESULT hr = XAudio2Create(&XAudio2::Instance, 0, XAUDIO2_DEFAULT_PROCESSOR);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG(Error, "Failed to initalize XAudio2. Error: 0x{0:x}", hr);
|
||||
LOG(Error, "Failed to initialize XAudio2. Error: 0x{0:x}", hr);
|
||||
return true;
|
||||
}
|
||||
XAudio2::Instance->RegisterForCallbacks(&XAudio2::Callback);
|
||||
@@ -681,7 +681,7 @@ bool AudioBackendXAudio2::Base_Init()
|
||||
hr = XAudio2::Instance->CreateMasteringVoice(&XAudio2::MasteringVoice);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
LOG(Error, "Failed to initalize XAudio2 mastering voice. Error: 0x{0:x}", hr);
|
||||
LOG(Error, "Failed to initialize XAudio2 mastering voice. Error: 0x{0:x}", hr);
|
||||
return true;
|
||||
}
|
||||
XAUDIO2_VOICE_DETAILS details;
|
||||
|
||||
@@ -487,6 +487,8 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
|
||||
const auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
|
||||
if (loadingTask == nullptr)
|
||||
{
|
||||
if (IsLoaded())
|
||||
return false;
|
||||
LOG(Warning, "WaitForLoaded asset \'{0}\' failed. No loading task attached and asset is not loaded.", ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -61,16 +61,24 @@ Array<String> SkinnedModel::GetBlendShapes()
|
||||
|
||||
SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source, bool autoRetarget)
|
||||
{
|
||||
// Fast-path to use cached mapping
|
||||
SkeletonMapping mapping;
|
||||
mapping.TargetSkeleton = this;
|
||||
SkeletonMappingData mappingData;
|
||||
if (_skeletonMappingCache.TryGet(source, mappingData))
|
||||
{
|
||||
mapping.SourceSkeleton = mappingData.SourceSkeleton;
|
||||
mapping.NodesMapping = mappingData.NodesMapping;
|
||||
return mapping;
|
||||
}
|
||||
mapping.SourceSkeleton = nullptr;
|
||||
|
||||
if (WaitForLoaded() || !source || source->WaitForLoaded())
|
||||
return mapping;
|
||||
PROFILE_CPU();
|
||||
ScopeLock lock(Locker);
|
||||
SkeletonMappingData mappingData;
|
||||
if (!_skeletonMappingCache.TryGet(source, mappingData))
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
// Initialize the mapping
|
||||
SkeletonRetarget* retarget = nullptr;
|
||||
const Guid sourceId = source->GetID();
|
||||
@@ -370,6 +378,7 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes)
|
||||
model->Skeleton.Bones[i].LocalTransform = node.LocalTransform;
|
||||
model->Skeleton.Bones[i].NodeIndex = i;
|
||||
}
|
||||
model->Skeleton.Dirty();
|
||||
ClearSkeletonMapping();
|
||||
|
||||
// Calculate offset matrix (inverse bind pose transform) for every bone manually
|
||||
@@ -427,6 +436,7 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<S
|
||||
// Setup
|
||||
model->Skeleton.Nodes = nodes;
|
||||
model->Skeleton.Bones = bones;
|
||||
model->Skeleton.Dirty();
|
||||
ClearSkeletonMapping();
|
||||
|
||||
// Calculate offset matrix (inverse bind pose transform) for every bone manually
|
||||
@@ -823,13 +833,13 @@ bool SkinnedModel::SaveMesh(WriteStream& stream, const ModelData& modelData, int
|
||||
|
||||
void SkinnedModel::ClearSkeletonMapping()
|
||||
{
|
||||
for (auto& e : _skeletonMappingCache)
|
||||
for (const auto& e : _skeletonMappingCache)
|
||||
{
|
||||
e.Key->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#if USE_EDITOR
|
||||
e.Key->OnReloading.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#endif
|
||||
Allocator::Free(e.Value.NodesMapping.Get());
|
||||
Allocator::Free((void*)e.Value.NodesMapping.Get());
|
||||
}
|
||||
_skeletonMappingCache.Clear();
|
||||
}
|
||||
@@ -837,8 +847,9 @@ void SkinnedModel::ClearSkeletonMapping()
|
||||
void SkinnedModel::OnSkeletonMappingSourceAssetUnloaded(Asset* obj)
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
auto i = _skeletonMappingCache.Find(obj);
|
||||
ASSERT(i != _skeletonMappingCache.End());
|
||||
SkeletonMappingData mappingData;
|
||||
bool found = _skeletonMappingCache.TryGet(obj, mappingData);
|
||||
ASSERT(found);
|
||||
|
||||
// Unlink event
|
||||
obj->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
@@ -847,8 +858,8 @@ void SkinnedModel::OnSkeletonMappingSourceAssetUnloaded(Asset* obj)
|
||||
#endif
|
||||
|
||||
// Clear cache
|
||||
Allocator::Free(i->Value.NodesMapping.Get());
|
||||
_skeletonMappingCache.Remove(i);
|
||||
Allocator::Free(mappingData.NodesMapping.Get());
|
||||
_skeletonMappingCache.Remove(obj);
|
||||
}
|
||||
|
||||
uint64 SkinnedModel::GetMemoryUsage() const
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "ModelBase.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Threading/ConcurrentDictionary.h"
|
||||
#include "Engine/Graphics/Models/SkinnedMesh.h"
|
||||
#include "Engine/Graphics/Models/SkeletonData.h"
|
||||
|
||||
@@ -101,9 +101,9 @@ public:
|
||||
struct FLAXENGINE_API SkeletonMapping
|
||||
{
|
||||
// Target skeleton.
|
||||
AssetReference<SkinnedModel> TargetSkeleton;
|
||||
SkinnedModel* TargetSkeleton;
|
||||
// Source skeleton.
|
||||
AssetReference<SkinnedModel> SourceSkeleton;
|
||||
SkinnedModel* SourceSkeleton;
|
||||
// The node-to-node mapping for the fast animation sampling for the skinned model skeleton nodes. Each item is index of the source skeleton node into target skeleton node.
|
||||
Span<int32> NodesMapping;
|
||||
};
|
||||
@@ -115,7 +115,7 @@ private:
|
||||
Span<int32> NodesMapping;
|
||||
};
|
||||
|
||||
Dictionary<Asset*, SkeletonMappingData> _skeletonMappingCache;
|
||||
ConcurrentDictionary<Asset*, SkeletonMappingData> _skeletonMappingCache;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -1700,6 +1700,8 @@ void VisualScript::CacheScriptingType()
|
||||
VisualScriptingBinaryModule::VisualScriptingBinaryModule()
|
||||
: _name("Visual Scripting")
|
||||
{
|
||||
// Visual Scripts can be unloaded and loaded again even in game
|
||||
CanReload = true;
|
||||
}
|
||||
|
||||
ScriptingObject* VisualScriptingBinaryModule::VisualScriptObjectSpawn(const ScriptingObjectSpawnParams& params)
|
||||
|
||||
@@ -684,6 +684,19 @@ Array<Asset*> Content::GetAssets()
|
||||
return assets;
|
||||
}
|
||||
|
||||
Array<Asset*> Content::GetAssets(const MClass* type)
|
||||
{
|
||||
Array<Asset*> assets;
|
||||
AssetsLocker.Lock();
|
||||
for (auto& e : Assets)
|
||||
{
|
||||
if (e.Value->Is(type))
|
||||
assets.Add(e.Value);
|
||||
}
|
||||
AssetsLocker.Unlock();
|
||||
return assets;
|
||||
}
|
||||
|
||||
const Dictionary<Guid, Asset*>& Content::GetAssetsRaw()
|
||||
{
|
||||
AssetsLocker.Lock();
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
#ifndef _MSC_VER
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#endif
|
||||
#include "AssetInfo.h"
|
||||
#include "Asset.h"
|
||||
#include "Config.h"
|
||||
@@ -122,7 +125,26 @@ public:
|
||||
/// Gets the assets (loaded or during load).
|
||||
/// </summary>
|
||||
/// <returns>The collection of assets.</returns>
|
||||
static Array<Asset*, HeapAllocation> GetAssets();
|
||||
API_FUNCTION() static Array<Asset*, HeapAllocation> GetAssets();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assets (loaded or during load).
|
||||
/// </summary>
|
||||
/// <param name="type">Type of the assets to search for. Includes any assets derived from the type.</param>
|
||||
/// <returns>Found actors list.</returns>
|
||||
API_FUNCTION() static Array<Asset*, HeapAllocation> GetAssets(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assets (loaded or during load).
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the object.</typeparam>
|
||||
/// <returns>Found actors list.</returns>
|
||||
template<typename T>
|
||||
static Array<T*, HeapAllocation> GetAssets()
|
||||
{
|
||||
Array<Asset*, HeapAllocation> assets = GetAssets(T::GetStaticClass());
|
||||
return *(Array<T*, HeapAllocation>*) & assets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw dictionary of assets (loaded or during load).
|
||||
|
||||
@@ -478,16 +478,23 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
|
||||
}
|
||||
|
||||
// Check if restore local changes on asset reimport
|
||||
constexpr bool RestoreModelOptionsOnReimport = true;
|
||||
constexpr bool RestoreAnimEventsOnReimport = true;
|
||||
const bool restoreModelOptions = RestoreModelOptionsOnReimport && (options.Type == ModelTool::ModelType::Model || options.Type == ModelTool::ModelType::SkinnedModel);
|
||||
const bool restoreMaterials = options.RestoreMaterialsOnReimport && data->Materials.HasItems();
|
||||
const bool restoreAnimEvents = RestoreAnimEventsOnReimport && options.Type == ModelTool::ModelType::Animation && data->Animations.HasItems();
|
||||
if ((restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath))
|
||||
if ((restoreModelOptions || restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath))
|
||||
{
|
||||
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
|
||||
if (asset && !asset->WaitForLoaded())
|
||||
{
|
||||
auto* model = ScriptingObject::Cast<ModelBase>(asset);
|
||||
auto* animation = ScriptingObject::Cast<Animation>(asset);
|
||||
if (restoreModelOptions && model)
|
||||
{
|
||||
// Copy general properties
|
||||
data->MinScreenSize = model->MinScreenSize;
|
||||
}
|
||||
if (restoreMaterials && model)
|
||||
{
|
||||
// Copy material settings
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
#include "HashSetBase.h"
|
||||
|
||||
template<typename KeyType, typename ValueType, typename AllocationType>
|
||||
class ConcurrentDictionary;
|
||||
|
||||
/// <summary>
|
||||
/// Describes single portion of space for the key and value pair in a hash map.
|
||||
/// </summary>
|
||||
@@ -13,6 +16,7 @@ struct DictionaryBucket
|
||||
friend Memory;
|
||||
friend HashSetBase<AllocationType, DictionaryBucket>;
|
||||
friend Dictionary<KeyType, ValueType, AllocationType>;
|
||||
friend ConcurrentDictionary<KeyType, ValueType, AllocationType>;
|
||||
|
||||
/// <summary>The key.</summary>
|
||||
KeyType Key;
|
||||
|
||||
@@ -57,5 +57,5 @@
|
||||
#define API_PARAM(...)
|
||||
#define API_TYPEDEF(...)
|
||||
#define API_INJECT_CODE(...)
|
||||
#define API_AUTO_SERIALIZATION(...) public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
#define API_AUTO_SERIALIZATION(...) public: bool ShouldSerialize(const void* otherObj) const override; void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
#define DECLARE_SCRIPTING_TYPE_MINIMAL(type) public: friend class type##Internal; static struct ScriptingTypeInitializer TypeInitializer;
|
||||
|
||||
@@ -36,6 +36,13 @@ public:
|
||||
/// </summary>
|
||||
virtual ~ISerializable() = default;
|
||||
|
||||
/// <summary>
|
||||
/// Compares with other instance to decide whether serialize this instance (eg. any field orp property is modified). Used to skip object serialization if not needed.
|
||||
/// </summary>
|
||||
/// <param name="otherObj">The instance of the object (always valid) to compare with to decide whether serialize this instance.</param>
|
||||
/// <returns>True if any field or property is modified compared to the other object instance, otherwise false.</returns>
|
||||
virtual bool ShouldSerialize(const void* otherObj) const { return true; }
|
||||
|
||||
/// <summary>
|
||||
/// Serializes object to the output stream compared to the values of the other object instance (eg. default class object). If other object is null then serialize all properties.
|
||||
/// </summary>
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.ColorConverter))]
|
||||
#endif
|
||||
partial struct Color
|
||||
partial struct Color : Json.ICustomValueEquals
|
||||
{
|
||||
/// <summary>
|
||||
/// The size of the <see cref="Color" /> type, in bytes.
|
||||
@@ -196,6 +196,13 @@ namespace FlaxEngine
|
||||
A = values[3];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Color)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object value)
|
||||
{
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double2Converter))]
|
||||
#endif
|
||||
partial struct Double2 : IEquatable<Double2>, IFormattable
|
||||
partial struct Double2 : IEquatable<Double2>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
|
||||
|
||||
@@ -1574,6 +1574,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Double2)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Double2" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double3Converter))]
|
||||
#endif
|
||||
partial struct Double3 : IEquatable<Double3>, IFormattable
|
||||
partial struct Double3 : IEquatable<Double3>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
|
||||
|
||||
@@ -1872,6 +1872,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Double3)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Double3" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double4Converter))]
|
||||
#endif
|
||||
partial struct Double4 : IEquatable<Double4>, IFormattable
|
||||
partial struct Double4 : IEquatable<Double4>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
|
||||
|
||||
@@ -1372,6 +1372,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Double4)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Double4" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float2Converter))]
|
||||
#endif
|
||||
partial struct Float2 : IEquatable<Float2>, IFormattable
|
||||
partial struct Float2 : IEquatable<Float2>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
|
||||
|
||||
@@ -1650,6 +1650,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Float2)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Float2" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float3Converter))]
|
||||
#endif
|
||||
partial struct Float3 : IEquatable<Float3>, IFormattable
|
||||
partial struct Float3 : IEquatable<Float3>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
|
||||
|
||||
@@ -1904,6 +1904,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Float3)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Float3" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float4Converter))]
|
||||
#endif
|
||||
partial struct Float4 : IEquatable<Float4>, IFormattable
|
||||
partial struct Float4 : IEquatable<Float4>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
|
||||
|
||||
@@ -1412,6 +1412,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Float4)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Float4" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Math.h"
|
||||
#include "Vector2.h"
|
||||
#include "Vector3.h"
|
||||
#include "Vector4.h"
|
||||
|
||||
/// <summary>
|
||||
/// Half-precision 16 bit floating point number consisting of a sign bit, a 5 bit biased exponent, and a 10 bit mantissa
|
||||
@@ -248,6 +249,19 @@ public:
|
||||
explicit Half4(const Color& c);
|
||||
explicit Half4(const Rectangle& rect);
|
||||
|
||||
operator Float2() const
|
||||
{
|
||||
return ToFloat2();
|
||||
}
|
||||
operator Float3() const
|
||||
{
|
||||
return ToFloat3();
|
||||
}
|
||||
operator Float4() const
|
||||
{
|
||||
return ToFloat4();
|
||||
}
|
||||
|
||||
public:
|
||||
Float2 ToFloat2() const;
|
||||
Float3 ToFloat3() const;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int2Converter))]
|
||||
#endif
|
||||
partial struct Int2 : IEquatable<Int2>, IFormattable
|
||||
partial struct Int2 : IEquatable<Int2>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0} Y:{1}";
|
||||
|
||||
@@ -940,6 +940,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Int2)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Int2" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int3Converter))]
|
||||
#endif
|
||||
partial struct Int3 : IEquatable<Int3>, IFormattable
|
||||
partial struct Int3 : IEquatable<Int3>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0} Y:{1} Z:{2}";
|
||||
|
||||
@@ -1023,6 +1023,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Int3)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Int3" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int4Converter))]
|
||||
#endif
|
||||
partial struct Int4 : IEquatable<Int4>, IFormattable
|
||||
partial struct Int4 : IEquatable<Int4>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0} Y:{1} Z:{2} W:{3}";
|
||||
|
||||
@@ -881,6 +881,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Int4)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Int4" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -41,16 +41,6 @@ FloatR10G10B10A2::FloatR10G10B10A2(const float* values)
|
||||
{
|
||||
}
|
||||
|
||||
FloatR10G10B10A2::operator Float3() const
|
||||
{
|
||||
return ToFloat3();
|
||||
}
|
||||
|
||||
FloatR10G10B10A2::operator Float4() const
|
||||
{
|
||||
return ToFloat4();
|
||||
}
|
||||
|
||||
Float3 FloatR10G10B10A2::ToFloat3() const
|
||||
{
|
||||
Float3 vectorOut;
|
||||
|
||||
@@ -40,9 +40,14 @@ struct FLAXENGINE_API FloatR10G10B10A2
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
operator Float3() const;
|
||||
operator Float4() const;
|
||||
operator Float3() const
|
||||
{
|
||||
return ToFloat3();
|
||||
}
|
||||
operator Float4() const
|
||||
{
|
||||
return ToFloat4();
|
||||
}
|
||||
|
||||
FloatR10G10B10A2& operator=(const FloatR10G10B10A2& other)
|
||||
{
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.QuaternionConverter))]
|
||||
#endif
|
||||
partial struct Quaternion : IEquatable<Quaternion>, IFormattable
|
||||
partial struct Quaternion : IEquatable<Quaternion>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
|
||||
|
||||
@@ -1681,6 +1681,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Quaternion)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether one quaternion is near another quaternion.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,7 +6,7 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
partial struct Rectangle : IEquatable<Rectangle>
|
||||
partial struct Rectangle : IEquatable<Rectangle>, Json.ICustomValueEquals
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Rectangle"/> which represents an empty space.
|
||||
@@ -523,6 +523,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Rectangle)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Runtime.InteropServices;
|
||||
namespace FlaxEngine
|
||||
{
|
||||
[Serializable]
|
||||
partial struct Transform : IEquatable<Transform>, IFormattable
|
||||
partial struct Transform : IEquatable<Transform>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "Translation:{0} Orientation:{1} Scale:{2}";
|
||||
|
||||
@@ -673,6 +673,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Transform)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether one transform is near another transform.
|
||||
/// </summary>
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector2Converter))]
|
||||
#endif
|
||||
public unsafe partial struct Vector2 : IEquatable<Vector2>, IFormattable
|
||||
public unsafe partial struct Vector2 : IEquatable<Vector2>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
|
||||
|
||||
@@ -954,6 +954,33 @@ namespace FlaxEngine
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a spherical linear interpolation between two vectors.
|
||||
/// </summary>
|
||||
/// <param name="start">Start vector.</param>
|
||||
/// <param name="end">End vector.</param>
|
||||
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
|
||||
/// <param name="result">>When the method completes, contains the linear interpolation of the two vectors.</param>
|
||||
public static void Slerp(ref Vector2 start, ref Vector2 end, float amount, out Vector2 result)
|
||||
{
|
||||
var dot = Mathr.Clamp(Dot(start, end), -1.0f, 1.0f);
|
||||
var theta = Mathr.Acos(dot) * amount;
|
||||
Vector2 relativeVector = (end - start * dot).Normalized;
|
||||
result = ((start * Mathr.Cos(theta)) + (relativeVector * Mathr.Sin(theta)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a spherical linear interpolation between two vectors.
|
||||
/// </summary>
|
||||
/// <param name="start">Start vector.</param>
|
||||
/// <param name="end">End vector.</param>
|
||||
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
|
||||
public static Vector2 Slerp(Vector2 start, Vector2 end, float amount)
|
||||
{
|
||||
Slerp(ref start, ref end, amount, out Vector2 result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a gradual change of a vector towards a specified target over time
|
||||
/// </summary>
|
||||
@@ -1774,6 +1801,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Vector2)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Vector2" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -558,6 +558,24 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
// Performs a spherical linear interpolation between two vectors.
|
||||
static void Slerp(const Vector2Base& start, const Vector2Base& end, T amount, Vector2Base& result)
|
||||
{
|
||||
T dot = Math::Clamp(Dot(start, end), -1.0f, 1.0f);
|
||||
T theta = Math::Acos(dot) * amount;
|
||||
Vector2Base relativeVector = end - start * dot;
|
||||
relativeVector.Normalize();
|
||||
result = ((start * Math::Cos(theta)) + (relativeVector * Math::Sin(theta)));
|
||||
}
|
||||
|
||||
// Performs a spherical linear interpolation between two vectors.
|
||||
static Vector2Base Slerp(const Vector2Base& start, const Vector2Base& end, T amount)
|
||||
{
|
||||
Vector2Base result;
|
||||
Slerp(start, end, amount, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Calculates the area of the triangle.
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector3Converter))]
|
||||
#endif
|
||||
public unsafe partial struct Vector3 : IEquatable<Vector3>, IFormattable
|
||||
public unsafe partial struct Vector3 : IEquatable<Vector3>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
|
||||
|
||||
@@ -1043,6 +1043,33 @@ namespace FlaxEngine
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a spherical linear interpolation between two vectors.
|
||||
/// </summary>
|
||||
/// <param name="start">Start vector.</param>
|
||||
/// <param name="end">End vector.</param>
|
||||
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
|
||||
/// <param name="result">When the method completes, contains the linear interpolation of the two vectors.</param>
|
||||
public static void Slerp(ref Vector3 start, ref Vector3 end, float amount, out Vector3 result)
|
||||
{
|
||||
var dot = Mathr.Clamp(Dot(start, end), -1.0f, 1.0f);
|
||||
var theta = Mathr.Acos(dot) * amount;
|
||||
Vector3 relativeVector = (end - start * dot).Normalized;
|
||||
result = ((start * Mathr.Cos(theta)) + (relativeVector * Mathr.Sin(theta)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a spherical linear interpolation between two vectors.
|
||||
/// </summary>
|
||||
/// <param name="start">Start vector.</param>
|
||||
/// <param name="end">End vector.</param>
|
||||
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
|
||||
public static Vector3 Slerp(Vector3 start, Vector3 end, float amount)
|
||||
{
|
||||
Slerp(ref start, ref end, amount, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a gradual change of a vector towards a specified target over time
|
||||
/// </summary>
|
||||
@@ -2133,6 +2160,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Vector3)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Vector3" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -686,6 +686,24 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
// Performs a spherical linear interpolation between two vectors.
|
||||
static void Slerp(const Vector3Base& start, const Vector3Base& end, T amount, Vector3Base& result)
|
||||
{
|
||||
T dot = Math::Clamp(Dot(start, end), -1.0f, 1.0f);
|
||||
T theta = Math::Acos(dot) * amount;
|
||||
Vector3Base relativeVector = end - start * dot;
|
||||
relativeVector.Normalize();
|
||||
result = ((start * Math::Cos(theta)) + (relativeVector * Math::Sin(theta)));
|
||||
}
|
||||
|
||||
// Performs a spherical linear interpolation between two vectors.
|
||||
static Vector3Base Slerp(const Vector3Base& start, const Vector3Base& end, T amount)
|
||||
{
|
||||
Vector3Base result;
|
||||
Slerp(start, end, amount, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Performs a cubic interpolation between two vectors.
|
||||
static void SmoothStep(const Vector3Base& start, const Vector3Base& end, T amount, Vector3Base& result)
|
||||
{
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace FlaxEngine
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector4Converter))]
|
||||
#endif
|
||||
public partial struct Vector4 : IEquatable<Vector4>, IFormattable
|
||||
public partial struct Vector4 : IEquatable<Vector4>, IFormattable, Json.ICustomValueEquals
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
|
||||
|
||||
@@ -260,6 +260,19 @@ namespace FlaxEngine
|
||||
/// </summary>
|
||||
public bool IsNormalized => Mathr.Abs((X * X + Y * Y + Z * Z + W * W) - 1.0f) < 1e-4f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the normalized vector. Returned vector has length equal 1.
|
||||
/// </summary>
|
||||
public Vector4 Normalized
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector4 vector4 = this;
|
||||
vector4.Normalize();
|
||||
return vector4;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicting whether this vector is zero
|
||||
/// </summary>
|
||||
@@ -878,6 +891,33 @@ namespace FlaxEngine
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a spherical linear interpolation between two vectors.
|
||||
/// </summary>
|
||||
/// <param name="start">Start vector.</param>
|
||||
/// <param name="end">End vector.</param>
|
||||
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
|
||||
/// <param name="result">When the method completes, contains the linear interpolation of the two vectors.</param>
|
||||
public static void Slerp(ref Vector4 start, ref Vector4 end, Real amount, out Vector4 result)
|
||||
{
|
||||
var dot = Mathr.Clamp(Dot(start, end), -1.0f, 1.0f);
|
||||
var theta = Mathr.Acos(dot) * amount;
|
||||
Vector4 relativeVector = (end - start * dot).Normalized;
|
||||
result = ((start * Mathr.Cos(theta)) + (relativeVector * Mathr.Sin(theta)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a spherical linear interpolation between two vectors.
|
||||
/// </summary>
|
||||
/// <param name="start">Start vector.</param>
|
||||
/// <param name="end">End vector.</param>
|
||||
/// <param name="amount">Value between 0 and 1 indicating the weight of <paramref name="end" />.</param>
|
||||
public static Vector4 Slerp(Vector4 start, Vector4 end, Real amount)
|
||||
{
|
||||
Slerp(ref start, ref end, amount, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a cubic interpolation between two vectors.
|
||||
/// </summary>
|
||||
@@ -1486,6 +1526,13 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (Vector4)other;
|
||||
return Equals(ref o);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Vector4" /> is equal to this instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -129,6 +129,12 @@ public:
|
||||
FLAXENGINE_API String ToString() const;
|
||||
|
||||
public:
|
||||
// Gets a value indicting whether this instance is normalized.
|
||||
bool IsNormalized() const
|
||||
{
|
||||
return Math::Abs((X * X + Y * Y + Z * Z + W * W) - 1.0f) < 1e-4f;
|
||||
}
|
||||
|
||||
// Gets a value indicting whether this vector is zero.
|
||||
bool IsZero() const
|
||||
{
|
||||
@@ -219,6 +225,45 @@ public:
|
||||
return Vector4Base(-X, -Y, -Z, -W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a normalized vector that has length equal to 1.
|
||||
/// </summary>
|
||||
Vector4Base GetNormalized() const
|
||||
{
|
||||
Vector4Base result(X, Y, Z, W);
|
||||
result.Normalize();
|
||||
return result;
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Performs vector normalization (scales vector up to unit length).
|
||||
/// </summary>
|
||||
void Normalize()
|
||||
{
|
||||
const T length = Math::Sqrt(X * X + Y * Y + Z * Z + W * W);
|
||||
if (length >= ZeroTolerance)
|
||||
{
|
||||
const T inv = (T)1.0f / length;
|
||||
X *= inv;
|
||||
Y *= inv;
|
||||
Z *= inv;
|
||||
W *= inv;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs fast vector normalization (scales vector up to unit length).
|
||||
/// </summary>
|
||||
void NormalizeFast()
|
||||
{
|
||||
const T inv = 1.0f / Math::Sqrt(X * X + Y * Y + Z * Z + W * W);
|
||||
X *= inv;
|
||||
Y *= inv;
|
||||
Z *= inv;
|
||||
W *= inv;
|
||||
}
|
||||
|
||||
public:
|
||||
Vector4Base operator+(const Vector4Base& b) const
|
||||
{
|
||||
@@ -469,6 +514,41 @@ public:
|
||||
result = Vector4Base(Math::Clamp(v.X, min.X, max.X), Math::Clamp(v.Y, min.Y, max.Y), Math::Clamp(v.Z, min.Z, max.Z), Math::Clamp(v.W, min.W, max.W));
|
||||
}
|
||||
|
||||
// Performs vector normalization (scales vector up to unit length).
|
||||
static Vector4Base Normalize(const Vector4Base& v)
|
||||
{
|
||||
Vector4Base r = v;
|
||||
const T length = Math::Sqrt(r.X * r.X + r.Y * r.Y + r.Z * r.Z + r.W * r.W);
|
||||
if (length >= ZeroTolerance)
|
||||
{
|
||||
const T inv = (T)1.0f / length;
|
||||
r.X *= inv;
|
||||
r.Y *= inv;
|
||||
r.Z *= inv;
|
||||
r.W *= inv;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Performs vector normalization (scales vector up to unit length). This is a faster version that does not perform check for length equal 0 (it assumes that input vector is not empty).
|
||||
static Vector4Base NormalizeFast(const Vector4Base& v)
|
||||
{
|
||||
const T inv = 1.0f / v.Length();
|
||||
return Vector4Base(v.X * inv, v.Y * inv, v.Z * inv, v.W * inv);
|
||||
}
|
||||
|
||||
// Performs vector normalization (scales vector up to unit length).
|
||||
static FORCE_INLINE void Normalize(const Vector4Base& input, Vector4Base& result)
|
||||
{
|
||||
result = Normalize(input);
|
||||
}
|
||||
|
||||
// Calculates the dot product of two vectors.
|
||||
FORCE_INLINE static T Dot(const Vector4Base& a, const Vector4Base& b)
|
||||
{
|
||||
return a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W;
|
||||
}
|
||||
|
||||
// Performs a linear interpolation between two vectors.
|
||||
static void Lerp(const Vector4Base& start, const Vector4Base& end, T amount, Vector4Base& result)
|
||||
{
|
||||
@@ -486,6 +566,24 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
// Performs a spherical linear interpolation between two vectors.
|
||||
static void Slerp(const Vector4Base& start, const Vector4Base& end, T amount, Vector4Base& result)
|
||||
{
|
||||
T dot = Math::Clamp(Dot(start, end), -1.0f, 1.0f);
|
||||
T theta = Math::Acos(dot) * amount;
|
||||
Vector4Base relativeVector = end - start * dot;
|
||||
relativeVector.Normalize();
|
||||
result = ((start * Math::Cos(theta)) + (relativeVector * Math::Sin(theta)));
|
||||
}
|
||||
|
||||
// Performs a spherical linear interpolation between two vectors.
|
||||
static Vector4Base Slerp(const Vector4Base& start, const Vector4Base& end, T amount)
|
||||
{
|
||||
Vector4Base result;
|
||||
Slerp(start, end, amount, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
FLAXENGINE_API static Vector4Base Transform(const Vector4Base& v, const Matrix& m);
|
||||
};
|
||||
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
#include "Engine/Core/Math/Ray.h"
|
||||
#include "Engine/Core/Math/Rectangle.h"
|
||||
#include "Engine/Core/Math/Transform.h"
|
||||
#include "Engine/Scripting/BinaryModule.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MUtils.h"
|
||||
@@ -88,6 +90,7 @@ static_assert((int32)VariantType::Types::MAX == ARRAY_COUNT(InBuiltTypesTypeName
|
||||
VariantType::VariantType(Types type, const StringView& typeName)
|
||||
{
|
||||
Type = type;
|
||||
StaticName = 0;
|
||||
TypeName = nullptr;
|
||||
const int32 length = typeName.Length();
|
||||
if (length)
|
||||
@@ -98,32 +101,41 @@ VariantType::VariantType(Types type, const StringView& typeName)
|
||||
}
|
||||
}
|
||||
|
||||
VariantType::VariantType(Types type, const StringAnsiView& typeName)
|
||||
VariantType::VariantType(Types type, const StringAnsiView& typeName, bool staticName)
|
||||
{
|
||||
Type = type;
|
||||
TypeName = nullptr;
|
||||
int32 length = typeName.Length();
|
||||
if (length)
|
||||
StaticName = staticName && (typeName.HasChars() && typeName[typeName.Length()] == 0); // Require string to be null-terminated (not fully safe check)
|
||||
if (staticName)
|
||||
{
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, typeName.Get(), length);
|
||||
TypeName[length] = 0;
|
||||
TypeName = (char*)typeName.Get();
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeName = nullptr;
|
||||
int32 length = typeName.Length();
|
||||
if (length)
|
||||
{
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, typeName.Get(), length);
|
||||
TypeName[length] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VariantType::VariantType(Types type, const ScriptingType& sType)
|
||||
: VariantType(type)
|
||||
{
|
||||
SetTypeName(sType);
|
||||
}
|
||||
|
||||
VariantType::VariantType(Types type, const MClass* klass)
|
||||
{
|
||||
Type = type;
|
||||
StaticName = false;
|
||||
TypeName = nullptr;
|
||||
#if USE_CSHARP
|
||||
if (klass)
|
||||
{
|
||||
const StringAnsiView typeName = klass->GetFullName();
|
||||
const int32 length = typeName.Length();
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, typeName.Get(), length);
|
||||
TypeName[length] = 0;
|
||||
}
|
||||
SetTypeName(*klass);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -190,9 +202,9 @@ VariantType::VariantType(const StringAnsiView& typeName)
|
||||
if (const auto mclass = Scripting::FindClass(typeName))
|
||||
{
|
||||
if (mclass->IsEnum())
|
||||
new(this) VariantType(Enum, typeName);
|
||||
new(this) VariantType(Enum, mclass);
|
||||
else
|
||||
new(this) VariantType(ManagedObject, typeName);
|
||||
new(this) VariantType(ManagedObject, mclass);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -204,36 +216,48 @@ VariantType::VariantType(const StringAnsiView& typeName)
|
||||
VariantType::VariantType(const VariantType& other)
|
||||
{
|
||||
Type = other.Type;
|
||||
TypeName = nullptr;
|
||||
const int32 length = StringUtils::Length(other.TypeName);
|
||||
if (length)
|
||||
StaticName = other.StaticName;
|
||||
if (StaticName)
|
||||
{
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, other.TypeName, length);
|
||||
TypeName[length] = 0;
|
||||
TypeName = other.TypeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeName = nullptr;
|
||||
const int32 length = StringUtils::Length(other.TypeName);
|
||||
if (length)
|
||||
{
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, other.TypeName, length);
|
||||
TypeName[length] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VariantType::VariantType(VariantType&& other) noexcept
|
||||
{
|
||||
Type = other.Type;
|
||||
StaticName = other.StaticName;
|
||||
TypeName = other.TypeName;
|
||||
other.Type = Null;
|
||||
other.TypeName = nullptr;
|
||||
other.StaticName = 0;
|
||||
}
|
||||
|
||||
VariantType& VariantType::operator=(const Types& type)
|
||||
{
|
||||
Type = type;
|
||||
Allocator::Free(TypeName);
|
||||
if (StaticName)
|
||||
Allocator::Free(TypeName);
|
||||
TypeName = nullptr;
|
||||
StaticName = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
VariantType& VariantType::operator=(VariantType&& other)
|
||||
{
|
||||
ASSERT(this != &other);
|
||||
Swap(Type, other.Type);
|
||||
Swap(Packed, other.Packed);
|
||||
Swap(TypeName, other.TypeName);
|
||||
return *this;
|
||||
}
|
||||
@@ -242,14 +266,23 @@ VariantType& VariantType::operator=(const VariantType& other)
|
||||
{
|
||||
ASSERT(this != &other);
|
||||
Type = other.Type;
|
||||
Allocator::Free(TypeName);
|
||||
TypeName = nullptr;
|
||||
const int32 length = StringUtils::Length(other.TypeName);
|
||||
if (length)
|
||||
if (StaticName)
|
||||
Allocator::Free(TypeName);
|
||||
StaticName = other.StaticName;
|
||||
if (StaticName)
|
||||
{
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, other.TypeName, length);
|
||||
TypeName[length] = 0;
|
||||
TypeName = other.TypeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeName = nullptr;
|
||||
const int32 length = StringUtils::Length(other.TypeName);
|
||||
if (length)
|
||||
{
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, other.TypeName, length);
|
||||
TypeName[length] = 0;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -283,24 +316,45 @@ void VariantType::SetTypeName(const StringView& typeName)
|
||||
{
|
||||
if (StringUtils::Length(TypeName) != typeName.Length())
|
||||
{
|
||||
Allocator::Free(TypeName);
|
||||
if (StaticName)
|
||||
Allocator::Free(TypeName);
|
||||
StaticName = 0;
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(typeName.Length() + 1));
|
||||
TypeName[typeName.Length()] = 0;
|
||||
}
|
||||
StringUtils::ConvertUTF162ANSI(typeName.Get(), TypeName, typeName.Length());
|
||||
}
|
||||
|
||||
void VariantType::SetTypeName(const StringAnsiView& typeName)
|
||||
void VariantType::SetTypeName(const StringAnsiView& typeName, bool staticName)
|
||||
{
|
||||
if (StringUtils::Length(TypeName) != typeName.Length())
|
||||
if (StringUtils::Length(TypeName) != typeName.Length() || StaticName != staticName)
|
||||
{
|
||||
Allocator::Free(TypeName);
|
||||
if (StaticName)
|
||||
Allocator::Free(TypeName);
|
||||
StaticName = staticName;
|
||||
if (staticName)
|
||||
{
|
||||
TypeName = (char*)typeName.Get();
|
||||
return;
|
||||
}
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(typeName.Length() + 1));
|
||||
TypeName[typeName.Length()] = 0;
|
||||
}
|
||||
Platform::MemoryCopy(TypeName, typeName.Get(), typeName.Length());
|
||||
}
|
||||
|
||||
void VariantType::SetTypeName(const ScriptingType& type)
|
||||
{
|
||||
SetTypeName(type.Fullname, type.Module->CanReload);
|
||||
}
|
||||
|
||||
void VariantType::SetTypeName(const MClass& klass)
|
||||
{
|
||||
#if USE_CSHARP
|
||||
SetTypeName(klass.GetFullName(), klass.GetAssembly()->CanReload());
|
||||
#endif
|
||||
}
|
||||
|
||||
const char* VariantType::GetTypeName() const
|
||||
{
|
||||
if (TypeName)
|
||||
@@ -322,6 +376,29 @@ VariantType VariantType::GetElementType() const
|
||||
return VariantType();
|
||||
}
|
||||
|
||||
void VariantType::Inline()
|
||||
{
|
||||
// Check if the typename comes from static assembly which can be used to inline name instead of dynamic memory allocation
|
||||
StringAnsiView typeName(TypeName);
|
||||
auto& modules = BinaryModule::GetModules();
|
||||
for (auto module : modules)
|
||||
{
|
||||
int32 typeIndex;
|
||||
if (!module->CanReload && module->FindScriptingType(typeName, typeIndex))
|
||||
{
|
||||
ScriptingTypeHandle typeHandle(module, typeIndex);
|
||||
SetTypeName(typeHandle.GetType().Fullname, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_CSHARP
|
||||
// Try with C#-only types
|
||||
if (const auto mclass = Scripting::FindClass(TypeName))
|
||||
SetTypeName(*mclass);
|
||||
#endif
|
||||
}
|
||||
|
||||
::String VariantType::ToString() const
|
||||
{
|
||||
::String result;
|
||||
@@ -632,8 +709,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);
|
||||
Type.SetTypeName(v->GetType());
|
||||
v->Deleted.Bind<Variant, &Variant::OnObjectDeleted>(this);
|
||||
}
|
||||
}
|
||||
@@ -644,9 +720,8 @@ 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();
|
||||
Type.SetTypeName(v->GetType());
|
||||
v->OnUnloaded.Bind<Variant, &Variant::OnAssetUnloaded>(this);
|
||||
}
|
||||
}
|
||||
@@ -3007,16 +3082,16 @@ Variant Variant::NewValue(const StringAnsiView& typeName)
|
||||
switch (type.Type)
|
||||
{
|
||||
case ScriptingTypes::Script:
|
||||
v.SetType(VariantType(VariantType::Object, typeName));
|
||||
v.SetType(VariantType(VariantType::Object, type));
|
||||
v.AsObject = type.Script.Spawn(ScriptingObjectSpawnParams(Guid::New(), typeHandle));
|
||||
if (v.AsObject)
|
||||
v.AsObject->Deleted.Bind<Variant, &Variant::OnObjectDeleted>(&v);
|
||||
break;
|
||||
case ScriptingTypes::Structure:
|
||||
v.SetType(VariantType(VariantType::Structure, typeName));
|
||||
v.SetType(VariantType(VariantType::Structure, type));
|
||||
break;
|
||||
case ScriptingTypes::Enum:
|
||||
v.SetType(VariantType(VariantType::Enum, typeName));
|
||||
v.SetType(VariantType(VariantType::Enum, type));
|
||||
v.AsEnum = 0;
|
||||
break;
|
||||
default:
|
||||
@@ -3030,16 +3105,16 @@ Variant Variant::NewValue(const StringAnsiView& typeName)
|
||||
// Fallback to C#-only types
|
||||
if (mclass->IsEnum())
|
||||
{
|
||||
v.SetType(VariantType(VariantType::Enum, typeName));
|
||||
v.SetType(VariantType(VariantType::Enum, mclass));
|
||||
v.AsEnum = 0;
|
||||
}
|
||||
else if (mclass->IsValueType())
|
||||
{
|
||||
v.SetType(VariantType(VariantType::Structure, typeName));
|
||||
v.SetType(VariantType(VariantType::Structure, mclass));
|
||||
}
|
||||
else
|
||||
{
|
||||
v.SetType(VariantType(VariantType::ManagedObject, typeName));
|
||||
v.SetType(VariantType(VariantType::ManagedObject, mclass));
|
||||
MObject* instance = mclass->CreateInstance();
|
||||
if (instance)
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ struct ScriptingTypeHandle;
|
||||
/// </summary>
|
||||
API_STRUCT(InBuild) struct FLAXENGINE_API VariantType
|
||||
{
|
||||
enum Types
|
||||
enum Types : uint8
|
||||
{
|
||||
Null = 0,
|
||||
Void,
|
||||
@@ -80,10 +80,22 @@ API_STRUCT(InBuild) struct FLAXENGINE_API VariantType
|
||||
};
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The type of the variant.
|
||||
/// </summary>
|
||||
Types Type;
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the variant.
|
||||
/// </summary>
|
||||
Types Type;
|
||||
|
||||
/// <summary>
|
||||
/// Internal flag used to indicate that pointer to TypeName has been linked from a static/external memory that is stable (eg. ScriptingType or MClass). Allows avoiding dynamic memory allocation.
|
||||
/// </summary>
|
||||
uint8 StaticName : 1;
|
||||
};
|
||||
uint16 Packed;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The optional additional full name of the scripting type. Used for Asset, Object, Enum, Structure types to describe type precisely.
|
||||
@@ -94,17 +106,20 @@ public:
|
||||
FORCE_INLINE VariantType()
|
||||
{
|
||||
Type = Null;
|
||||
StaticName = 0;
|
||||
TypeName = nullptr;
|
||||
}
|
||||
|
||||
FORCE_INLINE explicit VariantType(Types type)
|
||||
{
|
||||
Type = type;
|
||||
StaticName = 0;
|
||||
TypeName = nullptr;
|
||||
}
|
||||
|
||||
explicit VariantType(Types type, const StringView& typeName);
|
||||
explicit VariantType(Types type, const StringAnsiView& typeName);
|
||||
explicit VariantType(Types type, const StringAnsiView& typeName, bool staticName = false);
|
||||
explicit VariantType(Types type, const ScriptingType& sType);
|
||||
explicit VariantType(Types type, const MClass* klass);
|
||||
explicit VariantType(const StringAnsiView& typeName);
|
||||
VariantType(const VariantType& other);
|
||||
@@ -112,7 +127,8 @@ public:
|
||||
|
||||
FORCE_INLINE ~VariantType()
|
||||
{
|
||||
Allocator::Free(TypeName);
|
||||
if (!StaticName)
|
||||
Allocator::Free(TypeName);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -130,9 +146,13 @@ public:
|
||||
|
||||
public:
|
||||
void SetTypeName(const StringView& typeName);
|
||||
void SetTypeName(const StringAnsiView& typeName);
|
||||
void SetTypeName(const StringAnsiView& typeName, bool staticName = false);
|
||||
void SetTypeName(const ScriptingType& type);
|
||||
void SetTypeName(const MClass& klass);
|
||||
const char* GetTypeName() const;
|
||||
VariantType GetElementType() const;
|
||||
// Drops custom type name into the name allocated by the scripting module to reduce memory allocations when referencing types.
|
||||
void Inline();
|
||||
::String ToString() const;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Threading/Task.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "Engine/Scripting/BinaryModule.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
|
||||
@@ -219,6 +220,7 @@ namespace
|
||||
if (module == GetBinaryModuleCorlib())
|
||||
return;
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(EngineDebug);
|
||||
|
||||
#if USE_CSHARP
|
||||
if (auto* managedModule = dynamic_cast<ManagedBinaryModule*>(module))
|
||||
@@ -381,6 +383,7 @@ DebugCommandsService DebugCommandsServiceInstance;
|
||||
|
||||
void DebugCommands::Execute(StringView command)
|
||||
{
|
||||
PROFILE_MEM(EngineDebug);
|
||||
// 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;
|
||||
@@ -423,6 +426,7 @@ void DebugCommands::Search(StringView searchText, Array<StringView>& matches, bo
|
||||
{
|
||||
if (searchText.IsEmpty())
|
||||
return;
|
||||
PROFILE_MEM(EngineDebug);
|
||||
// TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
|
||||
String searchTextCopy = searchText;
|
||||
searchText = searchTextCopy;
|
||||
|
||||
@@ -480,6 +480,7 @@ DebugDrawCall WriteLists(int32& vertexCounter, const Array<T>& listA, const Arra
|
||||
|
||||
FORCE_INLINE DebugTriangle* AppendTriangles(int32 count, float duration, bool depthTest)
|
||||
{
|
||||
PROFILE_MEM(EngineDebug);
|
||||
Array<DebugTriangle>* list;
|
||||
if (depthTest)
|
||||
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
|
||||
@@ -492,6 +493,7 @@ FORCE_INLINE DebugTriangle* AppendTriangles(int32 count, float duration, bool de
|
||||
|
||||
FORCE_INLINE DebugTriangle* AppendWireTriangles(int32 count, float duration, bool depthTest)
|
||||
{
|
||||
PROFILE_MEM(EngineDebug);
|
||||
Array<DebugTriangle>* list;
|
||||
if (depthTest)
|
||||
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultWireTriangles : &Context->DebugDrawDepthTest.OneFrameWireTriangles;
|
||||
@@ -539,7 +541,7 @@ DebugDrawService DebugDrawServiceInstance;
|
||||
|
||||
bool DebugDrawService::Init()
|
||||
{
|
||||
PROFILE_MEM(Graphics);
|
||||
PROFILE_MEM(EngineDebug);
|
||||
Context = &GlobalContext;
|
||||
|
||||
// Init wireframe sphere cache
|
||||
@@ -658,7 +660,7 @@ void DebugDrawService::Update()
|
||||
}
|
||||
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(Graphics);
|
||||
PROFILE_MEM(EngineDebug);
|
||||
|
||||
// Update lists
|
||||
float deltaTime = Time::Update.DeltaTime.GetTotalSeconds();
|
||||
@@ -1114,6 +1116,7 @@ void DebugDraw::DrawRay(const Ray& ray, const Color& color, float length, float
|
||||
|
||||
void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration, bool depthTest)
|
||||
{
|
||||
PROFILE_MEM(EngineDebug);
|
||||
const Float3 startF = start - Context->Origin, endF = end - Context->Origin;
|
||||
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
|
||||
if (duration > 0)
|
||||
@@ -1132,6 +1135,7 @@ void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color&
|
||||
|
||||
void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& startColor, const Color& endColor, float duration, bool depthTest)
|
||||
{
|
||||
PROFILE_MEM(EngineDebug);
|
||||
const Float3 startF = start - Context->Origin, endF = end - Context->Origin;
|
||||
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
|
||||
if (duration > 0)
|
||||
@@ -1161,6 +1165,7 @@ void DebugDraw::DrawLines(const Span<Float3>& lines, const Matrix& transform, co
|
||||
}
|
||||
|
||||
// Draw lines
|
||||
PROFILE_MEM(EngineDebug);
|
||||
const Float3* p = lines.Get();
|
||||
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
|
||||
const Matrix transformF = transform * Matrix::Translation(-Context->Origin);
|
||||
@@ -1200,6 +1205,7 @@ void DebugDraw::DrawLines(GPUBuffer* lines, const Matrix& transform, float durat
|
||||
}
|
||||
|
||||
// Draw lines
|
||||
PROFILE_MEM(EngineDebug);
|
||||
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
|
||||
auto& geometry = debugDrawData.GeometryBuffers.AddOne();
|
||||
geometry.Buffer = lines;
|
||||
@@ -1224,6 +1230,7 @@ void DebugDraw::DrawLines(const Span<Double3>& lines, const Matrix& transform, c
|
||||
}
|
||||
|
||||
// Draw lines
|
||||
PROFILE_MEM(EngineDebug);
|
||||
const Double3* p = lines.Get();
|
||||
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
|
||||
const Matrix transformF = transform * Matrix::Translation(-Context->Origin);
|
||||
@@ -1270,6 +1277,7 @@ void DebugDraw::DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3&
|
||||
const float segmentCountInv = 1.0f / (float)segmentCount;
|
||||
|
||||
// Draw segmented curve from lines
|
||||
PROFILE_MEM(EngineDebug);
|
||||
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
|
||||
if (duration > 0)
|
||||
{
|
||||
@@ -1310,6 +1318,7 @@ void DebugDraw::DrawWireBox(const BoundingBox& box, const Color& color, float du
|
||||
c -= Context->Origin;
|
||||
|
||||
// Draw lines
|
||||
PROFILE_MEM(EngineDebug);
|
||||
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
|
||||
if (duration > 0)
|
||||
{
|
||||
@@ -1344,6 +1353,7 @@ void DebugDraw::DrawWireFrustum(const BoundingFrustum& frustum, const Color& col
|
||||
c -= Context->Origin;
|
||||
|
||||
// Draw lines
|
||||
PROFILE_MEM(EngineDebug);
|
||||
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
|
||||
if (duration > 0)
|
||||
{
|
||||
@@ -1378,6 +1388,7 @@ void DebugDraw::DrawWireBox(const OrientedBoundingBox& box, const Color& color,
|
||||
c -= Context->Origin;
|
||||
|
||||
// Draw lines
|
||||
PROFILE_MEM(EngineDebug);
|
||||
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
|
||||
if (duration > 0)
|
||||
{
|
||||
@@ -1419,6 +1430,7 @@ void DebugDraw::DrawWireSphere(const BoundingSphere& sphere, const Color& color,
|
||||
auto& cache = SphereCache[index];
|
||||
|
||||
// Draw lines of the unit sphere after linear transform
|
||||
PROFILE_MEM(EngineDebug);
|
||||
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
|
||||
if (duration > 0)
|
||||
{
|
||||
@@ -1454,6 +1466,7 @@ void DebugDraw::DrawSphere(const BoundingSphere& sphere, const Color& color, flo
|
||||
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
|
||||
else
|
||||
list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles;
|
||||
PROFILE_MEM(EngineDebug);
|
||||
list->EnsureCapacity(list->Count() + SphereTriangleCache.Count());
|
||||
|
||||
const Float3 centerF = sphere.Center - Context->Origin;
|
||||
@@ -1485,6 +1498,7 @@ void DebugDraw::DrawCircle(const Vector3& position, const Float3& normal, float
|
||||
Matrix::Multiply(scale, world, matrix);
|
||||
|
||||
// Draw lines of the unit circle after linear transform
|
||||
PROFILE_MEM(EngineDebug);
|
||||
Float3 prev = Float3::Transform(CircleCache[0], matrix);
|
||||
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
|
||||
for (int32 i = 1; i < DEBUG_DRAW_CIRCLE_VERTICES;)
|
||||
@@ -1515,6 +1529,7 @@ void DebugDraw::DrawWireTriangle(const Vector3& v0, const Vector3& v1, const Vec
|
||||
|
||||
void DebugDraw::DrawTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color, float duration, bool depthTest)
|
||||
{
|
||||
PROFILE_MEM(EngineDebug);
|
||||
DebugTriangle t;
|
||||
t.Color = Color32(color);
|
||||
t.TimeLeft = duration;
|
||||
@@ -1570,6 +1585,7 @@ void DebugDraw::DrawTriangles(GPUBuffer* triangles, const Matrix& transform, flo
|
||||
DebugLog::ThrowException("Cannot draw debug lines with incorrect amount of items in array");
|
||||
return;
|
||||
}
|
||||
PROFILE_MEM(EngineDebug);
|
||||
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
|
||||
auto& geometry = debugDrawData.GeometryBuffers.AddOne();
|
||||
geometry.Buffer = triangles;
|
||||
@@ -1859,6 +1875,7 @@ void DebugDraw::DrawWireCapsule(const Vector3& position, const Quaternion& orien
|
||||
Matrix::Multiply(rotation, translation, world);
|
||||
|
||||
// Write vertices
|
||||
PROFILE_MEM(EngineDebug);
|
||||
auto& debugDrawData = depthTest ? Context->DebugDrawDepthTest : Context->DebugDrawDefault;
|
||||
Color32 color32(color);
|
||||
if (duration > 0)
|
||||
@@ -1953,6 +1970,7 @@ namespace
|
||||
void DrawCylinder(Array<DebugTriangle>* list, const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color, float duration)
|
||||
{
|
||||
// Setup cache
|
||||
PROFILE_MEM(EngineDebug);
|
||||
Float3 CylinderCache[DEBUG_DRAW_CYLINDER_VERTICES];
|
||||
const float angleBetweenFacets = TWO_PI / DEBUG_DRAW_CYLINDER_RESOLUTION;
|
||||
const float verticalOffset = height * 0.5f;
|
||||
@@ -2024,6 +2042,7 @@ namespace
|
||||
|
||||
void DrawCone(Array<DebugTriangle>* list, const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, const Color& color, float duration)
|
||||
{
|
||||
PROFILE_MEM(EngineDebug);
|
||||
const float tolerance = 0.001f;
|
||||
const float angle1 = Math::Clamp(angleXY, tolerance, PI - tolerance);
|
||||
const float angle2 = Math::Clamp(angleXZ, tolerance, PI - tolerance);
|
||||
@@ -2113,6 +2132,7 @@ void DebugDraw::DrawArc(const Vector3& position, const Quaternion& orientation,
|
||||
{
|
||||
if (angle <= 0)
|
||||
return;
|
||||
PROFILE_MEM(EngineDebug);
|
||||
if (angle > TWO_PI)
|
||||
angle = TWO_PI;
|
||||
Array<DebugTriangle>* list;
|
||||
@@ -2145,6 +2165,7 @@ void DebugDraw::DrawWireArc(const Vector3& position, const Quaternion& orientati
|
||||
{
|
||||
if (angle <= 0)
|
||||
return;
|
||||
PROFILE_MEM(EngineDebug);
|
||||
if (angle > TWO_PI)
|
||||
angle = TWO_PI;
|
||||
const int32 resolution = Math::CeilToInt((float)DEBUG_DRAW_CONE_RESOLUTION / TWO_PI * angle);
|
||||
@@ -2211,6 +2232,7 @@ void DebugDraw::DrawBox(const BoundingBox& box, const Color& color, float durati
|
||||
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
|
||||
else
|
||||
list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles;
|
||||
PROFILE_MEM(EngineDebug);
|
||||
list->EnsureCapacity(list->Count() + 36);
|
||||
for (int i0 = 0; i0 < 36;)
|
||||
{
|
||||
@@ -2239,6 +2261,7 @@ void DebugDraw::DrawBox(const OrientedBoundingBox& box, const Color& color, floa
|
||||
list = duration > 0 ? &Context->DebugDrawDepthTest.DefaultTriangles : &Context->DebugDrawDepthTest.OneFrameTriangles;
|
||||
else
|
||||
list = duration > 0 ? &Context->DebugDrawDefault.DefaultTriangles : &Context->DebugDrawDefault.OneFrameTriangles;
|
||||
PROFILE_MEM(EngineDebug);
|
||||
list->EnsureCapacity(list->Count() + 36);
|
||||
for (int i0 = 0; i0 < 36;)
|
||||
{
|
||||
@@ -2254,6 +2277,7 @@ void DebugDraw::DrawText(const StringView& text, const Float2& position, const C
|
||||
{
|
||||
if (text.Length() == 0 || size < 4)
|
||||
return;
|
||||
PROFILE_MEM(EngineDebug);
|
||||
Array<DebugText2D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText2D : &Context->DebugDrawDefault.OneFrameText2D;
|
||||
auto& t = list->AddOne();
|
||||
t.Text.Resize(text.Length() + 1);
|
||||
@@ -2269,6 +2293,7 @@ void DebugDraw::DrawText(const StringView& text, const Vector3& position, const
|
||||
{
|
||||
if (text.Length() == 0 || size < 4)
|
||||
return;
|
||||
PROFILE_MEM(EngineDebug);
|
||||
Array<DebugText3D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText3D : &Context->DebugDrawDefault.OneFrameText3D;
|
||||
auto& t = list->AddOne();
|
||||
t.Text.Resize(text.Length() + 1);
|
||||
@@ -2286,6 +2311,7 @@ void DebugDraw::DrawText(const StringView& text, const Transform& transform, con
|
||||
{
|
||||
if (text.Length() == 0 || size < 4)
|
||||
return;
|
||||
PROFILE_MEM(EngineDebug);
|
||||
Array<DebugText3D>* list = duration > 0 ? &Context->DebugDrawDefault.DefaultText3D : &Context->DebugDrawDefault.OneFrameText3D;
|
||||
auto& t = list->AddOne();
|
||||
t.Text.Resize(text.Length() + 1);
|
||||
|
||||
@@ -7,17 +7,17 @@
|
||||
#include "Engine/Core/Random.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Content/Deprecated.h"
|
||||
#if !FOLIAGE_USE_SINGLE_QUAD_TREE
|
||||
#include "Engine/Threading/JobSystem.h"
|
||||
#if FOLIAGE_USE_DRAW_CALLS_BATCHING
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Renderer/RenderList.h"
|
||||
#endif
|
||||
#endif
|
||||
#include "Engine/Level/SceneQuery.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Renderer/RenderList.h"
|
||||
#include "Engine/Renderer/GlobalSignDistanceFieldPass.h"
|
||||
#include "Engine/Renderer/GI/GlobalSurfaceAtlasPass.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
@@ -41,23 +41,42 @@ Foliage::Foliage(const SpawnParams& params)
|
||||
|
||||
void Foliage::AddToCluster(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_SIZE>& clusters, FoliageCluster* cluster, FoliageInstance& instance)
|
||||
{
|
||||
ASSERT(instance.Bounds.Radius > ZeroTolerance);
|
||||
ASSERT(cluster->Bounds.Intersects(instance.Bounds));
|
||||
ASSERT_LOW_LAYER(instance.Bounds.Radius > ZeroTolerance);
|
||||
|
||||
// Find target cluster
|
||||
while (cluster->Children[0])
|
||||
// Minor clusters don't use bounds intersection but try to find the first free cluster instead
|
||||
if (cluster->IsMinor)
|
||||
{
|
||||
// Insert into the first non-full child cluster or subdivide 1st child
|
||||
#define CHECK_CHILD(idx) \
|
||||
if (cluster->Children[idx]->Instances.Count() < FOLIAGE_CLUSTER_CAPACITY) \
|
||||
{ \
|
||||
cluster->Children[idx]->Instances.Add(&instance); \
|
||||
return; \
|
||||
}
|
||||
CHECK_CHILD(3);
|
||||
CHECK_CHILD(2);
|
||||
CHECK_CHILD(1);
|
||||
cluster = cluster->Children[0];
|
||||
#undef CHECK_CHILD
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find target cluster
|
||||
ASSERT(cluster->Bounds.Intersects(instance.Bounds));
|
||||
while (cluster->Children[0])
|
||||
{
|
||||
#define CHECK_CHILD(idx) \
|
||||
if (cluster->Children[idx]->Bounds.Intersects(instance.Bounds)) \
|
||||
{ \
|
||||
cluster = cluster->Children[idx]; \
|
||||
continue; \
|
||||
}
|
||||
CHECK_CHILD(0);
|
||||
CHECK_CHILD(1);
|
||||
CHECK_CHILD(2);
|
||||
CHECK_CHILD(3);
|
||||
CHECK_CHILD(0);
|
||||
CHECK_CHILD(1);
|
||||
CHECK_CHILD(2);
|
||||
CHECK_CHILD(3);
|
||||
#undef CHECK_CHILD
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's not full
|
||||
@@ -79,11 +98,20 @@ void Foliage::AddToCluster(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_S
|
||||
// Setup children
|
||||
const Vector3 min = cluster->Bounds.Minimum;
|
||||
const Vector3 max = cluster->Bounds.Maximum;
|
||||
const Vector3 size = cluster->Bounds.GetSize();
|
||||
const Vector3 size = max - min;
|
||||
cluster->Children[0]->Init(BoundingBox(min, min + size * Vector3(0.5f, 1.0f, 0.5f)));
|
||||
cluster->Children[1]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.5f), max));
|
||||
cluster->Children[2]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.0f), min + size * Vector3(1.0f, 1.0f, 0.5f)));
|
||||
cluster->Children[3]->Init(BoundingBox(min + size * Vector3(0.0f, 0.0f, 0.5f), min + size * Vector3(0.5f, 1.0f, 1.0f)));
|
||||
if (cluster->IsMinor || size.MinValue() < 1.0f)
|
||||
{
|
||||
// Mark children as minor to avoid infinite subdivision
|
||||
cluster->IsMinor = true;
|
||||
cluster->Children[0]->IsMinor = true;
|
||||
cluster->Children[1]->IsMinor = true;
|
||||
cluster->Children[2]->IsMinor = true;
|
||||
cluster->Children[3]->IsMinor = true;
|
||||
}
|
||||
|
||||
// Move instances to a proper cells
|
||||
for (int32 i = 0; i < cluster->Instances.Count(); i++)
|
||||
@@ -165,6 +193,8 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
|
||||
// Draw visible instances
|
||||
const auto frame = Engine::FrameCount;
|
||||
const auto model = type.Model.Get();
|
||||
const auto transitionLOD = renderContext.View.Pass != DrawPass::Depth; // Let the main view pass update LOD transitions
|
||||
// TODO: move DrawState to be stored per-view (so shadows can fade objects on their own)
|
||||
for (int32 i = 0; i < cluster->Instances.Count(); i++)
|
||||
{
|
||||
auto& instance = *cluster->Instances.Get()[i];
|
||||
@@ -182,20 +212,29 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
|
||||
// Handling model fade-out transition
|
||||
if (modelFrame == frame && instance.DrawState.PrevLOD != -1)
|
||||
{
|
||||
// Check if start transition
|
||||
if (instance.DrawState.LODTransition == 255)
|
||||
if (transitionLOD)
|
||||
{
|
||||
instance.DrawState.LODTransition = 0;
|
||||
}
|
||||
// Check if start transition
|
||||
if (instance.DrawState.LODTransition == 255)
|
||||
{
|
||||
instance.DrawState.LODTransition = 0;
|
||||
}
|
||||
|
||||
RenderTools::UpdateModelLODTransition(instance.DrawState.LODTransition);
|
||||
RenderTools::UpdateModelLODTransition(instance.DrawState.LODTransition);
|
||||
|
||||
// Check if end transition
|
||||
if (instance.DrawState.LODTransition == 255)
|
||||
{
|
||||
instance.DrawState.PrevLOD = lodIndex;
|
||||
// Check if end transition
|
||||
if (instance.DrawState.LODTransition == 255)
|
||||
{
|
||||
instance.DrawState.PrevLOD = lodIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD);
|
||||
const float normalizedProgress = static_cast<float>(instance.DrawState.LODTransition) * (1.0f / 255.0f);
|
||||
DrawInstance(renderContext, instance, type, model, prevLOD, normalizedProgress, drawCallsLists, result);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (instance.DrawState.LODTransition < 255)
|
||||
{
|
||||
const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD);
|
||||
const float normalizedProgress = static_cast<float>(instance.DrawState.LODTransition) * (1.0f / 255.0f);
|
||||
@@ -208,29 +247,32 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
|
||||
lodIndex += renderContext.View.ModelLODBias;
|
||||
lodIndex = model->ClampLODIndex(lodIndex);
|
||||
|
||||
// Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports)
|
||||
if (modelFrame == frame)
|
||||
if (transitionLOD)
|
||||
{
|
||||
// Check if start transition
|
||||
if (instance.DrawState.PrevLOD != lodIndex && instance.DrawState.LODTransition == 255)
|
||||
// Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports)
|
||||
if (modelFrame == frame)
|
||||
{
|
||||
// Check if start transition
|
||||
if (instance.DrawState.PrevLOD != lodIndex && instance.DrawState.LODTransition == 255)
|
||||
{
|
||||
instance.DrawState.LODTransition = 0;
|
||||
}
|
||||
|
||||
RenderTools::UpdateModelLODTransition(instance.DrawState.LODTransition);
|
||||
|
||||
// Check if end transition
|
||||
if (instance.DrawState.LODTransition == 255)
|
||||
{
|
||||
instance.DrawState.PrevLOD = lodIndex;
|
||||
}
|
||||
}
|
||||
// Check if there was a gap between frames in drawing this model instance
|
||||
else if (modelFrame < frame || instance.DrawState.PrevLOD == -1)
|
||||
{
|
||||
// Reset state
|
||||
instance.DrawState.PrevLOD = lodIndex;
|
||||
instance.DrawState.LODTransition = 0;
|
||||
}
|
||||
|
||||
RenderTools::UpdateModelLODTransition(instance.DrawState.LODTransition);
|
||||
|
||||
// Check if end transition
|
||||
if (instance.DrawState.LODTransition == 255)
|
||||
{
|
||||
instance.DrawState.PrevLOD = lodIndex;
|
||||
}
|
||||
}
|
||||
// Check if there was a gap between frames in drawing this model instance
|
||||
else if (modelFrame < frame || instance.DrawState.PrevLOD == -1)
|
||||
{
|
||||
// Reset state
|
||||
instance.DrawState.PrevLOD = lodIndex;
|
||||
instance.DrawState.LODTransition = 255;
|
||||
}
|
||||
|
||||
// Draw
|
||||
@@ -253,7 +295,8 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
|
||||
|
||||
//DebugDraw::DrawSphere(instance.Bounds, Color::YellowGreen);
|
||||
|
||||
instance.DrawState.PrevFrame = frame;
|
||||
if (transitionLOD)
|
||||
instance.DrawState.PrevFrame = frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,7 +365,7 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster,
|
||||
draw.DrawState = &instance.DrawState;
|
||||
draw.Bounds = sphere;
|
||||
draw.PerInstanceRandom = instance.Random;
|
||||
draw.DrawModes = type._drawModes;
|
||||
draw.DrawModes = type.DrawModes;
|
||||
draw.SetStencilValue(_layer);
|
||||
type.Model->Draw(renderContext, draw);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ void FoliageCluster::Init(const BoundingBox& bounds)
|
||||
Bounds = bounds;
|
||||
TotalBounds = bounds;
|
||||
MaxCullDistance = 0.0f;
|
||||
IsMinor = false;
|
||||
|
||||
Children[0] = nullptr;
|
||||
Children[1] = nullptr;
|
||||
@@ -20,26 +21,7 @@ void FoliageCluster::Init(const BoundingBox& bounds)
|
||||
|
||||
void FoliageCluster::UpdateTotalBoundsAndCullDistance()
|
||||
{
|
||||
if (Children[0])
|
||||
{
|
||||
ASSERT(Instances.IsEmpty());
|
||||
|
||||
Children[0]->UpdateTotalBoundsAndCullDistance();
|
||||
Children[1]->UpdateTotalBoundsAndCullDistance();
|
||||
Children[2]->UpdateTotalBoundsAndCullDistance();
|
||||
Children[3]->UpdateTotalBoundsAndCullDistance();
|
||||
|
||||
TotalBounds = Children[0]->TotalBounds;
|
||||
BoundingBox::Merge(TotalBounds, Children[1]->TotalBounds, TotalBounds);
|
||||
BoundingBox::Merge(TotalBounds, Children[2]->TotalBounds, TotalBounds);
|
||||
BoundingBox::Merge(TotalBounds, Children[3]->TotalBounds, TotalBounds);
|
||||
|
||||
MaxCullDistance = Children[0]->MaxCullDistance;
|
||||
MaxCullDistance = Math::Max(MaxCullDistance, Children[1]->MaxCullDistance);
|
||||
MaxCullDistance = Math::Max(MaxCullDistance, Children[2]->MaxCullDistance);
|
||||
MaxCullDistance = Math::Max(MaxCullDistance, Children[3]->MaxCullDistance);
|
||||
}
|
||||
else if (Instances.HasItems())
|
||||
if (Instances.HasItems())
|
||||
{
|
||||
BoundingBox box;
|
||||
BoundingBox::FromSphere(Instances[0]->Bounds, TotalBounds);
|
||||
@@ -57,6 +39,30 @@ void FoliageCluster::UpdateTotalBoundsAndCullDistance()
|
||||
MaxCullDistance = 0;
|
||||
}
|
||||
|
||||
if (Children[0])
|
||||
{
|
||||
Children[0]->UpdateTotalBoundsAndCullDistance();
|
||||
Children[1]->UpdateTotalBoundsAndCullDistance();
|
||||
Children[2]->UpdateTotalBoundsAndCullDistance();
|
||||
Children[3]->UpdateTotalBoundsAndCullDistance();
|
||||
|
||||
if (Instances.HasItems())
|
||||
BoundingBox::Merge(TotalBounds, Children[0]->TotalBounds, TotalBounds);
|
||||
else
|
||||
TotalBounds = Children[0]->TotalBounds;
|
||||
BoundingBox::Merge(TotalBounds, Children[1]->TotalBounds, TotalBounds);
|
||||
BoundingBox::Merge(TotalBounds, Children[2]->TotalBounds, TotalBounds);
|
||||
BoundingBox::Merge(TotalBounds, Children[3]->TotalBounds, TotalBounds);
|
||||
|
||||
if (Instances.HasItems())
|
||||
MaxCullDistance = Math::Max(MaxCullDistance, Children[0]->MaxCullDistance);
|
||||
else
|
||||
MaxCullDistance = Children[0]->MaxCullDistance;
|
||||
MaxCullDistance = Math::Max(MaxCullDistance, Children[1]->MaxCullDistance);
|
||||
MaxCullDistance = Math::Max(MaxCullDistance, Children[2]->MaxCullDistance);
|
||||
MaxCullDistance = Math::Max(MaxCullDistance, Children[3]->MaxCullDistance);
|
||||
}
|
||||
|
||||
BoundingSphere::FromBox(TotalBounds, TotalBoundsSphere);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,11 @@ public:
|
||||
/// </summary>
|
||||
float MaxCullDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Flag used by clusters that are not typical quad-tree nodes but have no volume (eg. lots of instances placed on top of each other).
|
||||
/// </summary>
|
||||
int32 IsMinor : 1;
|
||||
|
||||
/// <summary>
|
||||
/// The child clusters. If any element is valid then all are created.
|
||||
/// </summary>
|
||||
|
||||
@@ -1094,6 +1094,11 @@ API_ENUM(Attributes="Flags") enum class ViewFlags : uint64
|
||||
/// Default flags for materials/models previews generating.
|
||||
/// </summary>
|
||||
DefaultAssetPreview = Reflections | Decals | DirectionalLights | PointLights | SpotLights | SkyLights | SpecularLight | AntiAliasing | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | ContactShadows | Sky | Particles,
|
||||
|
||||
/// <summary>
|
||||
/// All flags enabled.
|
||||
/// </summary>
|
||||
All = None | DebugDraw | EditorSprites | Reflections | SSR | AO | GI | DirectionalLights | PointLights | SpotLights | SkyLights | Shadows | SpecularLight | AntiAliasing | CustomPostProcess | Bloom | ToneMapping | EyeAdaptation | CameraArtifacts | LensFlares | Decals | DepthOfField | PhysicsDebug | Fog | MotionBlur | ContactShadows | GlobalSDF | Sky | LightsDebug | Particles,
|
||||
};
|
||||
|
||||
DECLARE_ENUM_OPERATORS(ViewFlags);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/// <summary>
|
||||
/// Current materials shader version.
|
||||
/// </summary>
|
||||
#define MATERIAL_GRAPH_VERSION 179
|
||||
#define MATERIAL_GRAPH_VERSION 180
|
||||
|
||||
class Material;
|
||||
class GPUShader;
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Renderer/Lightmaps.h"
|
||||
#endif
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Level/Scene/Lightmap.h"
|
||||
#include "Engine/Level/Actors/EnvironmentProbe.h"
|
||||
#include "Engine/Renderer/ReflectionsPass.h"
|
||||
|
||||
void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<byte>& cb, int32& srv)
|
||||
{
|
||||
@@ -26,6 +28,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<by
|
||||
const int32 shadowsBufferRegisterIndex = srv + 2;
|
||||
const int32 shadowMapShaderRegisterIndex = srv + 3;
|
||||
const int32 volumetricFogTextureRegisterIndex = srv + 4;
|
||||
const int32 preIntegratedGFRegisterIndex = srv + 5;
|
||||
const bool canUseShadow = view.Pass != DrawPass::Depth;
|
||||
|
||||
// Set fog input
|
||||
@@ -47,6 +50,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<by
|
||||
data.ExponentialHeightFog.FogCutoffDistance = 0.1f;
|
||||
data.ExponentialHeightFog.StartDistance = 0.0f;
|
||||
data.ExponentialHeightFog.ApplyDirectionalInscattering = 0.0f;
|
||||
data.ExponentialHeightFog.VolumetricFogMaxDistance = -1.0f;
|
||||
}
|
||||
params.GPUContext->BindSR(volumetricFogTextureRegisterIndex, volumetricFogTexture);
|
||||
|
||||
@@ -100,9 +104,12 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<by
|
||||
}
|
||||
if (noEnvProbe)
|
||||
{
|
||||
data.EnvironmentProbe.Data1 = Float4::Zero;
|
||||
Platform::MemoryClear(&data.EnvironmentProbe, sizeof(data.EnvironmentProbe));
|
||||
params.GPUContext->UnBindSR(envProbeShaderRegisterIndex);
|
||||
}
|
||||
// TODO: find a better way to find this texture (eg. cache GPUTextureView* handle within ForwardShading cache for a whole frame)
|
||||
static AssetReference<Texture> PreIntegratedGF = Content::LoadAsyncInternal<Texture>(PRE_INTEGRATED_GF_ASSET_NAME);
|
||||
params.GPUContext->BindSR(preIntegratedGFRegisterIndex, PreIntegratedGF->GetTexture());
|
||||
|
||||
// Set local lights
|
||||
data.LocalLightsCount = 0;
|
||||
|
||||
@@ -25,10 +25,9 @@ struct ForwardShadingFeature : MaterialShaderFeature
|
||||
{
|
||||
enum { MaxLocalLights = 4 };
|
||||
|
||||
enum { SRVs = 5 };
|
||||
enum { SRVs = 6 };
|
||||
|
||||
PACK_STRUCT(struct Data
|
||||
{
|
||||
PACK_STRUCT(struct Data {
|
||||
ShaderLightData DirectionalLight;
|
||||
ShaderLightData SkyLight;
|
||||
ShaderEnvProbeData EnvironmentProbe;
|
||||
@@ -76,8 +75,7 @@ struct GlobalIlluminationFeature : MaterialShaderFeature
|
||||
{
|
||||
enum { SRVs = 3 };
|
||||
|
||||
PACK_STRUCT(struct Data
|
||||
{
|
||||
PACK_STRUCT(struct Data {
|
||||
DynamicDiffuseGlobalIlluminationPass::ConstantsData DDGI;
|
||||
});
|
||||
|
||||
@@ -92,13 +90,10 @@ struct SDFReflectionsFeature : MaterialShaderFeature
|
||||
{
|
||||
enum { SRVs = 7 };
|
||||
|
||||
PACK_STRUCT(struct Data
|
||||
{
|
||||
PACK_STRUCT(struct Data {
|
||||
GlobalSignDistanceFieldPass::ConstantsData GlobalSDF;
|
||||
GlobalSurfaceAtlasPass::ConstantsData GlobalSurfaceAtlas;
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
static bool Bind(MaterialShader::BindParameters& params, Span<byte>& cb, int32& srv);
|
||||
#if USE_EDITOR
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
#include "Engine/Core/Math/Transform.h"
|
||||
#include "Engine/Core/Math/Ray.h"
|
||||
#include "Engine/Core/Math/CollisionsHelper.h"
|
||||
#include "Engine/Core/Math/Half.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Graphics/PixelFormat.h"
|
||||
|
||||
/// <summary>
|
||||
/// Helper container used for detailed triangle mesh intersections tests.
|
||||
@@ -31,23 +33,38 @@ public:
|
||||
}
|
||||
|
||||
template<typename IndexType>
|
||||
void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices, uint32 positionsStride = sizeof(Float3))
|
||||
void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices, uint32 positionsStride = sizeof(Float3), PixelFormat positionsFormat = PixelFormat::R32G32B32_Float)
|
||||
{
|
||||
Triangles.Clear();
|
||||
Triangles.EnsureCapacity(triangles, false);
|
||||
const IndexType* it = indices;
|
||||
for (uint32 i = 0; i < triangles; i++)
|
||||
#define LOOP_BEGIN() \
|
||||
for (uint32 i = 0; i < triangles; i++) \
|
||||
{ \
|
||||
const IndexType i0 = *(it++); \
|
||||
const IndexType i1 = *(it++); \
|
||||
const IndexType i2 = *(it++); \
|
||||
if (i0 < vertices && i1 < vertices && i2 < vertices) \
|
||||
{
|
||||
const IndexType i0 = *(it++);
|
||||
const IndexType i1 = *(it++);
|
||||
const IndexType i2 = *(it++);
|
||||
if (i0 < vertices && i1 < vertices && i2 < vertices)
|
||||
{
|
||||
#define LOOP_END() } }
|
||||
if (positionsFormat == PixelFormat::R32G32B32_Float)
|
||||
{
|
||||
LOOP_BEGIN()
|
||||
#define GET_POS(idx) *(const Float3*)((const byte*)positions + positionsStride * idx)
|
||||
Triangles.Add({ GET_POS(i0), GET_POS(i1), GET_POS(i2) });
|
||||
#undef GET_POS
|
||||
}
|
||||
LOOP_END()
|
||||
}
|
||||
else if (positionsFormat == PixelFormat::R16G16B16A16_Float)
|
||||
{
|
||||
LOOP_BEGIN()
|
||||
#define GET_POS(idx) ((const Half4*)((const byte*)positions + positionsStride * idx))->ToFloat3()
|
||||
Triangles.Add({ GET_POS(i0), GET_POS(i1), GET_POS(i2) });
|
||||
#undef GET_POS
|
||||
LOOP_END()
|
||||
}
|
||||
#undef LOOP_BEGIN
|
||||
#undef LOOP_END
|
||||
}
|
||||
|
||||
void Clear()
|
||||
|
||||
@@ -265,6 +265,39 @@ namespace FlaxEngine
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of the input <see cref="T:System.Span`1"/> into the elements of this stream.
|
||||
/// </summary>
|
||||
/// <param name="src">The source <see cref="T:System.Span`1"/>.</param>
|
||||
public void Set(Span<uint> src)
|
||||
{
|
||||
if (IsLinear(PixelFormat.R32_UInt))
|
||||
{
|
||||
src.CopyTo(MemoryMarshal.Cast<byte, uint>(_data));
|
||||
}
|
||||
else if (IsLinear(PixelFormat.R16_UInt))
|
||||
{
|
||||
var count = Count;
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
((ushort*)data)[i] = (ushort)src[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = Count;
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var v = new Float4(src[i]);
|
||||
_sampler.Write(data + i * _stride, ref v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this stream into a destination <see cref="T:System.Span`1" />.
|
||||
/// </summary>
|
||||
@@ -281,9 +314,7 @@ namespace FlaxEngine
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dst[i] = new Float2(_sampler.Read(data + i * _stride));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -304,9 +335,7 @@ namespace FlaxEngine
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dst[i] = new Float3(_sampler.Read(data + i * _stride));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,9 +356,37 @@ namespace FlaxEngine
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
dst[i] = (Color)_sampler.Read(data + i * _stride);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this stream into a destination <see cref="T:System.Span`1" />.
|
||||
/// </summary>
|
||||
/// <param name="dst">The destination <see cref="T:System.Span`1" />.</param>
|
||||
public void CopyTo(Span<uint> dst)
|
||||
{
|
||||
if (IsLinear(PixelFormat.R32_UInt))
|
||||
{
|
||||
_data.CopyTo(MemoryMarshal.Cast<uint, byte>(dst));
|
||||
}
|
||||
else if (IsLinear(PixelFormat.R16_UInt))
|
||||
{
|
||||
var count = Count;
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
dst[i] = ((ushort*)data)[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = Count;
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
dst[i] = (uint)_sampler.Read(data + i * _stride).X;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -619,6 +676,16 @@ namespace FlaxEngine
|
||||
return Attribute((VertexElement.Types)((byte)VertexElement.Types.TexCoord0 + channel));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index buffer with triangle indices.
|
||||
/// </summary>
|
||||
/// <remarks>Uses <see cref="Index"/> stream to read or write data to the index buffer.</remarks>
|
||||
public uint[] Triangles
|
||||
{
|
||||
get => GetStreamUInt(Index());
|
||||
set => SetStreamUInt(Index(), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the vertex positions. Null if <see cref="VertexElement.Types.Position"/> does not exist in vertex buffers of the mesh.
|
||||
/// </summary>
|
||||
@@ -659,6 +726,25 @@ namespace FlaxEngine
|
||||
set => SetStreamFloat2(VertexElement.Types.TexCoord, value);
|
||||
}
|
||||
|
||||
private uint[] GetStreamUInt(Stream stream)
|
||||
{
|
||||
uint[] result = null;
|
||||
if (stream.IsValid)
|
||||
{
|
||||
result = new uint[stream.Count];
|
||||
stream.CopyTo(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SetStreamUInt(Stream stream, uint[] value)
|
||||
{
|
||||
if (stream.IsValid)
|
||||
{
|
||||
stream.Set(value);
|
||||
}
|
||||
}
|
||||
|
||||
private delegate void TransformDelegate3(ref Float3 value);
|
||||
|
||||
private Float3[] GetStreamFloat3(VertexElement.Types attribute, TransformDelegate3 transform = null)
|
||||
|
||||
@@ -441,6 +441,9 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*,
|
||||
GPUBuffer* vertexBuffer1 = nullptr;
|
||||
GPUBuffer* vertexBuffer2 = nullptr;
|
||||
GPUBuffer* indexBuffer = nullptr;
|
||||
#if MODEL_USE_PRECISE_MESH_INTERSECTS
|
||||
VertexElement positionsElement;
|
||||
#endif
|
||||
|
||||
// Create GPU buffers
|
||||
#if GPU_ENABLE_RESOURCE_NAMING
|
||||
@@ -470,10 +473,11 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*,
|
||||
|
||||
// Init collision proxy
|
||||
#if MODEL_USE_PRECISE_MESH_INTERSECTS
|
||||
positionsElement = vbLayout[0]->FindElement(VertexElement::Types::Position);
|
||||
if (use16BitIndexBuffer)
|
||||
_collisionProxy.Init<uint16>(vertices, triangles, (const Float3*)vbData[0], (const uint16*)ibData);
|
||||
_collisionProxy.Init<uint16>(vertices, triangles, (const Float3*)vbData[0], (const uint16*)ibData, vertexBuffer0->GetStride(), positionsElement.Format);
|
||||
else
|
||||
_collisionProxy.Init<uint32>(vertices, triangles, (const Float3*)vbData[0], (const uint32*)ibData);
|
||||
_collisionProxy.Init<uint32>(vertices, triangles, (const Float3*)vbData[0], (const uint32*)ibData, vertexBuffer0->GetStride(), positionsElement.Format);
|
||||
#endif
|
||||
|
||||
// Free old buffers
|
||||
|
||||
@@ -73,6 +73,10 @@ struct TIsPODType<SkeletonBone>
|
||||
/// </remarks>
|
||||
class FLAXENGINE_API SkeletonData
|
||||
{
|
||||
private:
|
||||
mutable volatile int64 _dirty = 1;
|
||||
mutable Array<Matrix> _cachedPose;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The nodes in this hierarchy. The root node is always at the index 0.
|
||||
@@ -114,6 +118,11 @@ public:
|
||||
int32 FindNode(const StringView& name) const;
|
||||
int32 FindBone(int32 nodeIndex) const;
|
||||
|
||||
// Gets the skeleton nodes transforms in mesh space (pose). Calculated from the local node transforms and hierarchy. Cached internally and updated when data is dirty.
|
||||
const Array<Matrix>& GetNodesPose() const;
|
||||
|
||||
// Marks data as dirty (modified) to update internal state and recalculate cached data if needed (eg. skeleton pose).
|
||||
void Dirty();
|
||||
uint64 GetMemoryUsage() const;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -154,6 +154,8 @@ void SkeletonData::Swap(SkeletonData& other)
|
||||
{
|
||||
Nodes.Swap(other.Nodes);
|
||||
Bones.Swap(other.Bones);
|
||||
Dirty();
|
||||
other.Dirty();
|
||||
}
|
||||
|
||||
Transform SkeletonData::GetNodeTransform(int32 nodeIndex) const
|
||||
@@ -171,6 +173,7 @@ Transform SkeletonData::GetNodeTransform(int32 nodeIndex) const
|
||||
void SkeletonData::SetNodeTransform(int32 nodeIndex, const Transform& value)
|
||||
{
|
||||
CHECK(Nodes.IsValidIndex(nodeIndex));
|
||||
Dirty();
|
||||
const int32 parentIndex = Nodes[nodeIndex].ParentIndex;
|
||||
if (parentIndex == -1)
|
||||
{
|
||||
@@ -201,6 +204,39 @@ int32 SkeletonData::FindBone(int32 nodeIndex) const
|
||||
return -1;
|
||||
}
|
||||
|
||||
const Array<Matrix>& SkeletonData::GetNodesPose() const
|
||||
{
|
||||
// Guard with a simple atomic flag to avoid locking if the pose is up to date
|
||||
if (Platform::AtomicRead(&_dirty))
|
||||
{
|
||||
ScopeLock lock(RenderContext::GPULocker);
|
||||
if (Platform::AtomicRead(&_dirty))
|
||||
{
|
||||
const SkeletonNode* nodes = Nodes.Get();
|
||||
const int32 nodesCount = Nodes.Count();
|
||||
_cachedPose.Resize(nodesCount);
|
||||
Matrix* posePtr = _cachedPose.Get();
|
||||
for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++)
|
||||
{
|
||||
const SkeletonNode& node = nodes[nodeIndex];
|
||||
Matrix local;
|
||||
Matrix::Transformation(node.LocalTransform.Scale, node.LocalTransform.Orientation, node.LocalTransform.Translation, local);
|
||||
if (node.ParentIndex != -1)
|
||||
Matrix::Multiply(local, posePtr[node.ParentIndex], posePtr[nodeIndex]);
|
||||
else
|
||||
posePtr[nodeIndex] = local;
|
||||
}
|
||||
Platform::AtomicStore(&_dirty, 0);
|
||||
}
|
||||
}
|
||||
return _cachedPose;
|
||||
}
|
||||
|
||||
void SkeletonData::Dirty()
|
||||
{
|
||||
Platform::AtomicStore(&_dirty, 1);
|
||||
}
|
||||
|
||||
uint64 SkeletonData::GetMemoryUsage() const
|
||||
{
|
||||
uint64 result = Nodes.Capacity() * sizeof(SkeletonNode) + Bones.Capacity() * sizeof(SkeletonBone);
|
||||
|
||||
@@ -5,11 +5,6 @@
|
||||
#include "Engine/Animations/Config.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "Engine/Core/Math/Matrix3x4.h"
|
||||
|
||||
SkinnedMeshDrawData::SkinnedMeshDrawData()
|
||||
{
|
||||
}
|
||||
|
||||
SkinnedMeshDrawData::~SkinnedMeshDrawData()
|
||||
{
|
||||
@@ -33,7 +28,7 @@ void SkinnedMeshDrawData::Setup(int32 bonesCount)
|
||||
|
||||
BonesCount = bonesCount;
|
||||
_hasValidData = false;
|
||||
_isDirty = false;
|
||||
_isDirty = true;
|
||||
Data.Resize(BoneMatrices->GetSize());
|
||||
SAFE_DELETE_GPU_RESOURCE(PrevBoneMatrices);
|
||||
}
|
||||
|
||||
@@ -36,11 +36,6 @@ public:
|
||||
Array<byte> Data;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SkinnedMeshDrawData"/> class.
|
||||
/// </summary>
|
||||
SkinnedMeshDrawData();
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="SkinnedMeshDrawData"/> class.
|
||||
/// </summary>
|
||||
@@ -76,7 +71,7 @@ public:
|
||||
void OnDataChanged(bool dropHistory);
|
||||
|
||||
/// <summary>
|
||||
/// After bones Data has been send to the GPU buffer.
|
||||
/// After bones Data has been sent to the GPU buffer.
|
||||
/// </summary>
|
||||
void OnFlush()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Graphics/GPUBuffer.h"
|
||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||
#include "Engine/Threading/ConcurrentDictionary.h"
|
||||
#if GPU_ENABLE_RESOURCE_NAMING
|
||||
#include "Engine/Scripting/Enums.h"
|
||||
#endif
|
||||
@@ -40,27 +41,37 @@ uint32 GetHash(const VertexBufferLayouts& key)
|
||||
|
||||
namespace
|
||||
{
|
||||
CriticalSection CacheLocker;
|
||||
Dictionary<uint32, GPUVertexLayout*> LayoutCache;
|
||||
Dictionary<VertexBufferLayouts, GPUVertexLayout*> VertexBufferCache;
|
||||
ConcurrentDictionary<uint32, GPUVertexLayout*> LayoutCache;
|
||||
ConcurrentDictionary<VertexBufferLayouts, GPUVertexLayout*> VertexBufferCache;
|
||||
|
||||
GPUVertexLayout* AddCache(const VertexBufferLayouts& key, int32 count)
|
||||
GPUVertexLayout* GetCache(const VertexBufferLayouts& key, int32 count)
|
||||
{
|
||||
GPUVertexLayout::Elements elements;
|
||||
bool anyValid = false;
|
||||
for (int32 slot = 0; slot < count; slot++)
|
||||
GPUVertexLayout* result;
|
||||
if (!VertexBufferCache.TryGet(key, result))
|
||||
{
|
||||
if (key.Layouts[slot])
|
||||
GPUVertexLayout::Elements elements;
|
||||
bool anyValid = false;
|
||||
for (int32 slot = 0; slot < count; slot++)
|
||||
{
|
||||
anyValid = true;
|
||||
int32 start = elements.Count();
|
||||
elements.Add(key.Layouts[slot]->GetElements());
|
||||
for (int32 j = start; j < elements.Count(); j++)
|
||||
elements.Get()[j].Slot = (byte)slot;
|
||||
if (key.Layouts[slot])
|
||||
{
|
||||
anyValid = true;
|
||||
int32 start = elements.Count();
|
||||
elements.Add(key.Layouts[slot]->GetElements());
|
||||
for (int32 j = start; j < elements.Count(); j++)
|
||||
elements.Get()[j].Slot = (byte)slot;
|
||||
}
|
||||
}
|
||||
result = anyValid ? GPUVertexLayout::Get(elements, true) : nullptr;
|
||||
if (!VertexBufferCache.Add(key, result))
|
||||
{
|
||||
// Other thread added the value
|
||||
Delete(result);
|
||||
bool found = VertexBufferCache.TryGet(key, result);
|
||||
ASSERT(found);
|
||||
}
|
||||
|
||||
}
|
||||
GPUVertexLayout* result = anyValid ? GPUVertexLayout::Get(elements) : nullptr;
|
||||
VertexBufferCache.Add(key, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -97,6 +108,7 @@ GPUVertexLayout::GPUVertexLayout()
|
||||
void GPUVertexLayout::SetElements(const Elements& elements, bool explicitOffsets)
|
||||
{
|
||||
uint32 offsets[GPU_MAX_VB_BINDED + 1] = {};
|
||||
uint32 maxOffset[GPU_MAX_VB_BINDED + 1] = {};
|
||||
_elements = elements;
|
||||
for (int32 i = 0; i < _elements.Count(); i++)
|
||||
{
|
||||
@@ -108,9 +120,10 @@ void GPUVertexLayout::SetElements(const Elements& elements, bool explicitOffsets
|
||||
else
|
||||
e.Offset = (byte)offset;
|
||||
offset += PixelFormatExtensions::SizeInBytes(e.Format);
|
||||
maxOffset[e.Slot] = Math::Max(maxOffset[e.Slot], offset);
|
||||
}
|
||||
_stride = 0;
|
||||
for (uint32 offset : offsets)
|
||||
for (uint32 offset : maxOffset)
|
||||
_stride += offset;
|
||||
}
|
||||
|
||||
@@ -139,14 +152,13 @@ VertexElement GPUVertexLayout::FindElement(VertexElement::Types type) const
|
||||
GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements, bool explicitOffsets)
|
||||
{
|
||||
// Hash input layout
|
||||
uint32 hash = 0;
|
||||
uint32 hash = explicitOffsets ? 131 : 0;
|
||||
for (const VertexElement& element : elements)
|
||||
{
|
||||
CombineHash(hash, GetHash(element));
|
||||
}
|
||||
|
||||
// Lookup existing cache
|
||||
CacheLocker.Lock();
|
||||
GPUVertexLayout* result;
|
||||
if (!LayoutCache.TryGet(hash, result))
|
||||
{
|
||||
@@ -158,12 +170,16 @@ GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements, bool explicitOff
|
||||
LOG(Error, " {}", e.ToString());
|
||||
#endif
|
||||
LOG(Error, "Failed to create vertex layout");
|
||||
CacheLocker.Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
LayoutCache.Add(hash, result);
|
||||
if (!LayoutCache.Add(hash, result))
|
||||
{
|
||||
// Other thread added the value
|
||||
Delete(result);
|
||||
bool found = LayoutCache.TryGet(hash, result);
|
||||
ASSERT(found);
|
||||
}
|
||||
}
|
||||
CacheLocker.Unlock();
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -183,13 +199,7 @@ GPUVertexLayout* GPUVertexLayout::Get(const Span<GPUBuffer*>& vertexBuffers)
|
||||
key.Layouts[i] = nullptr;
|
||||
|
||||
// Lookup existing cache
|
||||
CacheLocker.Lock();
|
||||
GPUVertexLayout* result;
|
||||
if (!VertexBufferCache.TryGet(key, result))
|
||||
result = AddCache(key, vertexBuffers.Length());
|
||||
CacheLocker.Unlock();
|
||||
|
||||
return result;
|
||||
return GetCache(key, vertexBuffers.Length());
|
||||
}
|
||||
|
||||
GPUVertexLayout* GPUVertexLayout::Get(const Span<GPUVertexLayout*>& layouts)
|
||||
@@ -207,13 +217,7 @@ GPUVertexLayout* GPUVertexLayout::Get(const Span<GPUVertexLayout*>& layouts)
|
||||
key.Layouts[i] = nullptr;
|
||||
|
||||
// Lookup existing cache
|
||||
CacheLocker.Lock();
|
||||
GPUVertexLayout* result;
|
||||
if (!VertexBufferCache.TryGet(key, result))
|
||||
result = AddCache(key, layouts.Length());
|
||||
CacheLocker.Unlock();
|
||||
|
||||
return result;
|
||||
return GetCache(key, layouts.Length());
|
||||
}
|
||||
|
||||
GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused, bool addMissing, int32 missingSlotOverride, bool referenceOrder)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "Engine/Graphics/Shaders/GPUShaderProgram.h"
|
||||
#include "Engine/Core/Types/DataContainer.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "../IncludeDirectXHeaders.h"
|
||||
|
||||
#if GRAPHICS_API_DIRECTX11
|
||||
|
||||
@@ -3,16 +3,9 @@
|
||||
#if GRAPHICS_API_DIRECTX12
|
||||
|
||||
#include "Engine/Graphics/Config.h"
|
||||
#include "Engine/Platform/Platform.h"
|
||||
#include "../IncludeDirectXHeaders.h"
|
||||
#if USE_PIX && GPU_ALLOW_PROFILE_EVENTS
|
||||
// Include these header files before pix3
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#define NOGDI
|
||||
#define NODRAWTEXT
|
||||
//#define NOCTLMGR
|
||||
#define NOFLATSBAPIS
|
||||
#include <Windows.h>
|
||||
#include <d3d12.h>
|
||||
#include <ThirdParty/WinPixEventRuntime/pix3.h>
|
||||
#endif
|
||||
#include "GPUContextDX12.h"
|
||||
|
||||
@@ -1685,7 +1685,7 @@ Quaternion Actor::LookingAt(const Vector3& worldPos) const
|
||||
{
|
||||
const Vector3 direction = worldPos - _transform.Translation;
|
||||
if (direction.LengthSquared() < ZeroTolerance)
|
||||
return _parent->GetOrientation();
|
||||
return _parent ? _parent->GetOrientation() : Quaternion::Identity;
|
||||
|
||||
const Float3 newForward = Vector3::Normalize(direction);
|
||||
const Float3 oldForward = _transform.Orientation * Vector3::Forward;
|
||||
@@ -1712,7 +1712,7 @@ Quaternion Actor::LookingAt(const Vector3& worldPos, const Vector3& worldUp) con
|
||||
{
|
||||
const Vector3 direction = worldPos - _transform.Translation;
|
||||
if (direction.LengthSquared() < ZeroTolerance)
|
||||
return _parent->GetOrientation();
|
||||
return _parent ? _parent->GetOrientation() : Quaternion::Identity;
|
||||
const Float3 forward = Vector3::Normalize(direction);
|
||||
const Float3 up = Vector3::Normalize(worldUp);
|
||||
if (Math::IsOne(Float3::Dot(forward, up)))
|
||||
|
||||
@@ -14,14 +14,84 @@
|
||||
#include "Engine/Content/Deprecated.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Graphics/GPUPass.h"
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Graphics/Models/MeshAccessor.h"
|
||||
#include "Engine/Graphics/Models/MeshDeformation.h"
|
||||
#include "Engine/Renderer/RenderList.h"
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
#include "Engine/Level/SceneObjectsFactory.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "Engine/Profiler/Profiler.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
|
||||
// Implements efficient skinning data update within a shared GPUMemoryPass with manual resource transitions batched for all animated models.
|
||||
class AnimatedModelRenderListExtension : public RenderList::IExtension
|
||||
{
|
||||
public:
|
||||
struct Item
|
||||
{
|
||||
GPUBuffer* BoneMatrices;
|
||||
void* Data;
|
||||
int32 Size;
|
||||
};
|
||||
|
||||
RenderListBuffer<Item> Items;
|
||||
|
||||
void PreDraw(GPUContext* context, RenderContextBatch& renderContextBatch) override
|
||||
{
|
||||
Items.Clear();
|
||||
}
|
||||
|
||||
void PostDraw(GPUContext* context, RenderContextBatch& renderContextBatch) override
|
||||
{
|
||||
const int32 count = Items.Count();
|
||||
if (count == 0)
|
||||
return;
|
||||
PROFILE_GPU_CPU_NAMED("Update Bones");
|
||||
GPUMemoryPass pass(context);
|
||||
Item* items = Items.Get();
|
||||
|
||||
// Special case for D3D11 backend that doesn't need transitions
|
||||
if (context->GetDevice()->GetRendererType() <= RendererType::DirectX11)
|
||||
{
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
Item& item = items[i];
|
||||
context->UpdateBuffer(item.BoneMatrices, item.Data, item.Size);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Batch resource barriers for buffer update
|
||||
for (int32 i = 0; i < count; i++)
|
||||
pass.Transition(items[i].BoneMatrices, GPUResourceAccess::CopyWrite);
|
||||
|
||||
// Update all buffers within Memory Pass (no barriers between)
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
Item& item = items[i];
|
||||
context->UpdateBuffer(item.BoneMatrices, item.Data, item.Size);
|
||||
}
|
||||
|
||||
// Batch resource barriers for reading in Vertex Shader
|
||||
for (int32 i = 0; i < count; i++)
|
||||
pass.Transition(items[i].BoneMatrices, GPUResourceAccess::ShaderReadGraphics);
|
||||
}
|
||||
|
||||
#if COMPILE_WITH_PROFILER
|
||||
// Insert amount of kilobytes of data updated into profiler trace
|
||||
uint32 dataSize = 0;
|
||||
for (int32 i = 0; i < count; i++)
|
||||
dataSize += items[i].Size;
|
||||
ZoneValue(dataSize / 1024);
|
||||
#endif
|
||||
|
||||
Items.Clear();
|
||||
}
|
||||
};
|
||||
|
||||
AnimatedModelRenderListExtension RenderListExtension;
|
||||
|
||||
AnimatedModel::AnimatedModel(const SpawnParams& params)
|
||||
: ModelInstanceActor(params)
|
||||
, _actualMode(AnimationUpdateMode::Never)
|
||||
@@ -1002,7 +1072,7 @@ void AnimatedModel::Draw(RenderContext& renderContext)
|
||||
if (renderContext.View.Pass == DrawPass::GlobalSDF)
|
||||
return;
|
||||
if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
|
||||
return; // No supported
|
||||
return; // Not supported
|
||||
ACTOR_GET_WORLD_MATRIX(this, view, world);
|
||||
GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world);
|
||||
|
||||
@@ -1012,9 +1082,8 @@ void AnimatedModel::Draw(RenderContext& renderContext)
|
||||
// Flush skinning data with GPU
|
||||
if (_skinningData.IsDirty())
|
||||
{
|
||||
RenderContext::GPULocker.Lock();
|
||||
GPUDevice::Instance->GetMainContext()->UpdateBuffer(_skinningData.BoneMatrices, _skinningData.Data.Get(), _skinningData.Data.Count());
|
||||
RenderContext::GPULocker.Unlock();
|
||||
RenderListExtension.Items.Add({ _skinningData.BoneMatrices, _skinningData.Data.Get(), _skinningData.Data.Count() });
|
||||
_skinningData.OnFlush();
|
||||
}
|
||||
|
||||
SkinnedMesh::DrawInfo draw;
|
||||
@@ -1056,9 +1125,8 @@ void AnimatedModel::Draw(RenderContextBatch& renderContextBatch)
|
||||
// Flush skinning data with GPU
|
||||
if (_skinningData.IsDirty())
|
||||
{
|
||||
RenderContext::GPULocker.Lock();
|
||||
GPUDevice::Instance->GetMainContext()->UpdateBuffer(_skinningData.BoneMatrices, _skinningData.Data.Get(), _skinningData.Data.Count());
|
||||
RenderContext::GPULocker.Unlock();
|
||||
RenderListExtension.Items.Add({ _skinningData.BoneMatrices, _skinningData.Data.Get(), _skinningData.Data.Count() });
|
||||
_skinningData.OnFlush();
|
||||
}
|
||||
|
||||
SkinnedMesh::DrawInfo draw;
|
||||
|
||||
@@ -473,6 +473,19 @@ public:
|
||||
/// <returns>Found actors list.</returns>
|
||||
API_FUNCTION() static Array<Actor*> GetActors(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, bool activeOnly = false);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all the actors of the given type in all the loaded scenes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the object.</typeparam>
|
||||
/// <param name="activeOnly">Finds only active actors.</param>
|
||||
/// <returns>Found actors list.</returns>
|
||||
template<typename T>
|
||||
static Array<T*> GetActors(bool activeOnly = false)
|
||||
{
|
||||
Array<Actor*> actors = GetActors(T::GetStaticClass(), activeOnly);
|
||||
return *(Array<T*>*)&actors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all the scripts of the given type in an actor or all the loaded scenes.
|
||||
/// </summary>
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace FlaxEngine
|
||||
public bool ValueEquals(object other)
|
||||
{
|
||||
var o = (MeshReference)other;
|
||||
return JsonSerializer.ValueEquals(Actor, o.Actor) &&
|
||||
return JsonSerializer.SceneObjectEquals(Actor, o.Actor) &&
|
||||
LODIndex == o.LODIndex &&
|
||||
MeshIndex == o.MeshIndex;
|
||||
}
|
||||
|
||||
@@ -227,9 +227,9 @@ public:
|
||||
void PrefabInstanceData::CollectPrefabInstances(PrefabInstancesData& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor)
|
||||
{
|
||||
ScopeLock lock(PrefabManager::PrefabsReferencesLocker);
|
||||
if (PrefabManager::PrefabsReferences.ContainsKey(prefabId))
|
||||
if (auto instancesPtr = PrefabManager::PrefabsReferences.TryGet(prefabId))
|
||||
{
|
||||
auto& instances = PrefabManager::PrefabsReferences[prefabId];
|
||||
auto& instances = *instancesPtr;
|
||||
int32 usedCount = 0;
|
||||
for (int32 instanceIndex = 0; instanceIndex < instances.Count(); instanceIndex++)
|
||||
{
|
||||
|
||||
@@ -25,6 +25,7 @@ NavMesh::NavMesh(const SpawnParams& params)
|
||||
void NavMesh::SaveNavMesh()
|
||||
{
|
||||
#if COMPILE_WITH_ASSETS_IMPORTER
|
||||
PROFILE_MEM(NavigationMesh);
|
||||
|
||||
// Skip if scene is missing
|
||||
const auto scene = GetScene();
|
||||
@@ -111,7 +112,7 @@ void NavMesh::OnAssetLoaded(Asset* asset, void* caller)
|
||||
if (Data.Tiles.HasItems())
|
||||
return;
|
||||
ScopeLock lock(DataAsset->Locker);
|
||||
PROFILE_MEM(Navigation);
|
||||
PROFILE_MEM(NavigationMesh);
|
||||
|
||||
// Remove added tiles
|
||||
if (_navMeshActive)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#if USE_EDITOR
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/Managed/ManagedEditor.h"
|
||||
#include "NavMeshBuilder.h"
|
||||
#include "Navigation.h"
|
||||
#endif
|
||||
|
||||
NavMeshBoundsVolume::NavMeshBoundsVolume(const SpawnParams& params)
|
||||
@@ -55,9 +55,30 @@ void NavMeshBoundsVolume::OnBoundsChanged(const BoundingBox& prevBounds)
|
||||
// Auto-rebuild modified navmesh area
|
||||
if (IsDuringPlay() && IsActiveInHierarchy() && !Editor::IsPlayMode && Editor::Managed->CanAutoBuildNavMesh())
|
||||
{
|
||||
BoundingBox dirtyBounds;
|
||||
BoundingBox::Merge(prevBounds, _box, dirtyBounds);
|
||||
NavMeshBuilder::Build(GetScene(), dirtyBounds, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
|
||||
if (_box.Intersects(prevBounds))
|
||||
{
|
||||
// Bounds were moved a bit so merge into a single request (for performance reasons)
|
||||
BoundingBox dirtyBounds;
|
||||
BoundingBox::Merge(prevBounds, _box, dirtyBounds);
|
||||
Navigation::BuildNavMesh(dirtyBounds, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dirty each bounds in separate
|
||||
Navigation::BuildNavMesh(prevBounds, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
|
||||
Navigation::BuildNavMesh(_box, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NavMeshBoundsVolume::OnActiveInTreeChanged()
|
||||
{
|
||||
BoxVolume::OnActiveInTreeChanged();
|
||||
|
||||
// Auto-rebuild
|
||||
if (IsDuringPlay() && !Editor::IsPlayMode && Editor::Managed->CanAutoBuildNavMesh())
|
||||
{
|
||||
Navigation::BuildNavMesh(_box, ManagedEditor::ManagedEditorOptions.AutoRebuildNavMeshTimeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ protected:
|
||||
void OnDisable() override;
|
||||
#if USE_EDITOR
|
||||
void OnBoundsChanged(const BoundingBox& prevBounds) override;
|
||||
void OnActiveInTreeChanged() override;
|
||||
Color GetWiresColor() override;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#if COMPILE_WITH_NAV_MESH_BUILDER
|
||||
|
||||
#include "NavMeshBuilder.h"
|
||||
#include "Navigation.h"
|
||||
#include "NavMesh.h"
|
||||
#include "NavigationSettings.h"
|
||||
#include "NavMeshBoundsVolume.h"
|
||||
@@ -706,6 +707,7 @@ struct BuildRequest
|
||||
ScriptingObjectReference<Scene> Scene;
|
||||
DateTime Time;
|
||||
BoundingBox DirtyBounds;
|
||||
bool SpecificScene;
|
||||
};
|
||||
|
||||
CriticalSection NavBuildQueueLocker;
|
||||
@@ -713,6 +715,7 @@ Array<BuildRequest> NavBuildQueue;
|
||||
|
||||
CriticalSection NavBuildTasksLocker;
|
||||
int32 NavBuildTasksMaxCount = 0;
|
||||
bool NavBuildCheckMissingNavMeshes = false;
|
||||
Array<class NavMeshTileBuildTask*> NavBuildTasks;
|
||||
|
||||
class NavMeshTileBuildTask : public ThreadPoolTask
|
||||
@@ -733,7 +736,7 @@ public:
|
||||
bool Run() override
|
||||
{
|
||||
PROFILE_CPU_NAMED("BuildNavMeshTile");
|
||||
PROFILE_MEM(Navigation);
|
||||
PROFILE_MEM(NavigationBuilding);
|
||||
const auto navMesh = NavMesh.Get();
|
||||
if (!navMesh)
|
||||
return false;
|
||||
@@ -776,13 +779,13 @@ void CancelNavMeshTileBuildTasks(NavMeshRuntime* runtime)
|
||||
NavBuildTasksLocker.Unlock();
|
||||
}
|
||||
|
||||
void CancelNavMeshTileBuildTasks(NavMeshRuntime* runtime, int32 x, int32 y)
|
||||
void CancelNavMeshTileBuildTasks(NavMeshRuntime* runtime, int32 x, int32 y, NavMesh* navMesh)
|
||||
{
|
||||
NavBuildTasksLocker.Lock();
|
||||
for (int32 i = 0; i < NavBuildTasks.Count(); i++)
|
||||
{
|
||||
auto task = NavBuildTasks[i];
|
||||
if (task->Runtime == runtime && task->X == x && task->Y == y)
|
||||
if (task->Runtime == runtime && task->X == x && task->Y == y && task->NavMesh == navMesh)
|
||||
{
|
||||
NavBuildTasksLocker.Unlock();
|
||||
|
||||
@@ -838,7 +841,7 @@ void NavMeshBuilder::Init()
|
||||
Level::SceneUnloading.Bind<OnSceneUnloading>();
|
||||
}
|
||||
|
||||
bool NavMeshBuilder::IsBuildingNavMesh()
|
||||
bool Navigation::IsBuildingNavMesh()
|
||||
{
|
||||
NavBuildTasksLocker.Lock();
|
||||
const bool hasAnyTask = NavBuildTasks.HasItems();
|
||||
@@ -847,7 +850,7 @@ bool NavMeshBuilder::IsBuildingNavMesh()
|
||||
return hasAnyTask;
|
||||
}
|
||||
|
||||
float NavMeshBuilder::GetNavMeshBuildingProgress()
|
||||
float Navigation::GetNavMeshBuildingProgress()
|
||||
{
|
||||
NavBuildTasksLocker.Lock();
|
||||
float result = 1.0f;
|
||||
@@ -907,15 +910,13 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo
|
||||
// Align dirty bounds to tile size
|
||||
BoundingBox dirtyBoundsNavMesh;
|
||||
BoundingBox::Transform(dirtyBounds, worldToNavMesh, dirtyBoundsNavMesh);
|
||||
BoundingBox dirtyBoundsAligned;
|
||||
dirtyBoundsAligned.Minimum = Float3::Floor(dirtyBoundsNavMesh.Minimum / tileSize) * tileSize;
|
||||
dirtyBoundsAligned.Maximum = Float3::Ceil(dirtyBoundsNavMesh.Maximum / tileSize) * tileSize;
|
||||
dirtyBoundsNavMesh.Minimum = Float3::Floor(dirtyBoundsNavMesh.Minimum / tileSize) * tileSize;
|
||||
dirtyBoundsNavMesh.Maximum = Float3::Ceil(dirtyBoundsNavMesh.Maximum / tileSize) * tileSize;
|
||||
|
||||
// Calculate tiles range for the given navigation dirty bounds (aligned to tiles size)
|
||||
const Int3 tilesMin(dirtyBoundsAligned.Minimum / tileSize);
|
||||
const Int3 tilesMax(dirtyBoundsAligned.Maximum / tileSize);
|
||||
const int32 tilesX = tilesMax.X - tilesMin.X;
|
||||
const int32 tilesY = tilesMax.Z - tilesMin.Z;
|
||||
const Int3 tilesMin(dirtyBoundsNavMesh.Minimum / tileSize);
|
||||
const Int3 tilesMax(dirtyBoundsNavMesh.Maximum / tileSize);
|
||||
const int32 tilesXZ = (tilesMax.X - tilesMin.X) * (tilesMax.Z - tilesMin.Z);
|
||||
|
||||
{
|
||||
PROFILE_CPU_NAMED("Prepare");
|
||||
@@ -932,18 +933,18 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo
|
||||
// Remove all tiles from navmesh runtime
|
||||
runtime->RemoveTiles(navMesh);
|
||||
runtime->SetTileSize(tileSize);
|
||||
runtime->EnsureCapacity(tilesX * tilesY);
|
||||
runtime->EnsureCapacity(tilesXZ);
|
||||
|
||||
// Remove all tiles from navmesh data
|
||||
navMesh->Data.TileSize = tileSize;
|
||||
navMesh->Data.Tiles.Clear();
|
||||
navMesh->Data.Tiles.EnsureCapacity(tilesX * tilesX);
|
||||
navMesh->Data.Tiles.EnsureCapacity(tilesXZ);
|
||||
navMesh->IsDataDirty = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensure to have enough memory for tiles
|
||||
runtime->EnsureCapacity(tilesX * tilesY);
|
||||
runtime->EnsureCapacity(tilesXZ);
|
||||
}
|
||||
|
||||
runtime->Locker.Unlock();
|
||||
@@ -959,11 +960,10 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo
|
||||
|
||||
// Cache navmesh volumes
|
||||
Array<BoundingBox, InlinedAllocation<8>> volumes;
|
||||
for (int32 i = 0; i < scene->Navigation.Volumes.Count(); i++)
|
||||
for (const NavMeshBoundsVolume* volume : scene->Navigation.Volumes)
|
||||
{
|
||||
const auto volume = scene->Navigation.Volumes.Get()[i];
|
||||
if (!volume->AgentsMask.IsNavMeshSupported(navMesh->Properties) ||
|
||||
!volume->GetBox().Intersects(dirtyBoundsAligned))
|
||||
!volume->GetBox().Intersects(dirtyBoundsNavMesh))
|
||||
continue;
|
||||
auto& bounds = volumes.AddOne();
|
||||
BoundingBox::Transform(volume->GetBox(), worldToNavMesh, bounds);
|
||||
@@ -1026,7 +1026,7 @@ void BuildDirtyBounds(Scene* scene, NavMesh* navMesh, const BoundingBox& dirtyBo
|
||||
for (const auto& tile : unusedTiles)
|
||||
{
|
||||
// Wait for any async tasks that are producing this tile
|
||||
CancelNavMeshTileBuildTasks(runtime, tile.X, tile.Y);
|
||||
CancelNavMeshTileBuildTasks(runtime, tile.X, tile.Y, navMesh);
|
||||
}
|
||||
runtime->Locker.Lock();
|
||||
for (const auto& tile : unusedTiles)
|
||||
@@ -1095,6 +1095,7 @@ void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds, bool rebuild
|
||||
else if (settings->AutoAddMissingNavMeshes)
|
||||
{
|
||||
// Spawn missing navmesh
|
||||
PROFILE_MEM(Navigation);
|
||||
navMesh = New<NavMesh>();
|
||||
navMesh->SetStaticFlags(StaticFlags::FullyStatic);
|
||||
navMesh->SetName(TEXT("NavMesh.") + navMeshProperties.Name);
|
||||
@@ -1108,39 +1109,6 @@ void BuildDirtyBounds(Scene* scene, const BoundingBox& dirtyBounds, bool rebuild
|
||||
{
|
||||
BuildDirtyBounds(scene, navMesh, dirtyBounds, rebuild);
|
||||
}
|
||||
|
||||
// Remove unused navmeshes
|
||||
if (settings->AutoRemoveMissingNavMeshes)
|
||||
{
|
||||
for (NavMesh* navMesh : scene->Navigation.Meshes)
|
||||
{
|
||||
// Skip used navmeshes
|
||||
if (navMesh->Data.Tiles.HasItems())
|
||||
continue;
|
||||
|
||||
// Skip navmeshes during async building
|
||||
int32 usageCount = 0;
|
||||
NavBuildTasksLocker.Lock();
|
||||
for (int32 i = 0; i < NavBuildTasks.Count(); i++)
|
||||
{
|
||||
if (NavBuildTasks.Get()[i]->NavMesh == navMesh)
|
||||
usageCount++;
|
||||
}
|
||||
NavBuildTasksLocker.Unlock();
|
||||
if (usageCount != 0)
|
||||
continue;
|
||||
|
||||
navMesh->DeleteObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BuildWholeScene(Scene* scene)
|
||||
{
|
||||
// Compute total navigation area bounds
|
||||
const BoundingBox worldBounds = scene->Navigation.GetNavigationBounds();
|
||||
|
||||
BuildDirtyBounds(scene, worldBounds, true);
|
||||
}
|
||||
|
||||
void ClearNavigation(Scene* scene)
|
||||
@@ -1154,22 +1122,58 @@ void ClearNavigation(Scene* scene)
|
||||
}
|
||||
}
|
||||
|
||||
void BuildNavigation(BuildRequest& request)
|
||||
{
|
||||
// If scene is not specified then build all loaded scenes
|
||||
if (!request.Scene)
|
||||
{
|
||||
for (Scene* scene : Level::Scenes)
|
||||
{
|
||||
request.Scene = scene;
|
||||
BuildNavigation(request);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Early out if scene is not using navigation
|
||||
if (request.Scene->Navigation.Volumes.IsEmpty())
|
||||
{
|
||||
ClearNavigation(request.Scene);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if similar request is already in a queue
|
||||
for (auto& e : NavBuildQueue)
|
||||
{
|
||||
if (e.Scene == request.Scene && (e.DirtyBounds == request.DirtyBounds || request.DirtyBounds == BoundingBox::Empty))
|
||||
{
|
||||
e = request;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue request
|
||||
NavBuildQueue.Add(request);
|
||||
}
|
||||
|
||||
void NavMeshBuilder::Update()
|
||||
{
|
||||
PROFILE_MEM(Navigation);
|
||||
PROFILE_MEM(NavigationBuilding);
|
||||
ScopeLock lock(NavBuildQueueLocker);
|
||||
|
||||
// Process nav mesh building requests and kick the tasks
|
||||
const auto now = DateTime::NowUTC();
|
||||
bool didRebuild = false;
|
||||
for (int32 i = 0; NavBuildQueue.HasItems() && i < NavBuildQueue.Count(); i++)
|
||||
{
|
||||
auto req = NavBuildQueue.Get()[i];
|
||||
if (now - req.Time >= 0)
|
||||
{
|
||||
NavBuildQueue.RemoveAt(i--);
|
||||
const auto scene = req.Scene.Get();
|
||||
Scene* scene = req.Scene.Get();
|
||||
if (!scene)
|
||||
continue;
|
||||
bool rebuild = req.DirtyBounds == BoundingBox::Empty;
|
||||
|
||||
// Early out if scene has no bounds volumes to define nav mesh area
|
||||
if (scene->Navigation.Volumes.IsEmpty())
|
||||
@@ -1179,80 +1183,69 @@ void NavMeshBuilder::Update()
|
||||
}
|
||||
|
||||
// Check if build a custom dirty bounds or whole scene
|
||||
if (req.DirtyBounds == BoundingBox::Empty)
|
||||
{
|
||||
BuildWholeScene(scene);
|
||||
}
|
||||
if (rebuild)
|
||||
req.DirtyBounds = scene->Navigation.GetNavigationBounds(); // Compute total navigation area bounds
|
||||
if (didRebuild)
|
||||
rebuild = false; // When rebuilding navmesh for multiple scenes, rebuild only the first one (other scenes will use additive update)
|
||||
else
|
||||
didRebuild = true;
|
||||
BuildDirtyBounds(scene, req.DirtyBounds, rebuild);
|
||||
NavBuildCheckMissingNavMeshes = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unused navmeshes (when all active tasks are done)
|
||||
// TODO: ignore AutoRemoveMissingNavMeshes in game and make it editor-only?
|
||||
if (NavBuildCheckMissingNavMeshes && NavBuildTasksMaxCount == 0 && NavigationSettings::Get()->AutoRemoveMissingNavMeshes)
|
||||
{
|
||||
NavBuildCheckMissingNavMeshes = false;
|
||||
NavBuildTasksLocker.Lock();
|
||||
int32 taskCount = NavBuildTasks.Count();
|
||||
NavBuildTasksLocker.Unlock();
|
||||
if (taskCount == 0)
|
||||
{
|
||||
for (Scene* scene : Level::Scenes)
|
||||
{
|
||||
BuildDirtyBounds(scene, req.DirtyBounds, false);
|
||||
for (NavMesh* navMesh : scene->Navigation.Meshes)
|
||||
{
|
||||
if (!navMesh->Data.Tiles.HasItems())
|
||||
{
|
||||
navMesh->DeleteObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NavMeshBuilder::Build(Scene* scene, float timeoutMs)
|
||||
void Navigation::BuildNavMesh(Scene* scene, float timeoutMs)
|
||||
{
|
||||
if (!scene)
|
||||
{
|
||||
LOG(Warning, "Could not generate navmesh without scene.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Early out if scene is not using navigation
|
||||
if (scene->Navigation.Volumes.IsEmpty())
|
||||
{
|
||||
ClearNavigation(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
PROFILE_CPU_NAMED("NavMeshBuilder");
|
||||
PROFILE_MEM(Navigation);
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(NavigationBuilding);
|
||||
ScopeLock lock(NavBuildQueueLocker);
|
||||
|
||||
BuildRequest req;
|
||||
req.Scene = scene;
|
||||
req.Time = DateTime::NowUTC() + TimeSpan::FromMilliseconds(timeoutMs);
|
||||
req.DirtyBounds = BoundingBox::Empty;
|
||||
|
||||
for (int32 i = 0; i < NavBuildQueue.Count(); i++)
|
||||
{
|
||||
auto& e = NavBuildQueue.Get()[i];
|
||||
if (e.Scene == scene && e.DirtyBounds == req.DirtyBounds)
|
||||
{
|
||||
e = req;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NavBuildQueue.Add(req);
|
||||
req.SpecificScene = scene != nullptr;
|
||||
BuildNavigation(req);
|
||||
}
|
||||
|
||||
void NavMeshBuilder::Build(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs)
|
||||
void Navigation::BuildNavMesh(const BoundingBox& dirtyBounds, Scene* scene, float timeoutMs)
|
||||
{
|
||||
if (!scene)
|
||||
{
|
||||
LOG(Warning, "Could not generate navmesh without scene.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Early out if scene is not using navigation
|
||||
if (scene->Navigation.Volumes.IsEmpty())
|
||||
{
|
||||
ClearNavigation(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
PROFILE_CPU_NAMED("NavMeshBuilder");
|
||||
PROFILE_MEM(Navigation);
|
||||
if (dirtyBounds.GetVolume() <= ZeroTolerance)
|
||||
return; // Skip updating empty bounds
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(NavigationBuilding);
|
||||
ScopeLock lock(NavBuildQueueLocker);
|
||||
|
||||
BuildRequest req;
|
||||
req.Scene = scene;
|
||||
req.Time = DateTime::NowUTC() + TimeSpan::FromMilliseconds(timeoutMs);
|
||||
req.DirtyBounds = dirtyBounds;
|
||||
|
||||
NavBuildQueue.Add(req);
|
||||
req.SpecificScene = scene != nullptr;
|
||||
BuildNavigation(req);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -15,11 +15,7 @@ class FLAXENGINE_API NavMeshBuilder
|
||||
{
|
||||
public:
|
||||
static void Init();
|
||||
static bool IsBuildingNavMesh();
|
||||
static float GetNavMeshBuildingProgress();
|
||||
static void Update();
|
||||
static void Build(Scene* scene, float timeoutMs);
|
||||
static void Build(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#include "NavMesh.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Random.h"
|
||||
#if COMPILE_WITH_DEBUG_DRAW
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
#endif
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
@@ -326,7 +329,7 @@ void NavMeshRuntime::EnsureCapacity(int32 tilesToAddCount)
|
||||
if (newTilesCount <= capacity)
|
||||
return;
|
||||
PROFILE_CPU_NAMED("NavMeshRuntime.EnsureCapacity");
|
||||
PROFILE_MEM(Navigation);
|
||||
PROFILE_MEM(NavigationMesh);
|
||||
|
||||
// Navmesh tiles capacity growing rule
|
||||
int32 newCapacity = capacity ? capacity : 32;
|
||||
@@ -387,7 +390,7 @@ void NavMeshRuntime::AddTiles(NavMesh* navMesh)
|
||||
return;
|
||||
auto& data = navMesh->Data;
|
||||
PROFILE_CPU_NAMED("NavMeshRuntime.AddTiles");
|
||||
PROFILE_MEM(Navigation);
|
||||
PROFILE_MEM(NavigationMesh);
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Validate data (must match navmesh) or init navmesh to match the tiles options
|
||||
@@ -419,7 +422,7 @@ void NavMeshRuntime::AddTile(NavMesh* navMesh, NavMeshTileData& tileData)
|
||||
ASSERT(navMesh);
|
||||
auto& data = navMesh->Data;
|
||||
PROFILE_CPU_NAMED("NavMeshRuntime.AddTile");
|
||||
PROFILE_MEM(Navigation);
|
||||
PROFILE_MEM(NavigationMesh);
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Validate data (must match navmesh) or init navmesh to match the tiles options
|
||||
@@ -603,7 +606,21 @@ void NavMeshRuntime::DebugDraw()
|
||||
if (!tile->header)
|
||||
continue;
|
||||
|
||||
//DebugDraw::DrawWireBox(*(BoundingBox*)&tile->header->bmin[0], Color::CadetBlue);
|
||||
#if 0
|
||||
// Debug draw tile bounds and owner scene name
|
||||
BoundingBox tileBounds = *(BoundingBox*)&tile->header->bmin[0];
|
||||
DebugDraw::DrawWireBox(tileBounds, Color::CadetBlue);
|
||||
// TODO: build map from tile coords to tile data to avoid this loop
|
||||
for (const auto& e : _tiles)
|
||||
{
|
||||
if (e.X == tile->header->x && e.Y == tile->header->y && e.Layer == tile->header->layer)
|
||||
{
|
||||
if (e.NavMesh && e.NavMesh->GetScene())
|
||||
DebugDraw::DrawText(e.NavMesh->GetScene()->GetName(), tileBounds.Minimum + tileBounds.GetSize() * Float3(0.5f, 0.8f, 0.5f), Color::CadetBlue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < tile->header->polyCount; i++)
|
||||
{
|
||||
|
||||
@@ -111,7 +111,7 @@ public:
|
||||
/// <param name="startPosition">The start position.</param>
|
||||
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
|
||||
/// <param name="maxDistance">The maximum distance to search for wall (search radius).</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool FindDistanceToWall(const Vector3& startPosition, NavMeshHit& hitInfo, float maxDistance = MAX_float) const;
|
||||
|
||||
/// <summary>
|
||||
@@ -187,7 +187,7 @@ public:
|
||||
/// <param name="startPosition">The start position.</param>
|
||||
/// <param name="endPosition">The end position.</param>
|
||||
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool RayCast(const Vector3& startPosition, const Vector3& endPosition, API_PARAM(Out) NavMeshHit& hitInfo) const;
|
||||
|
||||
public:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "NavModifierVolume.h"
|
||||
#include "NavigationSettings.h"
|
||||
#include "NavMeshBuilder.h"
|
||||
#include "Navigation.h"
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#if USE_EDITOR
|
||||
@@ -83,7 +83,7 @@ void NavModifierVolume::OnBoundsChanged(const BoundingBox& prevBounds)
|
||||
#else
|
||||
const float timeoutMs = 0.0f;
|
||||
#endif
|
||||
NavMeshBuilder::Build(GetScene(), dirtyBounds, timeoutMs);
|
||||
Navigation::BuildNavMesh(dirtyBounds, GetScene(), timeoutMs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ NavigationService NavigationServiceInstance;
|
||||
|
||||
void* dtAllocDefault(size_t size, dtAllocHint)
|
||||
{
|
||||
PROFILE_MEM(Navigation);
|
||||
PROFILE_MEM(NavigationMesh);
|
||||
return Allocator::Allocate(size);
|
||||
}
|
||||
|
||||
@@ -382,30 +382,6 @@ bool Navigation::RayCast(const Vector3& startPosition, const Vector3& endPositio
|
||||
return NavMeshes.First()->RayCast(startPosition, endPosition, hitInfo);
|
||||
}
|
||||
|
||||
#if COMPILE_WITH_NAV_MESH_BUILDER
|
||||
|
||||
bool Navigation::IsBuildingNavMesh()
|
||||
{
|
||||
return NavMeshBuilder::IsBuildingNavMesh();
|
||||
}
|
||||
|
||||
float Navigation::GetNavMeshBuildingProgress()
|
||||
{
|
||||
return NavMeshBuilder::GetNavMeshBuildingProgress();
|
||||
}
|
||||
|
||||
void Navigation::BuildNavMesh(Scene* scene, float timeoutMs)
|
||||
{
|
||||
NavMeshBuilder::Build(scene, timeoutMs);
|
||||
}
|
||||
|
||||
void Navigation::BuildNavMesh(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs)
|
||||
{
|
||||
NavMeshBuilder::Build(scene, dirtyBounds, timeoutMs);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if COMPILE_WITH_DEBUG_DRAW
|
||||
|
||||
void Navigation::DrawNavMesh()
|
||||
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
/// <param name="startPosition">The start position.</param>
|
||||
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
|
||||
/// <param name="maxDistance">The maximum distance to search for wall (search radius).</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool FindDistanceToWall(const Vector3& startPosition, API_PARAM(Out) NavMeshHit& hitInfo, float maxDistance = MAX_float);
|
||||
|
||||
/// <summary>
|
||||
@@ -81,12 +81,10 @@ public:
|
||||
/// <param name="startPosition">The start position.</param>
|
||||
/// <param name="endPosition">The end position.</param>
|
||||
/// <param name="hitInfo">The result hit information. Valid only when query succeed.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool RayCast(const Vector3& startPosition, const Vector3& endPosition, API_PARAM(Out) NavMeshHit& hitInfo);
|
||||
|
||||
public:
|
||||
#if COMPILE_WITH_NAV_MESH_BUILDER
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if navigation system is during navmesh building (any request is valid or async task active).
|
||||
/// </summary>
|
||||
@@ -100,32 +98,49 @@ public:
|
||||
/// <summary>
|
||||
/// Builds the Nav Mesh for the given scene (discards all its tiles).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.
|
||||
/// </remarks>
|
||||
/// <param name="scene">The scene.</param>
|
||||
/// <remarks>Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.</remarks>
|
||||
/// <param name="scene">The scene. Pass null to build navmesh for all loaded scenes.</param>
|
||||
/// <param name="timeoutMs">The timeout to wait before building Nav Mesh (in milliseconds).</param>
|
||||
API_FUNCTION() static void BuildNavMesh(Scene* scene, float timeoutMs = 50);
|
||||
API_FUNCTION() static void BuildNavMesh(Scene* scene = nullptr, float timeoutMs = 50);
|
||||
|
||||
/// <summary>
|
||||
/// Builds the Nav Mesh for the given scene (builds only the tiles overlapping the given bounding box).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.
|
||||
/// </remarks>
|
||||
/// <remarks>Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.</remarks>
|
||||
/// <param name="dirtyBounds">The bounds in world-space to build overlapping tiles.</param>
|
||||
/// <param name="scene">The scene. Pass null to build navmesh for all loaded scenes that intersect with a given bounds.</param>
|
||||
/// <param name="timeoutMs">The timeout to wait before building Nav Mesh (in milliseconds).</param>
|
||||
API_FUNCTION() static void BuildNavMesh(const BoundingBox& dirtyBounds, Scene* scene = nullptr, float timeoutMs = 50);
|
||||
|
||||
/// <summary>
|
||||
/// Builds the Nav Mesh for all the loaded scenes (builds only the tiles overlapping the given bounding box).
|
||||
/// </summary>
|
||||
/// <remarks>Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.</remarks>
|
||||
/// <param name="dirtyBounds">The bounds in world-space to build overlapping tiles.</param>
|
||||
/// <param name="timeoutMs">The timeout to wait before building Nav Mesh (in milliseconds).</param>
|
||||
API_FUNCTION() static void BuildNavMesh(const BoundingBox& dirtyBounds, float timeoutMs = 50)
|
||||
{
|
||||
BuildNavMesh(dirtyBounds, nullptr, timeoutMs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the Nav Mesh for the given scene (builds only the tiles overlapping the given bounding box).
|
||||
/// [Deprecated in v1.12]
|
||||
/// </summary>
|
||||
/// <remarks>Requests are enqueued till the next game scripts update. Actual navmesh building in done via Thread Pool tasks in a background to prevent game thread stalls.</remarks>
|
||||
/// <param name="scene">The scene.</param>
|
||||
/// <param name="dirtyBounds">The bounds in world-space to build overlapping tiles.</param>
|
||||
/// <param name="timeoutMs">The timeout to wait before building Nav Mesh (in milliseconds).</param>
|
||||
API_FUNCTION() static void BuildNavMesh(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs = 50);
|
||||
|
||||
API_FUNCTION() DEPRECATED("Use BuildNavMesh with reordered arguments instead") static void BuildNavMesh(Scene* scene, const BoundingBox& dirtyBounds, float timeoutMs = 50)
|
||||
{
|
||||
BuildNavMesh(dirtyBounds, scene, timeoutMs);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if COMPILE_WITH_DEBUG_DRAW
|
||||
|
||||
/// <summary>
|
||||
/// Draws the navigation for all the scenes (uses DebugDraw interface).
|
||||
/// </summary>
|
||||
static void DrawNavMesh();
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -1029,7 +1029,7 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b
|
||||
}
|
||||
|
||||
// Speed up replication of client-owned objects to other clients from server to reduce lag (data has to go from client to server and then to other clients)
|
||||
if (NetworkManager::IsServer())
|
||||
if (NetworkManager::IsServer() || NetworkManager::IsHost())
|
||||
DirtyObjectImpl(item, obj);
|
||||
}
|
||||
|
||||
@@ -2147,7 +2147,7 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
Objects.Remove(it);
|
||||
continue;
|
||||
}
|
||||
if (item.Role != NetworkObjectRole::OwnedAuthoritative)
|
||||
if (item.Role != NetworkObjectRole::OwnedAuthoritative && NetworkManager::IsClient())
|
||||
continue; // Send replication messages of only owned objects or from other client objects
|
||||
CachedReplicationResult->AddObject(obj);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
#include "Engine/Level/Scene/Scene.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#if USE_EDITOR
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/Managed/ManagedEditor.h"
|
||||
#endif
|
||||
|
||||
ParticleEffect::ParticleEffect(const SpawnParams& params)
|
||||
: Actor(params)
|
||||
@@ -465,7 +469,12 @@ void ParticleEffect::Update()
|
||||
if (UpdateMode == SimulationUpdateMode::FixedTimestep)
|
||||
{
|
||||
// Check if last simulation update was past enough to kick a new on
|
||||
const float time = Time::Update.Time.GetTotalSeconds();
|
||||
bool useTimeScale = UseTimeScale;
|
||||
#if USE_EDITOR
|
||||
if (!Editor::IsPlayMode && IsDuringPlay())
|
||||
useTimeScale = false;
|
||||
#endif
|
||||
const float time = (useTimeScale ? Time::Update.Time : Time::Update.UnscaledTime).GetTotalSeconds();
|
||||
if (time - Instance.LastUpdateTime < FixedTimestep)
|
||||
return;
|
||||
}
|
||||
@@ -475,9 +484,6 @@ void ParticleEffect::Update()
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "Editor/Editor.h"
|
||||
#include "Editor/Managed/ManagedEditor.h"
|
||||
|
||||
void ParticleEffect::UpdateExecuteInEditor()
|
||||
{
|
||||
// Auto-play in Editor
|
||||
|
||||
@@ -677,11 +677,10 @@ void CleanupGPUParticlesSorting()
|
||||
SAFE_DELETE_GPU_RESOURCE(GPUIndirectArgsBuffer);
|
||||
}
|
||||
|
||||
void DrawEmittersGPU(RenderContextBatch& renderContextBatch)
|
||||
void DrawEmittersGPU(GPUContext* context, RenderContextBatch& renderContextBatch)
|
||||
{
|
||||
PROFILE_GPU_CPU_NAMED("DrawEmittersGPU");
|
||||
ScopeReadLock systemScope(Particles::SystemLocker);
|
||||
GPUContext* context = GPUDevice::Instance->GetMainContext();
|
||||
|
||||
// Count draws and sorting passes needed for resources allocation
|
||||
uint32 indirectArgsSize = 0;
|
||||
@@ -1124,9 +1123,9 @@ void DrawEmitterGPU(RenderContextBatch& renderContextBatch, ParticleBuffer* buff
|
||||
if (GPUEmitterDraws.Count() == 0)
|
||||
{
|
||||
// The first emitter schedules the drawing of all batched draws
|
||||
renderContextBatch.GetMainContext().List->AddDelayedDraw([](RenderContextBatch& renderContextBatch, int32 contextIndex)
|
||||
renderContextBatch.GetMainContext().List->AddDelayedDraw([](GPUContext* context, RenderContextBatch& renderContextBatch, int32 renderContextIndex)
|
||||
{
|
||||
DrawEmittersGPU(renderContextBatch);
|
||||
DrawEmittersGPU(context, renderContextBatch);
|
||||
});
|
||||
}
|
||||
GPUEmitterDraws.Add({ buffer, drawCall, drawModes, staticFlags, bounds, renderModulesIndices, indirectArgsSize, sortOrder, sorting });
|
||||
|
||||
@@ -2784,6 +2784,69 @@ float PhysicsBackend::ComputeShapeSqrDistanceToPoint(void* shape, const Vector3&
|
||||
{
|
||||
auto shapePhysX = (PxShape*)shape;
|
||||
const PxTransform trans(C2P(position), C2P(orientation));
|
||||
|
||||
// Special case for heightfield collider (not implemented in PhysX)
|
||||
if (shapePhysX->getGeometryType() == PxGeometryType::eHEIGHTFIELD)
|
||||
{
|
||||
// Do a bunch of raycasts in all directions to find the closest point on the heightfield
|
||||
PxVec3 origin = C2P(point);
|
||||
Array<PxVec3> unitDirections;
|
||||
constexpr int32 resolution = 32;
|
||||
unitDirections.EnsureCapacity((resolution + 1) * (resolution + 1));
|
||||
for (int32 i = 0; i <= resolution; i++)
|
||||
{
|
||||
float phi = PI * (float)i / resolution;
|
||||
float sinPhi = Math::Sin(phi);
|
||||
float cosPhi = Math::Cos(phi);
|
||||
for (int32 j = 0; j <= resolution; j++)
|
||||
{
|
||||
float theta = 2.0f * PI * (float)j / resolution;
|
||||
float cosTheta = Math::Cos(theta);
|
||||
float sinTheta = Math::Sin(theta);
|
||||
|
||||
PxVec3 v;
|
||||
v.x = cosTheta * sinPhi;
|
||||
v.y = cosPhi;
|
||||
v.z = sinTheta * sinPhi;
|
||||
|
||||
// All generated vectors are unit vectors (length 1)
|
||||
unitDirections.Add(v);
|
||||
}
|
||||
}
|
||||
|
||||
PxReal maxDistance = PX_MAX_REAL; // Search indefinitely
|
||||
PxQueryFilterData filterData;
|
||||
filterData.data.word0 = (PxU32)shapePhysX->getSimulationFilterData().word0;
|
||||
PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eMESH_BOTH_SIDES; // Both sides added for if it is underneath the height field
|
||||
PxRaycastBuffer buffer;
|
||||
auto scene = shapePhysX->getActor()->getScene();
|
||||
|
||||
PxReal closestDistance = maxDistance;
|
||||
PxVec3 tempClosestPoint;
|
||||
for (PxVec3& unitDir : unitDirections)
|
||||
{
|
||||
bool hitResult = scene->raycast(origin, unitDir, maxDistance, buffer, hitFlags, filterData);
|
||||
if (hitResult)
|
||||
{
|
||||
auto& hit = buffer.getAnyHit(0);
|
||||
if (hit.distance < closestDistance && hit.distance > 0.0f)
|
||||
{
|
||||
tempClosestPoint = hit.position;
|
||||
closestDistance = hit.distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (closestDistance < maxDistance)
|
||||
{
|
||||
*closestPoint = P2C(tempClosestPoint);
|
||||
return closestDistance * closestDistance; // Result is squared distance
|
||||
}
|
||||
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
// Default point distance for other collider queries
|
||||
#if USE_LARGE_WORLDS
|
||||
PxVec3 closestPointPx;
|
||||
float result = PxGeometryQuery::pointDistance(C2P(point), shapePhysX->getGeometry(), trans, &closestPointPx);
|
||||
|
||||
@@ -102,7 +102,7 @@ public:
|
||||
/// <param name="end">The end position of the line.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool LineCast(const Vector3& start, const Vector3& end, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -113,18 +113,18 @@ public:
|
||||
/// <param name="hitInfo">The result hit information. Valid only when method returns true.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool LineCast(const Vector3& start, const Vector3& end, API_PARAM(Out) RayCastHit& hitInfo, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
// <summary>
|
||||
/// Performs a line between two points in the scene, returns all hitpoints infos.
|
||||
/// Performs a line between two points in the scene, returns all hit points info.
|
||||
/// </summary>
|
||||
/// <param name="start">The origin of the ray.</param>
|
||||
/// <param name="end">The end position of the line.</param>
|
||||
/// <param name="results">The result hits. Valid only when method returns true.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool LineCastAll(const Vector3& start, const Vector3& end, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -135,7 +135,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool RayCast(const Vector3& origin, const Vector3& direction, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -147,7 +147,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -159,7 +159,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool RayCastAll(const Vector3& origin, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -172,7 +172,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if box hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if box hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -186,7 +186,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if box hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if box hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -200,7 +200,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if box hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if box hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool BoxCastAll(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -212,7 +212,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if sphere hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool SphereCast(const Vector3& center, float radius, const Vector3& direction, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -225,7 +225,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if sphere hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool SphereCast(const Vector3& center, float radius, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -238,7 +238,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if sphere hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool SphereCastAll(const Vector3& center, float radius, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -252,7 +252,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if capsule hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool CapsuleCast(const Vector3& center, float radius, float height, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -267,7 +267,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if capsule hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool CapsuleCast(const Vector3& center, float radius, float height, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -282,7 +282,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if capsule hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool CapsuleCastAll(const Vector3& center, float radius, float height, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -296,7 +296,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if convex mesh hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -311,7 +311,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if convex mesh hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -326,7 +326,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if convex mesh hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() static bool ConvexCastAll(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -375,7 +375,7 @@ public:
|
||||
API_FUNCTION() static bool CheckConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given box.
|
||||
/// Finds all colliders touching or inside the given box.
|
||||
/// </summary>
|
||||
/// <param name="center">The box center.</param>
|
||||
/// <param name="halfExtents">The half size of the box in each direction.</param>
|
||||
@@ -387,7 +387,7 @@ public:
|
||||
API_FUNCTION() static bool OverlapBox(const Vector3& center, const Vector3& halfExtents, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given sphere.
|
||||
/// Finds all colliders touching or inside the given sphere.
|
||||
/// </summary>
|
||||
/// <param name="center">The sphere center.</param>
|
||||
/// <param name="radius">The radius of the sphere.</param>
|
||||
@@ -398,7 +398,7 @@ public:
|
||||
API_FUNCTION() static bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given capsule.
|
||||
/// Finds all colliders touching or inside the given capsule.
|
||||
/// </summary>
|
||||
/// <param name="center">The capsule center.</param>
|
||||
/// <param name="radius">The radius of the capsule.</param>
|
||||
@@ -411,7 +411,7 @@ public:
|
||||
API_FUNCTION() static bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given convex mesh.
|
||||
/// Finds all colliders touching or inside the given convex mesh.
|
||||
/// </summary>
|
||||
/// <param name="center">The convex mesh center.</param>
|
||||
/// <param name="convexMesh">Collision data of the convex mesh.</param>
|
||||
@@ -424,7 +424,7 @@ public:
|
||||
API_FUNCTION() static bool OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given box.
|
||||
/// Finds all colliders touching or inside the given box.
|
||||
/// </summary>
|
||||
/// <param name="center">The box center.</param>
|
||||
/// <param name="halfExtents">The half size of the box in each direction.</param>
|
||||
@@ -436,7 +436,7 @@ public:
|
||||
API_FUNCTION() static bool OverlapBox(const Vector3& center, const Vector3& halfExtents, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given sphere.
|
||||
/// Finds all colliders touching or inside the given sphere.
|
||||
/// </summary>
|
||||
/// <param name="center">The sphere center.</param>
|
||||
/// <param name="radius">The radius of the sphere.</param>
|
||||
@@ -447,7 +447,7 @@ public:
|
||||
API_FUNCTION() static bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given capsule.
|
||||
/// Finds all colliders touching or inside the given capsule.
|
||||
/// </summary>
|
||||
/// <param name="center">The capsule center.</param>
|
||||
/// <param name="radius">The radius of the capsule.</param>
|
||||
@@ -460,7 +460,7 @@ public:
|
||||
API_FUNCTION() static bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given convex mesh.
|
||||
/// Finds all colliders touching or inside the given convex mesh.
|
||||
/// </summary>
|
||||
/// <param name="center">The convex mesh center.</param>
|
||||
/// <param name="convexMesh">Collision data of the convex mesh.</param>
|
||||
|
||||
@@ -140,7 +140,7 @@ public:
|
||||
/// <param name="end">The end position of the line.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool LineCast(const Vector3& start, const Vector3& end, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -151,18 +151,18 @@ public:
|
||||
/// <param name="hitInfo">The result hit information. Valid only when method returns true.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool LineCast(const Vector3& start, const Vector3& end, API_PARAM(Out) RayCastHit& hitInfo, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
// <summary>
|
||||
/// Performs a line between two points in the scene, returns all hitpoints infos.
|
||||
/// Performs a line between two points in the scene, returns all hit points info.
|
||||
/// </summary>
|
||||
/// <param name="start">The origin of the ray.</param>
|
||||
/// <param name="end">The normalized direction of the ray.</param>
|
||||
/// <param name="results">The result hits. Valid only when method returns true.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool LineCastAll(const Vector3& start, const Vector3& end, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -173,7 +173,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -185,7 +185,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -197,7 +197,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if ray hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if ray hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool RayCastAll(const Vector3& origin, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -210,7 +210,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if box hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if box hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -224,7 +224,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if box hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if box hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -238,7 +238,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if box hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if box hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool BoxCastAll(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -250,7 +250,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if sphere hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool SphereCast(const Vector3& center, float radius, const Vector3& direction, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -263,7 +263,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if sphere hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool SphereCast(const Vector3& center, float radius, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -276,7 +276,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if sphere hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool SphereCastAll(const Vector3& center, float radius, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -290,7 +290,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if capsule hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool CapsuleCast(const Vector3& center, float radius, float height, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -305,7 +305,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if capsule hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool CapsuleCast(const Vector3& center, float radius, float height, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -320,7 +320,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if capsule hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool CapsuleCastAll(const Vector3& center, float radius, float height, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -334,7 +334,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if convex mesh hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -349,7 +349,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if convex mesh hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -364,7 +364,7 @@ public:
|
||||
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
|
||||
/// <param name="layerMask">The layer mask used to filter the results.</param>
|
||||
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
|
||||
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
|
||||
/// <returns>True if convex mesh hits a matching object, otherwise false.</returns>
|
||||
API_FUNCTION() bool ConvexCastAll(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
@@ -413,7 +413,7 @@ public:
|
||||
API_FUNCTION() bool CheckConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given box.
|
||||
/// Finds all colliders touching or inside the given box.
|
||||
/// </summary>
|
||||
/// <param name="center">The box center.</param>
|
||||
/// <param name="halfExtents">The half size of the box in each direction.</param>
|
||||
@@ -425,7 +425,7 @@ public:
|
||||
API_FUNCTION() bool OverlapBox(const Vector3& center, const Vector3& halfExtents, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given sphere.
|
||||
/// Finds all colliders touching or inside the given sphere.
|
||||
/// </summary>
|
||||
/// <param name="center">The sphere center.</param>
|
||||
/// <param name="radius">The radius of the sphere.</param>
|
||||
@@ -436,7 +436,7 @@ public:
|
||||
API_FUNCTION() bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given capsule.
|
||||
/// Finds all colliders touching or inside the given capsule.
|
||||
/// </summary>
|
||||
/// <param name="center">The capsule center.</param>
|
||||
/// <param name="radius">The radius of the capsule.</param>
|
||||
@@ -449,7 +449,7 @@ public:
|
||||
API_FUNCTION() bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given convex mesh.
|
||||
/// Finds all colliders touching or inside the given convex mesh.
|
||||
/// </summary>
|
||||
/// <param name="center">The convex mesh center.</param>
|
||||
/// <param name="convexMesh">Collision data of the convex mesh.</param>
|
||||
@@ -462,7 +462,7 @@ public:
|
||||
API_FUNCTION() bool OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given box.
|
||||
/// Finds all colliders touching or inside the given box.
|
||||
/// </summary>
|
||||
/// <param name="center">The box center.</param>
|
||||
/// <param name="halfExtents">The half size of the box in each direction.</param>
|
||||
@@ -474,7 +474,7 @@ public:
|
||||
API_FUNCTION() bool OverlapBox(const Vector3& center, const Vector3& halfExtents, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given sphere.
|
||||
/// Finds all colliders touching or inside the given sphere.
|
||||
/// </summary>
|
||||
/// <param name="center">The sphere center.</param>
|
||||
/// <param name="radius">The radius of the sphere.</param>
|
||||
@@ -485,7 +485,7 @@ public:
|
||||
API_FUNCTION() bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given capsule.
|
||||
/// Finds all colliders touching or inside the given capsule.
|
||||
/// </summary>
|
||||
/// <param name="center">The capsule center.</param>
|
||||
/// <param name="radius">The radius of the capsule.</param>
|
||||
@@ -498,7 +498,7 @@ public:
|
||||
API_FUNCTION() bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds all colliders touching or inside of the given convex mesh.
|
||||
/// Finds all colliders touching or inside the given convex mesh.
|
||||
/// </summary>
|
||||
/// <param name="center">The convex mesh center.</param>
|
||||
/// <param name="convexMesh">Collision data of the convex mesh.</param>
|
||||
|
||||
@@ -12,25 +12,25 @@
|
||||
|
||||
bool FileSystemBase::ShowOpenFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array<String, HeapAllocation>& filenames)
|
||||
{
|
||||
// No supported
|
||||
// Not supported
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSystemBase::ShowSaveFileDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& filter, bool multiSelect, const StringView& title, Array<String, HeapAllocation>& filenames)
|
||||
{
|
||||
// No supported
|
||||
// Not supported
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSystemBase::ShowBrowseFolderDialog(Window* parentWindow, const StringView& initialDirectory, const StringView& title, String& path)
|
||||
{
|
||||
// No supported
|
||||
// Not supported
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSystemBase::ShowFileExplorer(const StringView& path)
|
||||
{
|
||||
// No supported
|
||||
// Not supported
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -243,6 +243,7 @@ void InitProfilerMemory(const Char* cmdLine, int32 stage)
|
||||
#define INIT_PARENT(parent, child) GroupParents[(int32)ProfilerMemory::Groups::child] = (uint8)ProfilerMemory::Groups::parent
|
||||
INIT_PARENT(Engine, EngineThreading);
|
||||
INIT_PARENT(Engine, EngineDelegate);
|
||||
INIT_PARENT(Engine, EngineDebug);
|
||||
INIT_PARENT(Malloc, MallocArena);
|
||||
INIT_PARENT(Graphics, GraphicsTextures);
|
||||
INIT_PARENT(Graphics, GraphicsRenderTargets);
|
||||
@@ -260,6 +261,8 @@ void InitProfilerMemory(const Char* cmdLine, int32 stage)
|
||||
INIT_PARENT(Content, ContentFiles);
|
||||
INIT_PARENT(Level, LevelFoliage);
|
||||
INIT_PARENT(Level, LevelTerrain);
|
||||
INIT_PARENT(Navigation, NavigationMesh);
|
||||
INIT_PARENT(Navigation, NavigationBuilding);
|
||||
INIT_PARENT(Scripting, ScriptingVisual);
|
||||
INIT_PARENT(Scripting, ScriptingCSharp);
|
||||
INIT_PARENT(ScriptingCSharp, ScriptingCSharpGCCommitted);
|
||||
|
||||
@@ -44,6 +44,8 @@ public:
|
||||
EngineThreading,
|
||||
// Memory used by Delegate (engine events system to store all references).
|
||||
EngineDelegate,
|
||||
// Memory used by debug tools (eg. DebugDraw, DebugCommands or DebugLog).
|
||||
EngineDebug,
|
||||
|
||||
// Total graphics memory usage.
|
||||
Graphics,
|
||||
@@ -105,6 +107,10 @@ public:
|
||||
|
||||
// Total navigation system memory.
|
||||
Navigation,
|
||||
// Navigation mesh memory.
|
||||
NavigationMesh,
|
||||
// Navigation mesh builder memory.
|
||||
NavigationBuilding,
|
||||
|
||||
// Total networking system memory.
|
||||
Networking,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "Engine/Profiler/Profiler.h"
|
||||
#include "Engine/Content/Assets/CubeTexture.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Math/Half.h"
|
||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||
#include "Engine/Level/Scene/Lightmap.h"
|
||||
#include "Engine/Level/Actors/PostFxVolume.h"
|
||||
@@ -30,6 +31,13 @@ namespace
|
||||
Array<RenderList*> FreeRenderList;
|
||||
Array<Pair<void*, uintptr>> MemPool;
|
||||
CriticalSection MemPoolLocker;
|
||||
|
||||
typedef Array<RenderList::IExtension*, FixedAllocation<8>> ExtensionsList;
|
||||
ExtensionsList& GetExtensions()
|
||||
{
|
||||
static ExtensionsList list;
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderObjectData::Store(const Matrix& worldMatrix, const Matrix& prevWorldMatrix, const Rectangle& lightmapUVsArea, const Float3& geometrySize, float perInstanceRandom, float worldDeterminantSign, float lodDitherFactor)
|
||||
@@ -235,6 +243,16 @@ void RenderList::CleanupCache()
|
||||
MemPoolLocker.Unlock();
|
||||
}
|
||||
|
||||
RenderList::IExtension::IExtension()
|
||||
{
|
||||
GetExtensions().Add(this);
|
||||
}
|
||||
|
||||
RenderList::IExtension::~IExtension()
|
||||
{
|
||||
GetExtensions().Remove(this);
|
||||
}
|
||||
|
||||
bool RenderList::BlendableSettings::operator<(const BlendableSettings& other) const
|
||||
{
|
||||
// Sort by higher priority
|
||||
@@ -257,18 +275,31 @@ void RenderList::AddSettingsBlend(IPostFxSettingsProvider* provider, float weigh
|
||||
|
||||
void RenderList::AddDelayedDraw(DelayedDraw&& func)
|
||||
{
|
||||
MemPoolLocker.Lock(); // TODO: convert _delayedDraws into RenderListBuffer with usage of arena Memory for fast alloc
|
||||
_delayedDraws.Add(MoveTemp(func));
|
||||
MemPoolLocker.Unlock();
|
||||
}
|
||||
|
||||
void RenderList::DrainDelayedDraws(RenderContextBatch& renderContextBatch, int32 contextIndex)
|
||||
void RenderList::DrainDelayedDraws(GPUContext* context, RenderContextBatch& renderContextBatch, int32 renderContextIndex)
|
||||
{
|
||||
if (_delayedDraws.IsEmpty())
|
||||
if (_delayedDraws.Count() == 0)
|
||||
return;
|
||||
PROFILE_CPU();
|
||||
for (DelayedDraw& e : _delayedDraws)
|
||||
e(renderContextBatch, contextIndex);
|
||||
_delayedDraws.SetCapacity(0);
|
||||
e(context, renderContextBatch, renderContextIndex);
|
||||
_delayedDraws.Clear();
|
||||
}
|
||||
|
||||
#define LOOP_EXTENSIONS() const auto& extensions = GetExtensions(); for (auto* e : extensions)
|
||||
|
||||
void RenderList::PreDraw(GPUContext* context, RenderContextBatch& renderContextBatch)
|
||||
{
|
||||
LOOP_EXTENSIONS()
|
||||
e->PreDraw(context, renderContextBatch);
|
||||
}
|
||||
|
||||
void RenderList::PostDraw(GPUContext* context, RenderContextBatch& renderContextBatch)
|
||||
{
|
||||
LOOP_EXTENSIONS()
|
||||
e->PostDraw(context, renderContextBatch);
|
||||
}
|
||||
|
||||
void RenderList::BlendSettings()
|
||||
@@ -494,7 +525,6 @@ RenderList::RenderList(const SpawnParams& params)
|
||||
, ObjectBuffer(0, PixelFormat::R32G32B32A32_Float, false, TEXT("Object Buffer"))
|
||||
, TempObjectBuffer(0, PixelFormat::R32G32B32A32_Float, false, TEXT("Object Buffer"))
|
||||
, _instanceBuffer(0, sizeof(ShaderObjectDrawInstanceData), TEXT("Instance Buffer"), GPUVertexLayout::Get({ { VertexElement::Types::Attribute0, 3, 0, 1, PixelFormat::R32_UInt } }))
|
||||
, _delayedDraws(&Memory)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -826,6 +856,13 @@ FORCE_INLINE bool DrawsEqual(const DrawCall* a, const DrawCall* b)
|
||||
Platform::MemoryCompare(a->Geometry.VertexBuffers, b->Geometry.VertexBuffers, sizeof(a->Geometry.VertexBuffers) + sizeof(a->Geometry.VertexBuffersOffsets)) == 0;
|
||||
}
|
||||
|
||||
FORCE_INLINE Span<GPUBuffer*> GetVB(GPUBuffer* const* ptr, int32 maxSize)
|
||||
{
|
||||
while (ptr[maxSize - 1] == nullptr && maxSize > 1)
|
||||
maxSize--;
|
||||
return ToSpan<GPUBuffer*>(ptr, maxSize);
|
||||
}
|
||||
|
||||
void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsList& list, RenderList* drawCallsList, GPUTextureView* input)
|
||||
{
|
||||
if (list.IsEmpty())
|
||||
@@ -954,7 +991,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL
|
||||
Platform::MemoryCopy(vb, activeDraw->Geometry.VertexBuffers, sizeof(DrawCall::Geometry.VertexBuffers));
|
||||
Platform::MemoryCopy(vbOffsets, activeDraw->Geometry.VertexBuffersOffsets, sizeof(DrawCall::Geometry.VertexBuffersOffsets));
|
||||
context->BindIB(activeDraw->Geometry.IndexBuffer);
|
||||
context->BindVB(ToSpan(vb, ARRAY_COUNT(vb)), vbOffsets);
|
||||
context->BindVB(GetVB(vb, ARRAY_COUNT(vb)), vbOffsets);
|
||||
context->DrawIndexedInstanced(activeDraw->Draw.IndicesCount, activeCount, instanceBufferOffset, 0, activeDraw->Draw.StartIndex);
|
||||
instanceBufferOffset += activeCount;
|
||||
|
||||
@@ -971,7 +1008,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL
|
||||
|
||||
// Single-draw call batch
|
||||
context->BindIB(drawCall.Geometry.IndexBuffer);
|
||||
context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, vbMax), drawCall.Geometry.VertexBuffersOffsets);
|
||||
context->BindVB(GetVB(drawCall.Geometry.VertexBuffers, vbMax), drawCall.Geometry.VertexBuffersOffsets);
|
||||
if (drawCall.InstanceCount == 0)
|
||||
{
|
||||
context->DrawIndexedInstancedIndirect(drawCall.Draw.IndirectArgsBuffer, drawCall.Draw.IndirectArgsOffset);
|
||||
@@ -994,7 +1031,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL
|
||||
Platform::MemoryCopy(vb, drawCall.Geometry.VertexBuffers, sizeof(DrawCall::Geometry.VertexBuffers));
|
||||
Platform::MemoryCopy(vbOffsets, drawCall.Geometry.VertexBuffersOffsets, sizeof(DrawCall::Geometry.VertexBuffersOffsets));
|
||||
context->BindIB(drawCall.Geometry.IndexBuffer);
|
||||
context->BindVB(ToSpan(vb, vbMax + 1), vbOffsets);
|
||||
context->BindVB(GetVB(vb, vbMax + 1), vbOffsets);
|
||||
|
||||
if (drawCall.InstanceCount == 0)
|
||||
{
|
||||
@@ -1024,7 +1061,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL
|
||||
|
||||
const DrawCall& drawCall = drawCallsData[perDraw.DrawObjectIndex];
|
||||
context->BindIB(drawCall.Geometry.IndexBuffer);
|
||||
context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, vbMax), drawCall.Geometry.VertexBuffersOffsets);
|
||||
context->BindVB(GetVB(drawCall.Geometry.VertexBuffers, vbMax), drawCall.Geometry.VertexBuffersOffsets);
|
||||
|
||||
if (drawCall.InstanceCount == 0)
|
||||
{
|
||||
@@ -1045,7 +1082,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL
|
||||
bindParams.DrawCall->Material->Bind(bindParams);
|
||||
|
||||
context->BindIB(drawCall.Geometry.IndexBuffer);
|
||||
context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, vbMax), drawCall.Geometry.VertexBuffersOffsets);
|
||||
context->BindVB(GetVB(drawCall.Geometry.VertexBuffers, vbMax), drawCall.Geometry.VertexBuffersOffsets);
|
||||
|
||||
for (int32 j = 0; j < batch.Instances.Count(); j++)
|
||||
{
|
||||
@@ -1069,7 +1106,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL
|
||||
drawCall.Material->Bind(bindParams);
|
||||
|
||||
context->BindIB(drawCall.Geometry.IndexBuffer);
|
||||
context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, vbMax), drawCall.Geometry.VertexBuffersOffsets);
|
||||
context->BindVB(GetVB(drawCall.Geometry.VertexBuffers, vbMax), drawCall.Geometry.VertexBuffersOffsets);
|
||||
|
||||
if (drawCall.InstanceCount == 0)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Memory/ArenaAllocation.h"
|
||||
#include "Engine/Core/Math/Half.h"
|
||||
#include "Engine/Graphics/PostProcessSettings.h"
|
||||
#include "Engine/Graphics/DynamicBuffer.h"
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
@@ -327,6 +326,21 @@ API_CLASS(Sealed) class FLAXENGINE_API RenderList : public ScriptingObject
|
||||
/// </summary>
|
||||
static void CleanupCache();
|
||||
|
||||
/// <summary>
|
||||
/// The rendering extension interface for custom drawing/effects linked to RenderList. Can be used during async scene drawing and further drawing/processing for more optimized rendering.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API IExtension
|
||||
{
|
||||
public:
|
||||
IExtension();
|
||||
virtual ~IExtension();
|
||||
|
||||
// Event called before collecting draw calls. Can be used for initialization.
|
||||
virtual void PreDraw(GPUContext* context, RenderContextBatch& renderContextBatch) {}
|
||||
// Event called after collecting draw calls. Can be used for cleanup or to perform additional drawing using collected draw calls data such as batched data processing.
|
||||
virtual void PostDraw(GPUContext* context, RenderContextBatch& renderContextBatch) {}
|
||||
};
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Memory storage with all draw-related data that lives during a single frame rendering time. Thread-safe to allocate memory during rendering jobs.
|
||||
@@ -460,13 +474,14 @@ public:
|
||||
/// </summary>
|
||||
DynamicTypedBuffer TempObjectBuffer;
|
||||
|
||||
typedef Function<void(RenderContextBatch& renderContextBatch, int32 contextIndex)> DelayedDraw;
|
||||
typedef Function<void(GPUContext* context, RenderContextBatch& renderContextBatch, int32 renderContextIndex)> DelayedDraw;
|
||||
void AddDelayedDraw(DelayedDraw&& func);
|
||||
void DrainDelayedDraws(RenderContextBatch& renderContextBatch, int32 contextIndex);
|
||||
void DrainDelayedDraws(GPUContext* context, RenderContextBatch& renderContextBatch, int32 renderContextIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Adds custom callback (eg. lambda) to invoke after scene draw calls are collected on a main thread (some async draw tasks might be active). Allows for safe usage of GPUContext for draw preparations or to perform GPU-driven drawing.
|
||||
/// </summary>
|
||||
/// <remarks>Can be called in async during scene rendering (thread-safe internally). Lambda is allocated by concurrent arena allocator owned by the RenderList.</remarks>
|
||||
template<typename T>
|
||||
FORCE_INLINE void AddDelayedDraw(const T& lambda)
|
||||
{
|
||||
@@ -475,9 +490,13 @@ public:
|
||||
AddDelayedDraw(MoveTemp(func));
|
||||
}
|
||||
|
||||
// IExtension implementation
|
||||
void PreDraw(GPUContext* context, RenderContextBatch& renderContextBatch);
|
||||
void PostDraw(GPUContext* context, RenderContextBatch& renderContextBatch);
|
||||
|
||||
private:
|
||||
DynamicVertexBuffer _instanceBuffer;
|
||||
Array<DelayedDraw, ConcurrentArenaAllocation> _delayedDraws;
|
||||
RenderListBuffer<DelayedDraw> _delayedDraws;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -423,6 +423,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
if (setup.UseMotionVectors)
|
||||
view.Pass |= DrawPass::MotionVectors;
|
||||
renderContextBatch.GetMainContext() = renderContext; // Sync render context in batch with the current value
|
||||
renderContext.List->PreDraw(context, renderContextBatch);
|
||||
|
||||
bool drawShadows = !isGBufferDebug && EnumHasAnyFlags(view.Flags, ViewFlags::Shadows) && ShadowsPass::Instance()->IsReady();
|
||||
switch (renderContext.View.Mode)
|
||||
@@ -461,7 +462,8 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
|
||||
// Perform custom post-scene drawing (eg. GPU dispatches used by VFX)
|
||||
for (int32 i = 0; i < renderContextBatch.Contexts.Count(); i++)
|
||||
renderContextBatch.Contexts[i].List->DrainDelayedDraws(renderContextBatch, i);
|
||||
renderContextBatch.Contexts[i].List->DrainDelayedDraws(context, renderContextBatch, i);
|
||||
renderContext.List->PostDraw(context, renderContextBatch);
|
||||
|
||||
#if USE_EDITOR
|
||||
GBufferPass::Instance()->OverrideDrawCalls(renderContext);
|
||||
|
||||
@@ -683,6 +683,8 @@ BinaryModule* BinaryModule::GetModule(const StringAnsiView& name)
|
||||
|
||||
BinaryModule::BinaryModule()
|
||||
{
|
||||
CanReload = USE_EDITOR;
|
||||
|
||||
// Register
|
||||
GetModules().Add(this);
|
||||
}
|
||||
|
||||
@@ -91,6 +91,11 @@ public:
|
||||
/// </summary>
|
||||
Dictionary<StringAnsi, int32> TypeNameToTypeIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Determinates whether module can be hot-reloaded at runtime. For example, in Editor after scripts recompilation. Some modules such as engine and class library modules are static.
|
||||
/// </summary>
|
||||
bool CanReload;
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -34,6 +34,7 @@ private:
|
||||
|
||||
int32 _isLoaded : 1;
|
||||
int32 _isLoading : 1;
|
||||
int32 _canReload : 1;
|
||||
mutable int32 _hasCachedClasses : 1;
|
||||
|
||||
mutable ClassesDictionary _classes;
|
||||
@@ -125,6 +126,14 @@ public:
|
||||
return _isLoaded != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if assembly can be hot-reloaded at runtime. For example, in Editor after scripts recompilation. Some assemblies such as engine and class library modules are static.
|
||||
/// </summary>
|
||||
FORCE_INLINE bool CanReload() const
|
||||
{
|
||||
return USE_EDITOR && _canReload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly name.
|
||||
/// </summary>
|
||||
|
||||
@@ -45,6 +45,7 @@ MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name)
|
||||
: _domain(domain)
|
||||
, _isLoaded(false)
|
||||
, _isLoading(false)
|
||||
, _canReload(true)
|
||||
, _hasCachedClasses(false)
|
||||
, _reloadCount(0)
|
||||
, _name(name)
|
||||
@@ -59,6 +60,7 @@ MAssembly::MAssembly(MDomain* domain, const StringAnsiView& name, const StringAn
|
||||
, _domain(domain)
|
||||
, _isLoaded(false)
|
||||
, _isLoading(false)
|
||||
, _canReload(true)
|
||||
, _hasCachedClasses(false)
|
||||
, _reloadCount(0)
|
||||
, _name(name)
|
||||
|
||||
@@ -874,6 +874,7 @@ bool MAssembly::LoadCorlib()
|
||||
return true;
|
||||
}
|
||||
_hasCachedClasses = false;
|
||||
_canReload = false;
|
||||
CachedAssemblyHandles.Add(_handle, this);
|
||||
|
||||
// End
|
||||
|
||||
@@ -502,6 +502,7 @@ bool Scripting::LoadBinaryModules(const String& path, const String& projectFolde
|
||||
// C#
|
||||
if (managedPath.HasChars() && !((ManagedBinaryModule*)module)->Assembly->IsLoaded())
|
||||
{
|
||||
(((ManagedBinaryModule*)module)->Assembly)->_canReload = module->CanReload;
|
||||
if (((ManagedBinaryModule*)module)->Assembly->Load(managedPath, nativePath))
|
||||
{
|
||||
LOG(Error, "Failed to load C# assembly '{0}' for binary module {1}.", managedPath, name);
|
||||
@@ -528,6 +529,7 @@ bool Scripting::Load()
|
||||
#if USE_CSHARP
|
||||
// Load C# core assembly
|
||||
ManagedBinaryModule* corlib = GetBinaryModuleCorlib();
|
||||
corlib->CanReload = false;
|
||||
if (corlib->Assembly->LoadCorlib())
|
||||
{
|
||||
LOG(Error, "Failed to load corlib C# assembly.");
|
||||
@@ -581,6 +583,8 @@ bool Scripting::Load()
|
||||
LOG(Error, "Failed to load FlaxEngine C# assembly.");
|
||||
return true;
|
||||
}
|
||||
flaxEngineModule->CanReload = false;
|
||||
flaxEngineModule->Assembly->_canReload = false;
|
||||
onEngineLoaded(flaxEngineModule->Assembly);
|
||||
|
||||
// Insert type aliases for vector types that don't exist in C++ but are just typedef (properly redirect them to actual types)
|
||||
|
||||
@@ -270,8 +270,8 @@ namespace FlaxEngine.Json
|
||||
|
||||
// Special case when saving reference to prefab object and the objects are different but the point to the same prefab object
|
||||
// In that case, skip saving reference as it's defined in prefab (will be populated via IdsMapping during deserialization)
|
||||
if (objA is SceneObject sceneA && objB is SceneObject sceneB && sceneA && sceneB && sceneA.HasPrefabLink && sceneB.HasPrefabLink)
|
||||
return sceneA.PrefabObjectID == sceneB.PrefabObjectID;
|
||||
if (objA is SceneObject sceneObjA && objB is SceneObject sceneObjB && sceneObjA && sceneObjB && sceneObjA.HasPrefabLink && sceneObjB.HasPrefabLink)
|
||||
return sceneObjA.PrefabObjectID == sceneObjB.PrefabObjectID;
|
||||
|
||||
// Comparing an Int32 and Int64 both of the same value returns false, make types the same then compare
|
||||
if (objA.GetType() != objB.GetType())
|
||||
@@ -286,7 +286,6 @@ namespace FlaxEngine.Json
|
||||
type == typeof(Int32) ||
|
||||
type == typeof(UInt32) ||
|
||||
type == typeof(Int64) ||
|
||||
type == typeof(SByte) ||
|
||||
type == typeof(UInt64);
|
||||
}
|
||||
if (IsInteger(objA) && IsInteger(objB))
|
||||
@@ -301,6 +300,12 @@ namespace FlaxEngine.Json
|
||||
{
|
||||
if (aList.Count != bList.Count)
|
||||
return false;
|
||||
for (int i = 0; i < aList.Count; i++)
|
||||
{
|
||||
if (!ValueEquals(aList[i], bList[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (objA is IEnumerable aEnumerable && objB is IEnumerable bEnumerable)
|
||||
{
|
||||
@@ -316,8 +321,30 @@ namespace FlaxEngine.Json
|
||||
return !bEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
if (objA is ICustomValueEquals customValueEquals && objA.GetType() == objB.GetType())
|
||||
// Custom comparer
|
||||
if (objA is ICustomValueEquals customValueEquals)
|
||||
return customValueEquals.ValueEquals(objB);
|
||||
|
||||
// If type contains SceneObject references then it needs to use custom comparision that handles prefab links (see SceneObjectEquals)
|
||||
var typeA = objA.GetType();
|
||||
if (typeA.IsValueType && !typeA.IsEnum && !typeA.IsPrimitive)
|
||||
{
|
||||
var contract = Settings.ContractResolver.ResolveContract(typeA);
|
||||
if (contract is JsonObjectContract objContract)
|
||||
{
|
||||
foreach (var property in objContract.Properties)
|
||||
{
|
||||
var valueProvider = property.ValueProvider;
|
||||
var propA = valueProvider.GetValue(objA);
|
||||
var propB = valueProvider.GetValue(objB);
|
||||
if (!ValueEquals(propA, propB))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Generic fallback
|
||||
return objA.Equals(objB);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -78,7 +78,10 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian
|
||||
v.Type = VariantType::Null;
|
||||
const auto mTypeName = SERIALIZE_FIND_MEMBER(stream, "TypeName");
|
||||
if (mTypeName != stream.MemberEnd() && mTypeName->value.IsString())
|
||||
{
|
||||
v.SetTypeName(StringAnsiView(mTypeName->value.GetStringAnsiView()));
|
||||
v.Inline();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -415,7 +415,7 @@ namespace Serialization
|
||||
|
||||
inline bool ShouldSerialize(const ISerializable& v, const void* otherObj)
|
||||
{
|
||||
return true;
|
||||
return !otherObj || v.ShouldSerialize(otherObj);
|
||||
}
|
||||
inline void Serialize(ISerializable::SerializeStream& stream, const ISerializable& v, const void* otherObj)
|
||||
{
|
||||
@@ -431,7 +431,7 @@ namespace Serialization
|
||||
template<typename T>
|
||||
inline typename TEnableIf<TIsBaseOf<ISerializable, T>::Value, bool>::Type ShouldSerialize(const ISerializable& v, const void* otherObj)
|
||||
{
|
||||
return true;
|
||||
return !otherObj || v.ShouldSerialize(otherObj);
|
||||
}
|
||||
template<typename T>
|
||||
inline typename TEnableIf<TIsBaseOf<ISerializable, T>::Value>::Type Serialize(ISerializable::SerializeStream& stream, const ISerializable& v, const void* otherObj)
|
||||
|
||||
@@ -255,6 +255,7 @@ void ReadStream::Read(VariantType& data)
|
||||
ptr++;
|
||||
}
|
||||
*ptr = 0;
|
||||
data.Inline();
|
||||
}
|
||||
else if (typeNameLength > 0)
|
||||
{
|
||||
|
||||
318
Source/Engine/Threading/ConcurrentDictionary.h
Normal file
318
Source/Engine/Threading/ConcurrentDictionary.h
Normal file
@@ -0,0 +1,318 @@
|
||||
// Copyright (c) Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Platform/CriticalSection.h"
|
||||
|
||||
/// <summary>
|
||||
/// Template for unordered dictionary with mapped key with value pairs that supports asynchronous data reading and writing.
|
||||
/// Implemented via reader-writer lock pattern, so multiple threads can read data at the same time, but only one thread can write data and it blocks all other threads (including readers) until the write operation is finished.
|
||||
/// Optimized for frequent reads (no lock operation).
|
||||
/// </summary>
|
||||
/// <typeparam name="KeyType">The type of the keys in the dictionary.</typeparam>
|
||||
/// <typeparam name="ValueType">The type of the values in the dictionary.</typeparam>
|
||||
/// <typeparam name="AllocationType">The type of memory allocator.</typeparam>
|
||||
template<typename KeyType, typename ValueType, typename AllocationType = HeapAllocation>
|
||||
class ConcurrentDictionary : Dictionary<KeyType, ValueType, AllocationType>
|
||||
{
|
||||
friend ConcurrentDictionary;
|
||||
public:
|
||||
typedef Dictionary<KeyType, ValueType, AllocationType> Base;
|
||||
typedef DictionaryBucket<KeyType, ValueType, AllocationType> Bucket;
|
||||
using AllocationData = typename AllocationType::template Data<Bucket>;
|
||||
using AllocationTag = typename AllocationType::Tag;
|
||||
|
||||
private:
|
||||
mutable volatile int64 _threadsReading = 0;
|
||||
volatile int64 _threadsWriting = 0;
|
||||
CriticalSection _locker;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes an empty <see cref="ConcurrentDictionary"/> without reserving any space.
|
||||
/// </summary>
|
||||
ConcurrentDictionary()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an empty <see cref="ConcurrentDictionary"/> without reserving any space.
|
||||
/// </summary>
|
||||
/// <param name="tag">The custom allocation tag.</param>
|
||||
ConcurrentDictionary(AllocationTag tag)
|
||||
: Base(tag)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="ConcurrentDictionary"/> class.
|
||||
/// </summary>
|
||||
~ConcurrentDictionary()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the amount of the elements in the collection.
|
||||
/// </summary>
|
||||
int32 Count() const
|
||||
{
|
||||
Reader reader(this);
|
||||
return Base::_elementsCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of the elements that can be contained by the collection.
|
||||
/// </summary>
|
||||
int32 Capacity() const
|
||||
{
|
||||
Reader reader(this);
|
||||
return Base::_size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get element with given key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the element.</param>
|
||||
/// <param name="result">The result value.</param>
|
||||
/// <returns>True if element of given key has been found, otherwise false.</returns>
|
||||
template<typename KeyComparableType>
|
||||
bool TryGet(const KeyComparableType& key, ValueType& result) const
|
||||
{
|
||||
Reader reader(this);
|
||||
typename Base::FindPositionResult pos;
|
||||
Base::FindPosition(key, pos);
|
||||
if (pos.ObjectIndex != -1)
|
||||
result = Base::_allocation.Get()[pos.ObjectIndex].Value;
|
||||
return pos.ObjectIndex != -1;
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Adds a pair of key and value to the collection.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>True if added element, otherwise false if it already exists (or other thread added it).</returns>
|
||||
template<typename KeyComparableType>
|
||||
bool Add(const KeyComparableType& key, const ValueType& value)
|
||||
{
|
||||
Writer writer(this);
|
||||
Bucket* bucket = Base::OnAdd(key, false, true);
|
||||
if (bucket)
|
||||
bucket->Occupy(key, value);
|
||||
return bucket != nullptr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes element with a specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The element key to remove.</param>
|
||||
/// <returns>True if item was removed from collection, otherwise false.</returns>
|
||||
template<typename KeyComparableType>
|
||||
bool Remove(const KeyComparableType& key)
|
||||
{
|
||||
Writer writer(this);
|
||||
return Base::Remove(key);
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Removes all elements from the collection.
|
||||
/// </summary>
|
||||
void Clear()
|
||||
{
|
||||
Writer writer(this);
|
||||
Base::Clear();
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The read-only dictionary collection iterator.
|
||||
/// </summary>
|
||||
struct ConstIterator : Base::IteratorBase
|
||||
{
|
||||
friend ConcurrentDictionary;
|
||||
public:
|
||||
ConstIterator(const ConcurrentDictionary* collection, const int32 index)
|
||||
: Base::IteratorBase(collection, index)
|
||||
{
|
||||
if (collection)
|
||||
collection->BeginRead();
|
||||
}
|
||||
|
||||
ConstIterator(const ConstIterator& i)
|
||||
: Base::IteratorBase(i._collection, i._index)
|
||||
{
|
||||
if (i.collection)
|
||||
i.collection->BeginRead();
|
||||
}
|
||||
|
||||
ConstIterator(ConstIterator&& i) noexcept
|
||||
: Base::IteratorBase(i._collection, i._index)
|
||||
{
|
||||
i._collection = nullptr;
|
||||
}
|
||||
|
||||
~ConstIterator()
|
||||
{
|
||||
if (this->_collection)
|
||||
((ConcurrentDictionary*)this->_collection)->EndRead();
|
||||
}
|
||||
|
||||
public:
|
||||
FORCE_INLINE bool operator!() const
|
||||
{
|
||||
return !(bool)*this;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator==(const ConstIterator& v) const
|
||||
{
|
||||
return this->_index == v._index && this->_collection == v._collection;
|
||||
}
|
||||
|
||||
FORCE_INLINE bool operator!=(const ConstIterator& v) const
|
||||
{
|
||||
return this->_index != v._index || this->_collection != v._collection;
|
||||
}
|
||||
|
||||
ConstIterator& operator=(const ConstIterator& v)
|
||||
{
|
||||
this->_collection = v._collection;
|
||||
this->_index = v._index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ConstIterator& operator=(ConstIterator&& v) noexcept
|
||||
{
|
||||
this->_collection = v._collection;
|
||||
this->_index = v._index;
|
||||
v._collection = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ConstIterator& operator++()
|
||||
{
|
||||
this->Next();
|
||||
return *this;
|
||||
}
|
||||
|
||||
ConstIterator operator++(int) const
|
||||
{
|
||||
ConstIterator i = *this;
|
||||
i.Next();
|
||||
return i;
|
||||
}
|
||||
|
||||
ConstIterator& operator--()
|
||||
{
|
||||
this->Prev();
|
||||
return *this;
|
||||
}
|
||||
|
||||
ConstIterator operator--(int) const
|
||||
{
|
||||
ConstIterator i = *this;
|
||||
i.Prev();
|
||||
return i;
|
||||
}
|
||||
};
|
||||
|
||||
ConstIterator begin() const
|
||||
{
|
||||
ConstIterator i(this, -1);
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
FORCE_INLINE ConstIterator end() const
|
||||
{
|
||||
return ConstIterator(this, Base::_size);
|
||||
}
|
||||
|
||||
private:
|
||||
void BeginWrite()
|
||||
{
|
||||
Platform::InterlockedIncrement(&_threadsWriting);
|
||||
|
||||
// Wait for all reads to end
|
||||
RETRY:
|
||||
while (Platform::AtomicRead(&_threadsReading))
|
||||
Platform::Yield();
|
||||
|
||||
// Thread-safe writing
|
||||
_locker.Lock();
|
||||
if (Platform::AtomicRead(&_threadsReading))
|
||||
{
|
||||
// Other reader entered during mutex locking so give them a chance to transition into active-waiting
|
||||
_locker.Unlock();
|
||||
goto RETRY;
|
||||
}
|
||||
}
|
||||
|
||||
void EndWrite()
|
||||
{
|
||||
_locker.Unlock();
|
||||
Platform::InterlockedDecrement(&_threadsWriting);
|
||||
}
|
||||
|
||||
void BeginRead() const
|
||||
{
|
||||
RETRY:
|
||||
Platform::InterlockedIncrement(&_threadsReading);
|
||||
|
||||
// Check if any thread is writing (or is about to write)
|
||||
if (Platform::AtomicRead(&_threadsWriting) != 0)
|
||||
{
|
||||
// Wait for all writes to end
|
||||
Platform::InterlockedDecrement(&_threadsReading);
|
||||
while (Platform::AtomicRead(&_threadsWriting))
|
||||
Platform::Yield();
|
||||
|
||||
// Try again
|
||||
goto RETRY;
|
||||
}
|
||||
}
|
||||
|
||||
void EndRead() const
|
||||
{
|
||||
Platform::InterlockedDecrement(&_threadsReading);
|
||||
}
|
||||
|
||||
private:
|
||||
// Utility for methods that read-write state.
|
||||
struct Writer
|
||||
{
|
||||
ConcurrentDictionary* _collection;
|
||||
|
||||
Writer(ConcurrentDictionary* collection)
|
||||
: _collection(collection)
|
||||
{
|
||||
_collection->BeginWrite();
|
||||
}
|
||||
|
||||
~Writer()
|
||||
{
|
||||
_collection->EndWrite();
|
||||
}
|
||||
};
|
||||
|
||||
// Utility for methods that read-only state.
|
||||
struct Reader
|
||||
{
|
||||
const ConcurrentDictionary* _collection;
|
||||
|
||||
Reader(const ConcurrentDictionary* collection)
|
||||
: _collection(collection)
|
||||
{
|
||||
_collection->BeginRead();
|
||||
}
|
||||
|
||||
~Reader()
|
||||
{
|
||||
_collection->EndRead();
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "Engine/Core/Types/Span.h"
|
||||
#include "Engine/Core/Types/Pair.h"
|
||||
#include "Engine/Core/Memory/SimpleHeapAllocation.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Core/Collections/RingBuffer.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
@@ -22,14 +21,6 @@
|
||||
|
||||
#if JOB_SYSTEM_ENABLED
|
||||
|
||||
// Local allocator for job system memory that uses internal pooling and assumes that JobsLocker is taken (write access owned by the calling thread).
|
||||
class JobSystemAllocation : public SimpleHeapAllocation<JobSystemAllocation>
|
||||
{
|
||||
public:
|
||||
static void* Allocate(uintptr size);
|
||||
static void Free(void* ptr, uintptr size);
|
||||
};
|
||||
|
||||
class JobSystemService : public EngineService
|
||||
{
|
||||
public:
|
||||
@@ -43,30 +34,25 @@ public:
|
||||
void Dispose() override;
|
||||
};
|
||||
|
||||
struct JobData
|
||||
// Holds a single job dispatch data
|
||||
struct alignas(int64) JobContext
|
||||
{
|
||||
int32 Index;
|
||||
int64 JobKey;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<JobData>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
|
||||
struct JobContext
|
||||
{
|
||||
volatile int64 JobsLeft;
|
||||
int32 DependenciesLeft;
|
||||
// The next index of the job to process updated when picking a job by the thread.
|
||||
volatile int64 JobIndex = 0;
|
||||
// The number of jobs left to process updated after job completion by the thread.
|
||||
volatile int64 JobsLeft = 0;
|
||||
// The unique label of this job used to identify it. Set to -1 when job is done.
|
||||
volatile int64 JobLabel = 0;
|
||||
// Utility atomic counter used to indicate that any job is waiting for this one to finish. Then Dependants can be accessed within thread-safe JobsLocker.
|
||||
volatile int64 DependantsCount = 0;
|
||||
// The number of dependency jobs left to be finished before starting this job.
|
||||
volatile int64 DependenciesLeft = 0;
|
||||
// The total number of jobs to process (in this context).
|
||||
int32 JobsCount = 0;
|
||||
// The job function to execute.
|
||||
Function<void(int32)> Job;
|
||||
Array<int64, JobSystemAllocation> Dependants;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<JobContext>
|
||||
{
|
||||
enum { Value = false };
|
||||
// List of dependant jobs to signal when this job is done.
|
||||
Array<int64> Dependants;
|
||||
};
|
||||
|
||||
class JobSystemThread : public IRunnable
|
||||
@@ -92,50 +78,36 @@ public:
|
||||
namespace
|
||||
{
|
||||
JobSystemService JobSystemInstance;
|
||||
Array<Pair<void*, uintptr>> MemPool;
|
||||
Thread* Threads[PLATFORM_THREADS_LIMIT / 2] = {};
|
||||
int32 ThreadsCount = 0;
|
||||
bool JobStartingOnDispatch = true;
|
||||
volatile int64 ExitFlag = 0;
|
||||
volatile int64 JobLabel = 0;
|
||||
Dictionary<int64, JobContext, JobSystemAllocation> JobContexts;
|
||||
volatile int64 JobEndLabel = 0;
|
||||
volatile int64 JobStartLabel = 0;
|
||||
volatile int64 JobContextsCount = 0;
|
||||
uint32 JobContextsSize = 0;
|
||||
uint32 JobContextsMask = 0;
|
||||
JobContext* JobContexts = nullptr;
|
||||
ConditionVariable JobsSignal;
|
||||
CriticalSection JobsMutex;
|
||||
ConditionVariable WaitSignal;
|
||||
CriticalSection WaitMutex;
|
||||
CriticalSection JobsLocker;
|
||||
RingBuffer<JobData> Jobs;
|
||||
}
|
||||
|
||||
void* JobSystemAllocation::Allocate(uintptr size)
|
||||
{
|
||||
void* result = nullptr;
|
||||
for (int32 i = 0; i < MemPool.Count(); i++)
|
||||
{
|
||||
if (MemPool.Get()[i].Second == size)
|
||||
{
|
||||
result = MemPool.Get()[i].First;
|
||||
MemPool.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!result)
|
||||
{
|
||||
PROFILE_MEM(EngineThreading);
|
||||
result = Platform::Allocate(size, 16);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void JobSystemAllocation::Free(void* ptr, uintptr size)
|
||||
{
|
||||
PROFILE_MEM(EngineThreading);
|
||||
MemPool.Add({ ptr, size });
|
||||
#define GET_CONTEXT_INDEX(label) (uint32)((label) & (int64)JobContextsMask)
|
||||
}
|
||||
|
||||
bool JobSystemService::Init()
|
||||
{
|
||||
PROFILE_MEM(EngineThreading);
|
||||
|
||||
// Initialize job context storage (fixed-size ring buffer for active jobs tracking)
|
||||
JobContextsSize = 256;
|
||||
JobContextsMask = JobContextsSize - 1;
|
||||
JobContexts = (JobContext*)Platform::Allocate(JobContextsSize * sizeof(JobContext), alignof(JobContext));
|
||||
Memory::ConstructItems(JobContexts, (int32)JobContextsSize);
|
||||
|
||||
// Spawn threads
|
||||
ThreadsCount = Math::Min<int32>(Platform::GetCPUInfo().LogicalProcessorCount, ARRAY_COUNT(Threads));
|
||||
for (int32 i = 0; i < ThreadsCount; i++)
|
||||
{
|
||||
@@ -146,6 +118,7 @@ bool JobSystemService::Init()
|
||||
return true;
|
||||
Threads[i] = thread;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -171,35 +144,67 @@ void JobSystemService::Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
JobContexts.SetCapacity(0);
|
||||
Jobs.Release();
|
||||
for (auto& e : MemPool)
|
||||
Platform::Free(e.First);
|
||||
MemPool.Clear();
|
||||
Memory::DestructItems(JobContexts, (int32)JobContextsSize);
|
||||
Platform::Free(JobContexts);
|
||||
JobContexts = nullptr;
|
||||
}
|
||||
|
||||
int32 JobSystemThread::Run()
|
||||
{
|
||||
// Pin thread to the physical core
|
||||
Platform::SetThreadAffinityMask(1ull << Index);
|
||||
|
||||
JobData data;
|
||||
Function<void(int32)> job;
|
||||
bool attachCSharpThread = true;
|
||||
MONO_THREAD_INFO_TYPE* monoThreadInfo = nullptr;
|
||||
while (Platform::AtomicRead(&ExitFlag) == 0)
|
||||
{
|
||||
// Try to get a job
|
||||
JobsLocker.Lock();
|
||||
if (Jobs.Count() != 0)
|
||||
int32 jobIndex;
|
||||
JobContext* jobContext = nullptr;
|
||||
{
|
||||
data = Jobs.PeekFront();
|
||||
Jobs.PopFront();
|
||||
const JobContext& context = ((const Dictionary<int64, JobContext>&)JobContexts).At(data.JobKey);
|
||||
job = context.Job;
|
||||
}
|
||||
JobsLocker.Unlock();
|
||||
int64 jobOffset = 0;
|
||||
RETRY:
|
||||
int64 jobStartLabel = Platform::AtomicRead(&JobStartLabel) + jobOffset;
|
||||
int64 jobEndLabel = Platform::AtomicRead(&JobEndLabel);
|
||||
if (jobStartLabel <= jobEndLabel && jobEndLabel > 0)
|
||||
{
|
||||
jobContext = &JobContexts[GET_CONTEXT_INDEX(jobStartLabel)];
|
||||
if (Platform::AtomicRead(&jobContext->DependenciesLeft) > 0)
|
||||
{
|
||||
// This job still waits for dependency so skip it for now and try the next one
|
||||
jobOffset++;
|
||||
jobContext = nullptr;
|
||||
goto RETRY;
|
||||
}
|
||||
|
||||
if (job.IsBinded())
|
||||
// Move forward with index for a job
|
||||
jobIndex = (int32)(Platform::InterlockedIncrement(&jobContext->JobIndex) - 1);
|
||||
if (jobIndex < jobContext->JobsCount)
|
||||
{
|
||||
// Index is valid
|
||||
}
|
||||
else if (jobStartLabel < jobEndLabel && jobOffset == 0)
|
||||
{
|
||||
// No more jobs inside this context, move to the next one
|
||||
Platform::InterlockedCompareExchange(&JobStartLabel, jobStartLabel + 1, jobStartLabel);
|
||||
jobContext = nullptr;
|
||||
goto RETRY;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No more jobs
|
||||
jobContext = nullptr;
|
||||
if (jobStartLabel < jobEndLabel)
|
||||
{
|
||||
// Try with a different one before going to sleep
|
||||
jobOffset++;
|
||||
goto RETRY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (jobContext)
|
||||
{
|
||||
#if USE_CSHARP
|
||||
// Ensure to have C# thread attached to this thead (late init due to MCore being initialized after Job System)
|
||||
@@ -212,37 +217,39 @@ int32 JobSystemThread::Run()
|
||||
#endif
|
||||
|
||||
// Run job
|
||||
job(data.Index);
|
||||
jobContext->Job(jobIndex);
|
||||
|
||||
// Move forward with the job queue
|
||||
bool notifyWaiting = false;
|
||||
JobsLocker.Lock();
|
||||
JobContext& context = JobContexts.At(data.JobKey);
|
||||
if (Platform::InterlockedDecrement(&context.JobsLeft) <= 0)
|
||||
if (Platform::InterlockedDecrement(&jobContext->JobsLeft) <= 0)
|
||||
{
|
||||
// Update any dependant jobs
|
||||
for (int64 dependant : context.Dependants)
|
||||
// Mark job as done before processing dependants
|
||||
Platform::AtomicStore(&jobContext->JobLabel, -1);
|
||||
|
||||
// Check if any other job waits on this one
|
||||
if (Platform::AtomicRead(&jobContext->DependantsCount) != 0)
|
||||
{
|
||||
JobContext& dependantContext = JobContexts.At(dependant);
|
||||
if (--dependantContext.DependenciesLeft <= 0)
|
||||
// Update dependant jobs
|
||||
JobsLocker.Lock();
|
||||
for (int64 dependant : jobContext->Dependants)
|
||||
{
|
||||
// Dispatch dependency when it's ready
|
||||
JobData dependantData;
|
||||
dependantData.JobKey = dependant;
|
||||
for (dependantData.Index = 0; dependantData.Index < dependantContext.JobsLeft; dependantData.Index++)
|
||||
Jobs.PushBack(dependantData);
|
||||
JobContext& dependantContext = JobContexts[GET_CONTEXT_INDEX(dependant)];
|
||||
if (dependantContext.JobLabel == dependant)
|
||||
Platform::InterlockedDecrement(&dependantContext.DependenciesLeft);
|
||||
}
|
||||
JobsLocker.Unlock();
|
||||
}
|
||||
|
||||
// Remove completed context
|
||||
JobContexts.Remove(data.JobKey);
|
||||
notifyWaiting = true;
|
||||
}
|
||||
JobsLocker.Unlock();
|
||||
if (notifyWaiting)
|
||||
WaitSignal.NotifyAll();
|
||||
// Cleanup completed context
|
||||
jobContext->Job.Unbind();
|
||||
jobContext->Dependants.Clear();
|
||||
Platform::AtomicStore(&jobContext->DependantsCount, 0);
|
||||
Platform::AtomicStore(&jobContext->DependenciesLeft, -999); // Mark to indicate deleted context
|
||||
Platform::AtomicStore(&jobContext->JobLabel, -1);
|
||||
Platform::InterlockedDecrement(&JobContextsCount);
|
||||
|
||||
job.Unbind();
|
||||
// Wakeup any thread waiting for the jobs to complete
|
||||
WaitSignal.NotifyAll();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -266,8 +273,8 @@ void JobSystem::Execute(const Function<void(int32)>& job, int32 jobCount)
|
||||
if (jobCount > 1)
|
||||
{
|
||||
// Async
|
||||
const int64 jobWaitHandle = Dispatch(job, jobCount);
|
||||
Wait(jobWaitHandle);
|
||||
const int64 label = Dispatch(job, jobCount);
|
||||
Wait(label);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
@@ -284,21 +291,31 @@ int64 JobSystem::Dispatch(const Function<void(int32)>& job, int32 jobCount)
|
||||
return 0;
|
||||
PROFILE_CPU();
|
||||
#if JOB_SYSTEM_ENABLED
|
||||
const auto label = Platform::InterlockedAdd(&JobLabel, (int64)jobCount) + jobCount;
|
||||
while (Platform::InterlockedIncrement(&JobContextsCount) >= JobContextsSize)
|
||||
{
|
||||
// Too many jobs in flight, wait for some to complete to free up contexts
|
||||
PROFILE_CPU_NAMED("JOB SYSTEM OVERFLOW");
|
||||
ZoneColor(TracyWaitZoneColor);
|
||||
Platform::InterlockedDecrement(&JobContextsCount);
|
||||
Platform::Sleep(1);
|
||||
}
|
||||
|
||||
JobData data;
|
||||
data.JobKey = label;
|
||||
// Get a new label
|
||||
const int64 label = Platform::InterlockedIncrement(&JobLabel);
|
||||
|
||||
JobContext context;
|
||||
// Build job
|
||||
JobContext& context = JobContexts[GET_CONTEXT_INDEX(label)];
|
||||
context.Job = job;
|
||||
context.JobIndex = 0;
|
||||
context.JobsLeft = jobCount;
|
||||
context.JobLabel = label;
|
||||
context.DependantsCount = 0;
|
||||
context.DependenciesLeft = 0;
|
||||
context.JobsCount = jobCount;
|
||||
context.Dependants.Clear();
|
||||
|
||||
JobsLocker.Lock();
|
||||
JobContexts.Add(label, MoveTemp(context));
|
||||
for (data.Index = 0; data.Index < jobCount; data.Index++)
|
||||
Jobs.PushBack(data);
|
||||
JobsLocker.Unlock();
|
||||
// Move the job queue forward
|
||||
Platform::InterlockedIncrement(&JobEndLabel);
|
||||
|
||||
if (JobStartingOnDispatch)
|
||||
{
|
||||
@@ -321,34 +338,47 @@ int64 JobSystem::Dispatch(const Function<void(int32)>& job, Span<int64> dependen
|
||||
if (jobCount <= 0)
|
||||
return 0;
|
||||
PROFILE_CPU();
|
||||
PROFILE_MEM(EngineThreading);
|
||||
#if JOB_SYSTEM_ENABLED
|
||||
const auto label = Platform::InterlockedAdd(&JobLabel, (int64)jobCount) + jobCount;
|
||||
while (Platform::InterlockedIncrement(&JobContextsCount) >= JobContextsSize)
|
||||
{
|
||||
// Too many jobs in flight, wait for some to complete to free up contexts
|
||||
PROFILE_CPU_NAMED("JOB SYSTEM OVERFLOW");
|
||||
ZoneColor(TracyWaitZoneColor);
|
||||
Platform::InterlockedDecrement(&JobContextsCount);
|
||||
Platform::Sleep(1);
|
||||
}
|
||||
|
||||
JobData data;
|
||||
data.JobKey = label;
|
||||
// Get a new label
|
||||
const int64 label = Platform::InterlockedIncrement(&JobLabel);
|
||||
|
||||
JobContext context;
|
||||
// Build job
|
||||
JobContext& context = JobContexts[GET_CONTEXT_INDEX(label)];
|
||||
context.Job = job;
|
||||
context.JobIndex = 0;
|
||||
context.JobsLeft = jobCount;
|
||||
context.JobLabel = label;
|
||||
context.DependantsCount = 0;
|
||||
context.DependenciesLeft = 0;
|
||||
|
||||
JobsLocker.Lock();
|
||||
for (int64 dependency : dependencies)
|
||||
context.JobsCount = jobCount;
|
||||
context.Dependants.Clear();
|
||||
{
|
||||
if (JobContext* dependencyContext = JobContexts.TryGet(dependency))
|
||||
JobsLocker.Lock();
|
||||
for (int64 dependency : dependencies)
|
||||
{
|
||||
context.DependenciesLeft++;
|
||||
dependencyContext->Dependants.Add(label);
|
||||
JobContext& dependencyContext = JobContexts[GET_CONTEXT_INDEX(dependency)];
|
||||
if (Platform::AtomicRead(&dependencyContext.JobLabel) == dependency)
|
||||
{
|
||||
Platform::InterlockedIncrement(&dependencyContext.DependantsCount);
|
||||
dependencyContext.Dependants.Add(label);
|
||||
context.DependenciesLeft++;
|
||||
}
|
||||
}
|
||||
JobsLocker.Unlock();
|
||||
}
|
||||
JobContexts.Add(label, MoveTemp(context));
|
||||
if (context.DependenciesLeft == 0)
|
||||
{
|
||||
// No dependencies left to complete so dispatch now
|
||||
for (data.Index = 0; data.Index < jobCount; data.Index++)
|
||||
Jobs.PushBack(data);
|
||||
}
|
||||
JobsLocker.Unlock();
|
||||
|
||||
// Move the job queue forward
|
||||
Platform::InterlockedIncrement(&JobEndLabel);
|
||||
|
||||
if (context.DependenciesLeft == 0 && JobStartingOnDispatch)
|
||||
{
|
||||
@@ -369,19 +399,17 @@ int64 JobSystem::Dispatch(const Function<void(int32)>& job, Span<int64> dependen
|
||||
void JobSystem::Wait()
|
||||
{
|
||||
#if JOB_SYSTEM_ENABLED
|
||||
JobsLocker.Lock();
|
||||
int32 numJobs = JobContexts.Count();
|
||||
JobsLocker.Unlock();
|
||||
PROFILE_CPU();
|
||||
ZoneColor(TracyWaitZoneColor);
|
||||
|
||||
int64 numJobs = Platform::AtomicRead(&JobContextsCount);
|
||||
while (numJobs > 0)
|
||||
{
|
||||
WaitMutex.Lock();
|
||||
WaitSignal.Wait(WaitMutex, 1);
|
||||
WaitMutex.Unlock();
|
||||
|
||||
JobsLocker.Lock();
|
||||
numJobs = JobContexts.Count();
|
||||
JobsLocker.Unlock();
|
||||
numJobs = Platform::AtomicRead(&JobContextsCount);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -394,12 +422,11 @@ void JobSystem::Wait(int64 label)
|
||||
|
||||
while (Platform::AtomicRead(&ExitFlag) == 0)
|
||||
{
|
||||
JobsLocker.Lock();
|
||||
const JobContext* context = JobContexts.TryGet(label);
|
||||
JobsLocker.Unlock();
|
||||
const JobContext& context = JobContexts[GET_CONTEXT_INDEX(label)];
|
||||
const bool finished = Platform::AtomicRead(&context.JobLabel) != label || Platform::AtomicRead(&context.JobsLeft) <= 0;
|
||||
|
||||
// Skip if context has been already executed (last job removes it)
|
||||
if (!context)
|
||||
if (finished)
|
||||
break;
|
||||
|
||||
// Wait on signal until input label is not yet done
|
||||
@@ -417,15 +444,10 @@ void JobSystem::SetJobStartingOnDispatch(bool value)
|
||||
{
|
||||
#if JOB_SYSTEM_ENABLED
|
||||
JobStartingOnDispatch = value;
|
||||
if (value)
|
||||
if (value && (Platform::AtomicRead(&JobEndLabel) - Platform::AtomicRead(&JobStartLabel)) > 0)
|
||||
{
|
||||
JobsLocker.Lock();
|
||||
const int32 count = Jobs.Count();
|
||||
JobsLocker.Unlock();
|
||||
if (count == 1)
|
||||
JobsSignal.NotifyOne();
|
||||
else if (count != 0)
|
||||
JobsSignal.NotifyAll();
|
||||
// Wake up threads to start processing jobs that may be already in the queue
|
||||
JobsSignal.NotifyAll();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -148,9 +148,8 @@ Task* Task::StartNew(Function<bool()>::Signature& action, Object* target)
|
||||
|
||||
void Task::Execute()
|
||||
{
|
||||
if (IsCanceled())
|
||||
if (!IsQueued())
|
||||
return;
|
||||
ASSERT(IsQueued());
|
||||
SetState(TaskState::Running);
|
||||
|
||||
// Perform an operation
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user