Merge branch 'FlaxEngine:master' into terrainscripting

This commit is contained in:
Ilya Fedorov
2023-12-02 14:18:18 +03:00
committed by GitHub
437 changed files with 11025 additions and 4949 deletions
+13 -3
View File
@@ -114,14 +114,19 @@ void Behavior::UpdateAsync()
void Behavior::StartLogic()
{
if (_result == BehaviorUpdateResult::Running)
return;
PROFILE_CPU();
// Ensure to have tree loaded on begin play
// Ensure to have tree loaded on play
CHECK(Tree && !Tree->WaitForLoaded());
BehaviorTree* tree = Tree.Get();
CHECK(tree->Graph.Root);
// Setup state
_result = BehaviorUpdateResult::Running;
_accumulatedTime = 0.0f;
_totalTime = 0;
// Init knowledge
_knowledge.InitMemory(tree);
@@ -135,6 +140,7 @@ void Behavior::StopLogic(BehaviorUpdateResult result)
_accumulatedTime = 0.0f;
_totalTime = 0;
_result = result;
_knowledge.FreeMemory();
}
void Behavior::ResetLogic()
@@ -170,7 +176,11 @@ void Behavior::OnDisable()
bool Behavior::GetNodeDebugRelevancy(const BehaviorTreeNode* node, const Behavior* behavior)
{
return node && behavior && node->_executionIndex != -1 && behavior->_knowledge.RelevantNodes.Get(node->_executionIndex);
return node &&
behavior &&
node->_executionIndex >= 0 &&
node->_executionIndex < behavior->_knowledge.RelevantNodes.Count() &&
behavior->_knowledge.RelevantNodes.Get(node->_executionIndex);
}
String Behavior::GetNodeDebugInfo(const BehaviorTreeNode* node, Behavior* behavior)
@@ -179,7 +189,7 @@ String Behavior::GetNodeDebugInfo(const BehaviorTreeNode* node, Behavior* behavi
return String::Empty;
BehaviorUpdateContext context;
Platform::MemoryClear(&context, sizeof(context));
if (behavior && node->_executionIndex != -1 && behavior->_knowledge.RelevantNodes.Get(node->_executionIndex))
if (GetNodeDebugRelevancy(node, behavior))
{
// Pass behavior and knowledge data only for relevant nodes to properly access it
context.Behavior = behavior;
+7 -1
View File
@@ -83,7 +83,7 @@ bool AccessVariant(Variant& instance, const StringAnsiView& member, Variant& val
}
}
#endif
else
else if (typeName.HasChars())
{
LOG(Warning, "Missing scripting type \'{0}\'", String(typeName));
}
@@ -150,7 +150,13 @@ void BehaviorKnowledge::InitMemory(BehaviorTree* tree)
RelevantNodes.Resize(tree->Graph.NodesCount, false);
RelevantNodes.SetAll(false);
if (!Memory && tree->Graph.NodesStatesSize)
{
Memory = Allocator::Allocate(tree->Graph.NodesStatesSize);
#if !BUILD_RELEASE
// Clear memory to make it easier to spot missing data issues (eg. zero GCHandle in C# BT node due to missing state init)
Platform::MemoryClear(Memory, tree->Graph.NodesStatesSize);
#endif
}
}
void BehaviorKnowledge::FreeMemory()
@@ -202,7 +202,7 @@ namespace FlaxEngine
public T Get(BehaviorKnowledge knowledge)
{
if (knowledge != null && knowledge.Get(Path, out var value))
return (T)value;
return Utilities.VariantUtils.Cast<T>(value);
return default;
}
@@ -218,7 +218,7 @@ namespace FlaxEngine
object tmp = null;
bool result = knowledge != null && knowledge.Get(Path, out tmp);
if (result)
value = (T)tmp;
value = Utilities.VariantUtils.Cast<T>(tmp);
return result;
}
+11 -5
View File
@@ -95,12 +95,16 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T GetState<T>(IntPtr memory) where T : struct
{
var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer();
var handle = GCHandle.FromIntPtr(Unsafe.Read<IntPtr>(ptr));
var ptr = Unsafe.Read<IntPtr>(IntPtr.Add(memory, _memoryOffset).ToPointer());
#if !BUILD_RELEASE
if (ptr == IntPtr.Zero)
throw new Exception($"Missing state '{typeof(T).FullName}' for node '{GetType().FullName}'");
#endif
var handle = GCHandle.FromIntPtr(ptr);
var state = handle.Target;
#if !BUILD_RELEASE
if (state == null)
throw new NullReferenceException();
throw new Exception($"Missing state '{typeof(T).FullName}' for node '{GetType().FullName}'");
#endif
return ref Unsafe.Unbox<T>(state);
}
@@ -111,8 +115,10 @@ namespace FlaxEngine
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FreeState(IntPtr memory)
{
var ptr = IntPtr.Add(memory, _memoryOffset).ToPointer();
var handle = GCHandle.FromIntPtr(Unsafe.Read<IntPtr>(ptr));
var ptr = Unsafe.Read<IntPtr>(IntPtr.Add(memory, _memoryOffset).ToPointer());
if (ptr == IntPtr.Zero)
return;
var handle = GCHandle.FromIntPtr(ptr);
handle.Free();
}
}
+2
View File
@@ -85,6 +85,8 @@ BehaviorUpdateResult BehaviorTreeNode::InvokeUpdate(const BehaviorUpdateContext&
result = BehaviorUpdateResult::Failed;
else
result = Update(context);
if ((int32)result < 0 || (int32)result > (int32)BehaviorUpdateResult::Failed)
result = BehaviorUpdateResult::Failed; // Invalid value is a failure
// Post-process result from decorators
for (BehaviorTreeDecorator* decorator : _decorators)
@@ -1342,7 +1342,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
{
const bool xAxis = Math::IsZero(v0.X) && Math::IsZero(v1.X);
const bool yAxis = Math::IsZero(v0.Y) && Math::IsZero(v1.Y);
if (xAxis || yAxis)
if (xAxis && yAxis)
{
// Single animation
value = SampleAnimation(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, aData.W);
}
else if (xAxis || yAxis)
{
if (yAxis)
{
@@ -2109,7 +2114,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
bucket.LoopsLeft--;
bucket.LoopsDone++;
}
value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, slot.Speed);
// Speed is accounted for in the new time pos, so keep sample speed at 1
value = SampleAnimation(node, loop, length, 0.0f, bucket.TimePosition, newTimePos, anim, 1);
bucket.TimePosition = newTimePos;
if (bucket.LoopsLeft == 0 && slot.BlendOutTime > 0.0f && length - slot.BlendOutTime < bucket.TimePosition)
{
+1
View File
@@ -225,6 +225,7 @@ bool AudioClip::ExtractDataRaw(Array<byte>& resultData, AudioDataInfo& resultDat
void AudioClip::CancelStreaming()
{
Asset::CancelStreaming();
CancelStreamingTasks();
}
+34 -6
View File
@@ -16,11 +16,13 @@
AssetReferenceBase::~AssetReferenceBase()
{
if (_asset)
Asset* asset = _asset;
if (asset)
{
_asset->OnLoaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
_asset->OnUnloaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
_asset->RemoveReference();
_asset = nullptr;
asset->OnLoaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnLoaded>(this);
asset->OnUnloaded.Unbind<AssetReferenceBase, &AssetReferenceBase::OnUnloaded>(this);
asset->RemoveReference();
}
}
@@ -70,8 +72,12 @@ void AssetReferenceBase::OnUnloaded(Asset* asset)
WeakAssetReferenceBase::~WeakAssetReferenceBase()
{
if (_asset)
_asset->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
Asset* asset = _asset;
if (asset)
{
_asset = nullptr;
asset->OnUnloaded.Unbind<WeakAssetReferenceBase, &WeakAssetReferenceBase::OnUnloaded>(this);
}
}
String WeakAssetReferenceBase::ToString() const
@@ -101,6 +107,20 @@ void WeakAssetReferenceBase::OnUnloaded(Asset* asset)
_asset = nullptr;
}
SoftAssetReferenceBase::~SoftAssetReferenceBase()
{
Asset* asset = _asset;
if (asset)
{
_asset = nullptr;
asset->OnUnloaded.Unbind<SoftAssetReferenceBase, &SoftAssetReferenceBase::OnUnloaded>(this);
asset->RemoveReference();
}
#if !BUILD_RELEASE
_id = Guid::Empty;
#endif
}
String SoftAssetReferenceBase::ToString() const
{
return _asset ? _asset->ToString() : (_id.IsValid() ? _id.ToString() : TEXT("<null>"));
@@ -502,6 +522,14 @@ void Asset::InitAsVirtual()
void Asset::CancelStreaming()
{
// Cancel loading task but go over asset locker to prevent case if other load threads still loads asset while it's reimported on other thread
Locker.Lock();
ContentLoadTask* loadTask = _loadingTask;
Locker.Unlock();
if (loadTask)
{
loadTask->Cancel();
}
}
#if USE_EDITOR
+3 -6
View File
@@ -9,9 +9,6 @@
/// </summary>
class FLAXENGINE_API AssetReferenceBase
{
public:
typedef Delegate<> EventType;
protected:
Asset* _asset = nullptr;
@@ -19,17 +16,17 @@ public:
/// <summary>
/// The asset loaded event (fired when asset gets loaded or is already loaded after change).
/// </summary>
EventType Loaded;
Action Loaded;
/// <summary>
/// The asset unloading event (should cleanup refs to it).
/// </summary>
EventType Unload;
Action Unload;
/// <summary>
/// Action fired when field gets changed (link a new asset or change to the another value).
/// </summary>
EventType Changed;
Action Changed;
public:
NON_COPYABLE(AssetReferenceBase);
+1
View File
@@ -783,6 +783,7 @@ void Model::InitAsVirtual()
void Model::CancelStreaming()
{
Asset::CancelStreaming();
CancelStreamingTasks();
}
@@ -969,6 +969,7 @@ void SkinnedModel::InitAsVirtual()
void SkinnedModel::CancelStreaming()
{
Asset::CancelStreaming();
CancelStreamingTasks();
}
@@ -1428,6 +1428,10 @@ Asset::LoadResult VisualScript::load()
#if USE_EDITOR
if (_instances.HasItems())
{
// Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use
_loadFailed = false;
_isLoaded = true;
// Setup scripting type
CacheScriptingType();
@@ -1512,7 +1516,7 @@ void VisualScript::unload(bool isReloading)
// Note: preserve the registered scripting type but invalidate the locally cached handle
if (_scriptingTypeHandle)
{
VisualScriptingModule.Locker.Lock();
VisualScriptingBinaryModule::Locker.Lock();
auto& type = VisualScriptingModule.Types[_scriptingTypeHandle.TypeIndex];
if (type.Script.DefaultInstance)
{
@@ -1523,7 +1527,7 @@ void VisualScript::unload(bool isReloading)
VisualScriptingModule.Scripts[_scriptingTypeHandle.TypeIndex] = nullptr;
_scriptingTypeHandleCached = _scriptingTypeHandle;
_scriptingTypeHandle = ScriptingTypeHandle();
VisualScriptingModule.Locker.Unlock();
VisualScriptingBinaryModule::Locker.Unlock();
}
}
@@ -1534,8 +1538,8 @@ AssetChunksFlag VisualScript::getChunksToPreload() const
void VisualScript::CacheScriptingType()
{
ScopeLock lock(VisualScriptingBinaryModule::Locker);
auto& binaryModule = VisualScriptingModule;
ScopeLock lock(binaryModule.Locker);
// Find base type
const StringAnsi baseTypename(Meta.BaseTypename);
+19 -11
View File
@@ -323,26 +323,29 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool
{
// Ensure path is in a valid format
String pathNorm(path);
FileSystem::NormalizePath(pathNorm);
ContentStorageManager::FormatPath(pathNorm);
const StringView filePath = pathNorm;
// Find target storage container and the asset
auto storage = ContentStorageManager::TryGetStorage(pathNorm);
auto asset = Content::GetAsset(pathNorm);
auto storage = ContentStorageManager::TryGetStorage(filePath);
auto asset = Content::GetAsset(filePath);
auto binaryAsset = dynamic_cast<BinaryAsset*>(asset);
if (asset && !binaryAsset)
{
LOG(Warning, "Cannot write to the non-binary asset location.");
return true;
}
if (!binaryAsset && !storage && FileSystem::FileExists(filePath))
{
// Force-resolve storage (asset at that path could be not yet loaded into registry)
storage = ContentStorageManager::GetStorage(filePath);
}
// Check if can perform write operation to the asset container
if (storage)
if (storage && !storage->AllowDataModifications())
{
if (!storage->AllowDataModifications())
{
LOG(Warning, "Cannot write to the asset storage container.");
return true;
}
LOG(Warning, "Cannot write to the asset storage container.");
return true;
}
// Initialize data container
@@ -352,6 +355,11 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool
// Use the same asset ID
data.Header.ID = binaryAsset->GetID();
}
else if (storage && storage->GetEntriesCount())
{
// Use the same file ID
data.Header.ID = storage->GetEntry(0).ID;
}
else
{
// Randomize ID
@@ -373,8 +381,8 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool
}
else
{
ASSERT(pathNorm.HasChars());
result = FlaxStorage::Create(pathNorm, data, silentMode);
ASSERT(filePath.HasChars());
result = FlaxStorage::Create(filePath, data, silentMode);
}
if (binaryAsset)
binaryAsset->_isSaving = false;
+37 -47
View File
@@ -54,8 +54,7 @@ namespace
// Assets
CriticalSection AssetsLocker;
Dictionary<Guid, Asset*> Assets(2048);
CriticalSection LoadCallAssetsLocker;
Array<Guid> LoadCallAssets(64);
Array<Guid> LoadCallAssets(PLATFORM_THREADS_LIMIT);
CriticalSection LoadedAssetsToInvokeLocker;
Array<Asset*> LoadedAssetsToInvoke(64);
Array<Asset*> ToUnload;
@@ -449,18 +448,19 @@ Asset* Content::LoadAsync(const StringView& path, const ScriptingTypeHandle& typ
{
// Ensure path is in a valid format
String pathNorm(path);
StringUtils::PathRemoveRelativeParts(pathNorm);
ContentStorageManager::FormatPath(pathNorm);
const StringView filePath = pathNorm;
#if USE_EDITOR
if (!FileSystem::FileExists(pathNorm))
if (!FileSystem::FileExists(filePath))
{
LOG(Error, "Missing file \'{0}\'", pathNorm);
LOG(Error, "Missing file \'{0}\'", filePath);
return nullptr;
}
#endif
AssetInfo assetInfo;
if (GetAssetInfo(pathNorm, assetInfo))
if (GetAssetInfo(filePath, assetInfo))
{
return LoadAsync(assetInfo.ID, type);
}
@@ -910,9 +910,13 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
return nullptr;
// Check if asset has been already loaded
Asset* result = GetAsset(id);
Asset* result = nullptr;
AssetsLocker.Lock();
Assets.TryGet(id, result);
if (result)
{
AssetsLocker.Unlock();
// Validate type
if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type))
{
@@ -923,57 +927,41 @@ Asset* Content::LoadAsync(const Guid& id, const ScriptingTypeHandle& type)
}
// Check if that asset is during loading
LoadCallAssetsLocker.Lock();
if (LoadCallAssets.Contains(id))
{
LoadCallAssetsLocker.Unlock();
AssetsLocker.Unlock();
// Wait for load end
// TODO: dont use active waiting and prevent deadlocks if running on a main thread
//while (!Engine::ShouldExit())
while (true)
// Wait for loading end by other thread
bool contains = true;
while (contains)
{
LoadCallAssetsLocker.Lock();
const bool contains = LoadCallAssets.Contains(id);
LoadCallAssetsLocker.Unlock();
if (!contains)
return GetAsset(id);
Platform::Sleep(1);
AssetsLocker.Lock();
contains = LoadCallAssets.Contains(id);
AssetsLocker.Unlock();
}
}
else
{
// Mark asset as loading
LoadCallAssets.Add(id);
LoadCallAssetsLocker.Unlock();
Assets.TryGet(id, result);
return result;
}
// Load asset
AssetInfo assetInfo;
result = load(id, type, assetInfo);
// Mark asset as loading and release lock so other threads can load other assets
LoadCallAssets.Add(id);
AssetsLocker.Unlock();
// End loading
LoadCallAssetsLocker.Lock();
LoadCallAssets.Remove(id);
LoadCallAssetsLocker.Unlock();
#define LOAD_FAILED() AssetsLocker.Lock(); LoadCallAssets.Remove(id); AssetsLocker.Unlock(); return nullptr
return result;
}
Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& assetInfo)
{
// Get cached asset info (from registry)
AssetInfo assetInfo;
if (!GetAssetInfo(id, assetInfo))
{
LOG(Warning, "Invalid or missing asset ({0}, {1}).", id, type.ToString());
return nullptr;
LOAD_FAILED();
}
#if ASSETS_LOADING_EXTRA_VERIFICATION
if (!FileSystem::FileExists(assetInfo.Path))
{
LOG(Error, "Cannot find file '{0}'", assetInfo.Path);
return nullptr;
LOAD_FAILED();
}
#endif
@@ -982,28 +970,27 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
if (factory == nullptr)
{
LOG(Error, "Cannot find asset factory. Info: {0}", assetInfo.ToString());
return nullptr;
LOAD_FAILED();
}
// Create asset object
auto result = factory->New(assetInfo);
result = factory->New(assetInfo);
if (result == nullptr)
{
LOG(Error, "Cannot create asset object. Info: {0}", assetInfo.ToString());
return nullptr;
LOAD_FAILED();
}
ASSERT(result->GetID() == id);
#if ASSETS_LOADING_EXTRA_VERIFICATION
if (IsAssetTypeIdInvalid(type, result->GetTypeHandle()) && !result->Is(type))
{
LOG(Error, "Different loaded asset type! Asset: '{0}'. Expected type: {1}", assetInfo.ToString(), type.ToString());
LOG(Warning, "Different loaded asset type! Asset: '{0}'. Expected type: {1}", assetInfo.ToString(), type.ToString());
result->DeleteObject();
return nullptr;
LOAD_FAILED();
}
#endif
// Register asset
ASSERT(result->GetID() == id);
AssetsLocker.Lock();
#if ASSETS_LOADING_EXTRA_VERIFICATION
ASSERT(!Assets.ContainsKey(id));
@@ -1011,11 +998,14 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
Assets.Add(id, result);
// Start asset loading
// TODO: refactor this to create asset loading task-chain before AssetsLocker.Lock() to allow better parallelization
result->startLoading();
// Remove from the loading queue and release lock
LoadCallAssets.Remove(id);
AssetsLocker.Unlock();
#undef LOAD_FAILED
return result;
}
-1
View File
@@ -366,7 +366,6 @@ private:
static void onAssetLoaded(Asset* asset);
static void onAssetUnload(Asset* asset);
static void onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId);
static Asset* load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo& assetInfo);
private:
static void deleteFileSafety(const StringView& path, const Guid& id);
@@ -48,6 +48,8 @@ protected:
// [ContentLoadTask]
Result run() override
{
if (IsCancelRequested())
return Result::Ok;
PROFILE_CPU();
AssetReference<BinaryAsset> ref = _asset.Get();
@@ -67,8 +69,6 @@ protected:
{
if (IsCancelRequested())
return Result::Ok;
// Load it
#if TRACY_ENABLE
ZoneScoped;
ZoneName(*name, name.Length());
+1 -3
View File
@@ -30,9 +30,7 @@ public:
/// <summary>
/// Finalizes an instance of the <see cref="SoftAssetReferenceBase"/> class.
/// </summary>
~SoftAssetReferenceBase()
{
}
~SoftAssetReferenceBase();
public:
/// <summary>
@@ -6,6 +6,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Engine/Globals.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/TaskGraph.h"
@@ -185,6 +186,16 @@ void ContentStorageManager::EnsureUnlocked()
Locker.Unlock();
}
void ContentStorageManager::FormatPath(String& path)
{
StringUtils::PathRemoveRelativeParts(path);
if (FileSystem::IsRelative(path))
{
// Convert local-project paths into absolute format which is used by Content Storage system
path = Globals::ProjectFolder / path;
}
}
bool ContentStorageManager::IsFlaxStoragePath(const String& path)
{
auto extension = FileSystem::GetExtension(path).ToLower();
@@ -75,6 +75,9 @@ public:
/// </summary>
static void EnsureUnlocked();
// Formats path into valid format used by the storage system (normalized and absolute).
static void FormatPath(String& path);
public:
/// <summary>
/// Determines whether the specified path can be a binary asset file (based on it's extension).
@@ -1302,15 +1302,15 @@ void FlaxStorage::CloseFileHandles()
// In those situations all the async tasks using this storage should be cancelled externally
// Ensure that no one is using this resource
int32 waitTime = 10;
int32 waitTime = 100;
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
Platform::Sleep(10);
Platform::Sleep(1);
if (Platform::AtomicRead(&_chunksLock) != 0)
{
// File can be locked by some streaming tasks (eg. AudioClip::StreamingTask or StreamModelLODTask)
Entry e;
for (int32 i = 0; i < GetEntriesCount(); i++)
{
Entry e;
GetEntry(i, e);
Asset* asset = Content::GetAsset(e.ID);
if (asset)
@@ -1320,8 +1320,12 @@ void FlaxStorage::CloseFileHandles()
}
}
}
waitTime = 100;
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
Platform::Sleep(1);
ASSERT(_chunksLock == 0);
// Close file handles (from all threads)
_file.DeleteAll();
}
+27 -7
View File
@@ -25,6 +25,19 @@ private:
int32 _capacity;
AllocationData _allocation;
FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromCount, int32 fromCapacity)
{
if IF_CONSTEXPR (AllocationType::HasSwap)
to.Swap(from);
else
{
to.Allocate(fromCapacity);
Memory::MoveItems(to.Get(), from.Get(), fromCount);
Memory::DestructItems(from.Get(), fromCount);
from.Free();
}
}
public:
/// <summary>
/// Initializes a new instance of the <see cref="Array"/> class.
@@ -134,7 +147,7 @@ public:
_capacity = other._capacity;
other._count = 0;
other._capacity = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _count, _capacity);
}
/// <summary>
@@ -191,7 +204,7 @@ public:
_capacity = other._capacity;
other._count = 0;
other._capacity = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _count, _capacity);
}
return *this;
}
@@ -713,9 +726,18 @@ public:
/// <param name="other">The other collection.</param>
void Swap(Array& other)
{
::Swap(_count, other._count);
::Swap(_capacity, other._capacity);
_allocation.Swap(other._allocation);
if IF_CONSTEXPR (AllocationType::HasSwap)
{
_allocation.Swap(other._allocation);
::Swap(_count, other._count);
::Swap(_capacity, other._capacity);
}
else
{
Array tmp = MoveTemp(other);
other = *this;
*this = MoveTemp(tmp);
}
}
/// <summary>
@@ -726,9 +748,7 @@ public:
T* data = _allocation.Get();
const int32 count = _count / 2;
for (int32 i = 0; i < count; i++)
{
::Swap(data[i], data[_count - i - 1]);
}
}
public:
+17 -7
View File
@@ -22,6 +22,16 @@ private:
int32 _capacity;
AllocationData _allocation;
FORCE_INLINE static int32 ToItemCount(int32 size)
{
return Math::DivideAndRoundUp<int32>(size, sizeof(ItemType));
}
FORCE_INLINE static int32 ToItemCapacity(int32 size)
{
return Math::Max<int32>(Math::DivideAndRoundUp<int32>(size, sizeof(ItemType)), 1);
}
public:
/// <summary>
/// Initializes a new instance of the <see cref="BitArray"/> class.
@@ -41,7 +51,7 @@ public:
, _capacity(capacity)
{
if (capacity > 0)
_allocation.Allocate(Math::Max<ItemType>(capacity / sizeof(ItemType), 1));
_allocation.Allocate(ToItemCapacity(capacity));
}
/// <summary>
@@ -53,7 +63,7 @@ public:
_count = _capacity = other.Count();
if (_capacity > 0)
{
const uint64 itemsCapacity = Math::Max<ItemType>(_capacity / sizeof(ItemType), 1);
const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -69,7 +79,7 @@ public:
_count = _capacity = other.Count();
if (_capacity > 0)
{
const uint64 itemsCapacity = Math::Max<ItemType>(_capacity / sizeof(ItemType), 1);
const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -101,7 +111,7 @@ public:
{
_allocation.Free();
_capacity = other._count;
const uint64 itemsCapacity = Math::Max<ItemType>(_capacity / sizeof(ItemType), 1);
const int32 itemsCapacity = ToItemCapacity(_capacity);
_allocation.Allocate(itemsCapacity);
Platform::MemoryCopy(Get(), other.Get(), itemsCapacity * sizeof(ItemType));
}
@@ -246,7 +256,7 @@ public:
return;
ASSERT(capacity >= 0);
const int32 count = preserveContents ? (_count < capacity ? _count : capacity) : 0;
_allocation.Relocate(Math::Max<ItemType>(capacity / sizeof(ItemType), 1), _count / sizeof(ItemType), count / sizeof(ItemType));
_allocation.Relocate(ToItemCapacity(capacity), ToItemCount(_count), ToItemCount(count));
_capacity = capacity;
_count = count;
}
@@ -272,7 +282,7 @@ public:
{
if (_capacity < minCapacity)
{
const int32 capacity = _allocation.CalculateCapacityGrow(Math::Max<int32>(_capacity / sizeof(ItemType), 1), minCapacity);
const int32 capacity = _allocation.CalculateCapacityGrow(ToItemCapacity(_capacity), minCapacity);
SetCapacity(capacity, preserveContents);
}
}
@@ -284,7 +294,7 @@ public:
void SetAll(const bool value)
{
if (_count != 0)
Platform::MemorySet(_allocation.Get(), Math::Max<ItemType>(_count / sizeof(ItemType), 1), value ? MAX_int32 : 0);
Platform::MemorySet(_allocation.Get(), ToItemCount(_count) * sizeof(ItemType), value ? MAX_uint32 : 0);
}
/// <summary>
+18 -5
View File
@@ -2,13 +2,26 @@
#pragma once
/// <summary>
/// Default capacity for the dictionaries (amount of space for the elements)
/// </summary>
#define DICTIONARY_DEFAULT_CAPACITY 256
#include "Engine/Platform/Defines.h"
/// <summary>
/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size)
/// Default capacity for the dictionaries (amount of space for the elements).
/// </summary>
#ifndef DICTIONARY_DEFAULT_CAPACITY
#if PLATFORM_DESKTOP
#define DICTIONARY_DEFAULT_CAPACITY 256
#else
#define DICTIONARY_DEFAULT_CAPACITY 64
#endif
#endif
/// <summary>
/// Default slack space divider for the dictionaries.
/// </summary>
#define DICTIONARY_DEFAULT_SLACK_SCALE 3
/// <summary>
/// Function for dictionary that tells how change hash index during iteration (size param is a buckets table size).
/// </summary>
#define DICTIONARY_PROB_FUNC(size, numChecks) (numChecks)
//#define DICTIONARY_PROB_FUNC(size, numChecks) (1)
+138 -58
View File
@@ -40,7 +40,7 @@ public:
private:
State _state;
void Free()
FORCE_INLINE void Free()
{
if (_state == Occupied)
{
@@ -50,7 +50,7 @@ public:
_state = Empty;
}
void Delete()
FORCE_INLINE void Delete()
{
_state = Deleted;
Memory::DestructItem(&Key);
@@ -58,7 +58,7 @@ public:
}
template<typename KeyComparableType>
void Occupy(const KeyComparableType& key)
FORCE_INLINE void Occupy(const KeyComparableType& key)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::ConstructItem(&Value);
@@ -66,7 +66,7 @@ public:
}
template<typename KeyComparableType>
void Occupy(const KeyComparableType& key, const ValueType& value)
FORCE_INLINE void Occupy(const KeyComparableType& key, const ValueType& value)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::ConstructItems(&Value, &value, 1);
@@ -74,7 +74,7 @@ public:
}
template<typename KeyComparableType>
void Occupy(const KeyComparableType& key, ValueType&& value)
FORCE_INLINE void Occupy(const KeyComparableType& key, ValueType&& value)
{
Memory::ConstructItems(&Key, &key, 1);
Memory::MoveItems(&Value, &value, 1);
@@ -110,6 +110,33 @@ private:
int32 _size = 0;
AllocationData _allocation;
FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromSize)
{
if IF_CONSTEXPR (AllocationType::HasSwap)
to.Swap(from);
else
{
to.Allocate(fromSize);
Bucket* toData = to.Get();
Bucket* fromData = from.Get();
for (int32 i = 0; i < fromSize; i++)
{
Bucket& fromBucket = fromData[i];
if (fromBucket.IsOccupied())
{
Bucket& toBucket = toData[i];
Memory::MoveItems(&toBucket.Key, &fromBucket.Key, 1);
Memory::MoveItems(&toBucket.Value, &fromBucket.Value, 1);
toBucket._state = Bucket::Occupied;
Memory::DestructItem(&fromBucket.Key);
Memory::DestructItem(&fromBucket.Value);
fromBucket._state = Bucket::Empty;
}
}
from.Free();
}
}
public:
/// <summary>
/// Initializes a new instance of the <see cref="Dictionary"/> class.
@@ -132,9 +159,6 @@ public:
/// </summary>
/// <param name="other">The other collection to move.</param>
Dictionary(Dictionary&& other) noexcept
: _elementsCount(other._elementsCount)
, _deletedCount(other._deletedCount)
, _size(other._size)
{
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
@@ -142,7 +166,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _size);
}
/// <summary>
@@ -183,7 +207,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _size);
}
return *this;
}
@@ -375,8 +399,12 @@ public:
template<typename KeyComparableType>
ValueType& At(const KeyComparableType& key)
{
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
Compact();
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity(_elementsCount + _deletedCount + 1);
EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
// Find location of the item or place to insert it
FindPositionResult pos;
@@ -388,9 +416,9 @@ public:
// Insert
ASSERT(pos.FreeSlotIndex != -1);
_elementsCount++;
Bucket& bucket = _allocation.Get()[pos.FreeSlotIndex];
bucket.Occupy(key);
_elementsCount++;
return bucket.Value;
}
@@ -493,7 +521,7 @@ public:
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
{
if (i->Value)
Delete(i->Value);
::Delete(i->Value);
}
Clear();
}
@@ -509,7 +537,7 @@ public:
return;
ASSERT(capacity >= 0);
AllocationData oldAllocation;
oldAllocation.Swap(_allocation);
MoveToEmpty(oldAllocation, _allocation, _size);
const int32 oldSize = _size;
const int32 oldElementsCount = _elementsCount;
_deletedCount = _elementsCount = 0;
@@ -533,13 +561,22 @@ public:
}
_size = capacity;
Bucket* oldData = oldAllocation.Get();
if (oldElementsCount != 0 && preserveContents)
if (oldElementsCount != 0 && capacity != 0 && preserveContents)
{
// TODO; move keys and values on realloc
FindPositionResult pos;
for (int32 i = 0; i < oldSize; i++)
{
if (oldData[i].IsOccupied())
Add(oldData[i].Key, MoveTemp(oldData[i].Value));
Bucket& oldBucket = oldData[i];
if (oldBucket.IsOccupied())
{
FindPosition(oldBucket.Key, pos);
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
bucket->_state = Bucket::Occupied;
_elementsCount++;
}
}
}
if (oldElementsCount != 0)
@@ -558,9 +595,9 @@ public:
{
if (_size >= minCapacity)
return;
if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
minCapacity = DICTIONARY_DEFAULT_CAPACITY;
const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
if (capacity < DICTIONARY_DEFAULT_CAPACITY)
capacity = DICTIONARY_DEFAULT_CAPACITY;
SetCapacity(capacity, preserveContents);
}
@@ -570,10 +607,19 @@ public:
/// <param name="other">The other collection.</param>
void Swap(Dictionary& other)
{
::Swap(_elementsCount, other._elementsCount);
::Swap(_deletedCount, other._deletedCount);
::Swap(_size, other._size);
_allocation.Swap(other._allocation);
if IF_CONSTEXPR (AllocationType::HasSwap)
{
::Swap(_elementsCount, other._elementsCount);
::Swap(_deletedCount, other._deletedCount);
::Swap(_size, other._size);
_allocation.Swap(other._allocation);
}
else
{
Dictionary tmp = MoveTemp(other);
other = *this;
*this = MoveTemp(tmp);
}
}
public:
@@ -584,24 +630,10 @@ public:
/// <param name="value">The value.</param>
/// <returns>Weak reference to the stored bucket.</returns>
template<typename KeyComparableType>
Bucket* Add(const KeyComparableType& key, const ValueType& value)
FORCE_INLINE Bucket* Add(const KeyComparableType& key, const ValueType& value)
{
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity(_elementsCount + _deletedCount + 1);
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(key, pos);
// Ensure key is unknown
ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
// Insert
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Bucket* bucket = OnAdd(key);
bucket->Occupy(key, value);
_elementsCount++;
return bucket;
}
@@ -612,24 +644,10 @@ public:
/// <param name="value">The value.</param>
/// <returns>Weak reference to the stored bucket.</returns>
template<typename KeyComparableType>
Bucket* Add(const KeyComparableType& key, ValueType&& value)
FORCE_INLINE Bucket* Add(const KeyComparableType& key, ValueType&& value)
{
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity(_elementsCount + _deletedCount + 1);
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(key, pos);
// Ensure key is unknown
ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
// Insert
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Bucket* bucket = OnAdd(key);
bucket->Occupy(key, MoveTemp(value));
_elementsCount++;
return bucket;
}
@@ -851,7 +869,7 @@ public:
return Iterator(this, _size);
}
protected:
private:
/// <summary>
/// The result container of the dictionary item lookup searching.
/// </summary>
@@ -911,4 +929,66 @@ protected:
result.ObjectIndex = -1;
result.FreeSlotIndex = insertPos;
}
template<typename KeyComparableType>
Bucket* OnAdd(const KeyComparableType& key)
{
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
Compact();
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(key, pos);
// Ensure key is unknown
ASSERT(pos.ObjectIndex == -1 && "That key has been already added to the dictionary.");
// Insert
ASSERT(pos.FreeSlotIndex != -1);
_elementsCount++;
return &_allocation.Get()[pos.FreeSlotIndex];
}
void Compact()
{
if (_elementsCount == 0)
{
// Fast path if it's empty
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i]._state = Bucket::Empty;
}
else
{
// Rebuild entire table completely
AllocationData oldAllocation;
MoveToEmpty(oldAllocation, _allocation, _size);
_allocation.Allocate(_size);
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i]._state = Bucket::Empty;
Bucket* oldData = oldAllocation.Get();
FindPositionResult pos;
for (int32 i = 0; i < _size; i++)
{
Bucket& oldBucket = oldData[i];
if (oldBucket.IsOccupied())
{
FindPosition(oldBucket.Key, pos);
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Memory::MoveItems(&bucket->Key, &oldBucket.Key, 1);
Memory::MoveItems(&bucket->Value, &oldBucket.Value, 1);
bucket->_state = Bucket::Occupied;
}
}
for (int32 i = 0; i < _size; i++)
oldData[i].Free();
}
_deletedCount = 0;
}
};
+167 -37
View File
@@ -37,26 +37,33 @@ public:
private:
State _state;
void Free()
FORCE_INLINE void Free()
{
if (_state == Occupied)
Memory::DestructItem(&Item);
_state = Empty;
}
void Delete()
FORCE_INLINE void Delete()
{
_state = Deleted;
Memory::DestructItem(&Item);
}
template<typename ItemType>
void Occupy(const ItemType& item)
FORCE_INLINE void Occupy(const ItemType& item)
{
Memory::ConstructItems(&Item, &item, 1);
_state = Occupied;
}
template<typename ItemType>
FORCE_INLINE void Occupy(ItemType& item)
{
Memory::MoveItems(&Item, &item, 1);
_state = Occupied;
}
FORCE_INLINE bool IsEmpty() const
{
return _state == Empty;
@@ -86,6 +93,31 @@ private:
int32 _size = 0;
AllocationData _allocation;
FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromSize)
{
if IF_CONSTEXPR (AllocationType::HasSwap)
to.Swap(from);
else
{
to.Allocate(fromSize);
Bucket* toData = to.Get();
Bucket* fromData = from.Get();
for (int32 i = 0; i < fromSize; i++)
{
Bucket& fromBucket = fromData[i];
if (fromBucket.IsOccupied())
{
Bucket& toBucket = toData[i];
Memory::MoveItems(&toBucket.Item, &fromBucket.Item, 1);
toBucket._state = Bucket::Occupied;
Memory::DestructItem(&fromBucket.Item);
fromBucket._state = Bucket::Empty;
}
}
from.Free();
}
}
public:
/// <summary>
/// Initializes a new instance of the <see cref="HashSet"/> class.
@@ -108,9 +140,6 @@ public:
/// </summary>
/// <param name="other">The other collection to move.</param>
HashSet(HashSet&& other) noexcept
: _elementsCount(other._elementsCount)
, _deletedCount(other._deletedCount)
, _size(other._size)
{
_elementsCount = other._elementsCount;
_deletedCount = other._deletedCount;
@@ -118,7 +147,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _size);
}
/// <summary>
@@ -159,7 +188,7 @@ public:
other._elementsCount = 0;
other._deletedCount = 0;
other._size = 0;
_allocation.Swap(other._allocation);
MoveToEmpty(_allocation, other._allocation, _size);
}
return *this;
}
@@ -169,7 +198,7 @@ public:
/// </summary>
~HashSet()
{
SetCapacity(0, false);
Clear();
}
public:
@@ -216,6 +245,7 @@ public:
HashSet* _collection;
int32 _index;
public:
Iterator(HashSet* collection, const int32 index)
: _collection(collection)
, _index(index)
@@ -228,7 +258,12 @@ public:
{
}
public:
Iterator()
: _collection(nullptr)
, _index(-1)
{
}
Iterator(const Iterator& i)
: _collection(i._collection)
, _index(i._index)
@@ -242,6 +277,11 @@ public:
}
public:
FORCE_INLINE int32 Index() const
{
return _index;
}
FORCE_INLINE bool IsEnd() const
{
return _index == _collection->_size;
@@ -374,7 +414,7 @@ public:
return;
ASSERT(capacity >= 0);
AllocationData oldAllocation;
oldAllocation.Swap(_allocation);
MoveToEmpty(oldAllocation, _allocation, _size);
const int32 oldSize = _size;
const int32 oldElementsCount = _elementsCount;
_deletedCount = _elementsCount = 0;
@@ -398,13 +438,21 @@ public:
}
_size = capacity;
Bucket* oldData = oldAllocation.Get();
if (oldElementsCount != 0 && preserveContents)
if (oldElementsCount != 0 && capacity != 0 && preserveContents)
{
// TODO; move keys and values on realloc
FindPositionResult pos;
for (int32 i = 0; i < oldSize; i++)
{
if (oldData[i].IsOccupied())
Add(oldData[i].Item);
Bucket& oldBucket = oldData[i];
if (oldBucket.IsOccupied())
{
FindPosition(oldBucket.Item, pos);
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
bucket->_state = Bucket::Occupied;
_elementsCount++;
}
}
}
if (oldElementsCount != 0)
@@ -421,14 +469,35 @@ public:
/// <param name="preserveContents">True if preserve collection data when changing its size, otherwise collection after resize will be empty.</param>
void EnsureCapacity(int32 minCapacity, bool preserveContents = true)
{
if (Capacity() >= minCapacity)
if (_size >= minCapacity)
return;
if (minCapacity < DICTIONARY_DEFAULT_CAPACITY)
minCapacity = DICTIONARY_DEFAULT_CAPACITY;
const int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
int32 capacity = _allocation.CalculateCapacityGrow(_size, minCapacity);
if (capacity < DICTIONARY_DEFAULT_CAPACITY)
capacity = DICTIONARY_DEFAULT_CAPACITY;
SetCapacity(capacity, preserveContents);
}
/// <summary>
/// Swaps the contents of collection with the other object without copy operation. Performs fast internal data exchange.
/// </summary>
/// <param name="other">The other collection.</param>
void Swap(HashSet& other)
{
if IF_CONSTEXPR (AllocationType::HasSwap)
{
::Swap(_elementsCount, other._elementsCount);
::Swap(_deletedCount, other._deletedCount);
::Swap(_size, other._size);
_allocation.Swap(other._allocation);
}
else
{
HashSet tmp = MoveTemp(other);
other = *this;
*this = MoveTemp(tmp);
}
}
public:
/// <summary>
/// Add element to the collection.
@@ -438,24 +507,23 @@ public:
template<typename ItemType>
bool Add(const ItemType& item)
{
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity(_elementsCount + _deletedCount + 1);
Bucket* bucket = OnAdd(item);
if (bucket)
bucket->Occupy(item);
return bucket != nullptr;
}
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(item, pos);
// Check if object has been already added
if (pos.ObjectIndex != -1)
return false;
// Insert
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
bucket->Occupy(item);
_elementsCount++;
return true;
/// <summary>
/// Add element to the collection.
/// </summary>
/// <param name="item">The element to add to the set.</param>
/// <returns>True if element has been added to the collection, otherwise false if the element is already present.</returns>
bool Add(T&& item)
{
Bucket* bucket = OnAdd(item);
if (bucket)
bucket->Occupy(MoveTemp(item));
return bucket != nullptr;
}
/// <summary>
@@ -593,7 +661,7 @@ public:
return Iterator(this, _size);
}
protected:
private:
/// <summary>
/// The result container of the set item lookup searching.
/// </summary>
@@ -654,4 +722,66 @@ protected:
result.ObjectIndex = -1;
result.FreeSlotIndex = insertPos;
}
template<typename ItemType>
Bucket* OnAdd(const ItemType& key)
{
// Check if need to rehash elements (prevent many deleted elements that use too much of capacity)
if (_deletedCount > _size / DICTIONARY_DEFAULT_SLACK_SCALE)
Compact();
// Ensure to have enough memory for the next item (in case of new element insertion)
EnsureCapacity((_elementsCount + 1) * DICTIONARY_DEFAULT_SLACK_SCALE + _deletedCount);
// Find location of the item or place to insert it
FindPositionResult pos;
FindPosition(key, pos);
// Check if object has been already added
if (pos.ObjectIndex != -1)
return nullptr;
// Insert
ASSERT(pos.FreeSlotIndex != -1);
_elementsCount++;
return &_allocation.Get()[pos.FreeSlotIndex];
}
void Compact()
{
if (_elementsCount == 0)
{
// Fast path if it's empty
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i]._state = Bucket::Empty;
}
else
{
// Rebuild entire table completely
AllocationData oldAllocation;
MoveToEmpty(oldAllocation, _allocation, _size);
_allocation.Allocate(_size);
Bucket* data = _allocation.Get();
for (int32 i = 0; i < _size; i++)
data[i]._state = Bucket::Empty;
Bucket* oldData = oldAllocation.Get();
FindPositionResult pos;
for (int32 i = 0; i < _size; i++)
{
Bucket& oldBucket = oldData[i];
if (oldBucket.IsOccupied())
{
FindPosition(oldBucket.Item, pos);
ASSERT(pos.FreeSlotIndex != -1);
Bucket* bucket = &_allocation.Get()[pos.FreeSlotIndex];
Memory::MoveItems(&bucket->Item, &oldBucket.Item, 1);
bucket->_state = Bucket::Occupied;
}
}
for (int32 i = 0; i < _size; i++)
oldData[i].Free();
}
_deletedCount = 0;
}
};
+7
View File
@@ -93,3 +93,10 @@
#endif
#define PACK_STRUCT(__Declaration__) PACK_BEGIN() __Declaration__ PACK_END()
// C++ 17
#if __cplusplus >= 201703L
#define IF_CONSTEXPR constexpr
#else
#define IF_CONSTEXPR
#endif
@@ -3,7 +3,7 @@
#pragma once
#include "Engine/Core/Config/Settings.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/SerializationFwd.h"
/// <summary>
/// Specifies the display mode of a game window.
+101 -114
View File
@@ -226,7 +226,7 @@ public:
/// <returns>Function result</returns>
FORCE_INLINE ReturnType operator()(Params... params) const
{
ASSERT(_function);
ASSERT_LOW_LAYER(_function);
return _function(_callee, Forward<Params>(params)...);
}
@@ -289,8 +289,13 @@ protected:
intptr volatile _ptr = 0;
intptr volatile _size = 0;
#else
HashSet<FunctionType>* _functions = nullptr;
CriticalSection* _locker = nullptr;
struct Data
{
HashSet<FunctionType> Functions;
CriticalSection Locker;
};
// Holds pointer to Data with Functions and Locker. Thread-safe access via atomic operations.
intptr volatile _data = 0;
#endif
typedef void (*StubSignature)(void*, Params...);
@@ -314,15 +319,12 @@ public:
_ptr = (intptr)newBindings;
_size = newSize;
#else
if (other._functions == nullptr)
Data* otherData = (Data*)Platform::AtomicRead(&_data);
if (otherData == nullptr)
return;
_functions = New<HashSet<FunctionType>>(*other._functions);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
if (i->Item._function && i->Item._lambda)
i->Item.LambdaCtor();
}
_locker = other._locker;
ScopeLock lock(otherData->Locker);
for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i)
Bind(i->Item);
#endif
}
@@ -334,10 +336,8 @@ public:
other._ptr = 0;
other._size = 0;
#else
_functions = other._functions;
_locker = other._locker;
other._functions = nullptr;
other._locker = nullptr;
_data = other._data;
other._data = 0;
#endif
}
@@ -356,20 +356,11 @@ public:
Allocator::Free((void*)_ptr);
}
#else
if (_locker != nullptr)
Data* data = (Data*)_data;
if (data)
{
Allocator::Free(_locker);
_locker = nullptr;
}
if (_functions != nullptr)
{
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
if (i->Item._lambda)
i->Item.LambdaCtor();
}
Allocator::Free(_functions);
_functions = nullptr;
_data = 0;
Delete(data);
}
#endif
}
@@ -385,8 +376,13 @@ public:
for (intptr i = 0; i < size; i++)
Bind(bindings[i]);
#else
for (auto i = other._functions->Begin(); i.IsNotEnd(); ++i)
Bind(i->Item);
Data* otherData = (Data*)Platform::AtomicRead(&_data);
if (otherData != nullptr)
{
ScopeLock lock(otherData->Locker);
for (auto i = otherData->Functions.Begin(); i.IsNotEnd(); ++i)
Bind(i->Item);
}
#endif
}
return *this;
@@ -402,10 +398,8 @@ public:
other._ptr = 0;
other._size = 0;
#else
_functions = other._functions;
_locker = other._locker;
other._functions = nullptr;
other._locker = nullptr;
_data = other._data;
other._data = 0;
#endif
}
return *this;
@@ -507,12 +501,20 @@ public:
Allocator::Free(bindings);
}
#else
if (_locker == nullptr)
_locker = New<CriticalSection>();
ScopeLock lock(*_locker);
if (_functions == nullptr)
_functions = New<HashSet<FunctionType>>(32);
_functions->Add(f);
Data* data = (Data*)Platform::AtomicRead(&_data);
while (!data)
{
Data* newData = New<Data>();
Data* oldData = (Data*)Platform::InterlockedCompareExchange(&_data, (intptr)newData, (intptr)data);
if (oldData != data)
{
// Other thread already set the new data so free it and try again
Delete(newData);
}
data = (Data*)Platform::AtomicRead(&_data);
}
ScopeLock lock(data->Locker);
data->Functions.Add(f);
#endif
}
@@ -568,13 +570,22 @@ public:
}
}
#else
if (_locker == nullptr)
_locker = New<CriticalSection>();
ScopeLock lock(*_locker);
if (_functions && _functions->Contains(f))
return;
Data* data = (Data*)Platform::AtomicRead(&_data);
if (data)
{
data->Locker.Lock();
if (data->Functions.Contains(f))
{
data->Locker.Unlock();
return;
}
}
#endif
Bind(f);
#if !DELEGATE_USE_ATOMIC
if (data)
data->Locker.Unlock();
#endif
}
/// <summary>
@@ -583,18 +594,9 @@ public:
template<void(*Method)(Params...)>
void Unbind()
{
#if DELEGATE_USE_ATOMIC
FunctionType f;
f.template Bind<Method>();
Unbind(f);
#else
if (_functions == nullptr)
return;
FunctionType f;
f.template Bind<Method>();
ScopeLock lock(*_locker);
_functions->Remove(f);
#endif
}
/// <summary>
@@ -604,18 +606,9 @@ public:
template<class T, void(T::*Method)(Params...)>
void Unbind(T* callee)
{
#if DELEGATE_USE_ATOMIC
FunctionType f;
f.template Bind<T, Method>(callee);
Unbind(f);
#else
if (_functions == nullptr)
return;
FunctionType f;
f.template Bind<T, Method>(callee);
ScopeLock lock(*_locker);
_functions->Remove(f);
#endif
}
/// <summary>
@@ -624,16 +617,8 @@ public:
/// <param name="method">The method.</param>
void Unbind(Signature method)
{
#if DELEGATE_USE_ATOMIC
FunctionType f(method);
Unbind(f);
#else
if (_functions == nullptr)
return;
FunctionType f(method);
ScopeLock lock(*_locker);
_functions->Remove(f);
#endif
}
/// <summary>
@@ -666,10 +651,11 @@ public:
Unbind(f);
}
#else
if (_functions == nullptr)
Data* data = (Data*)Platform::AtomicRead(&_data);
if (!data)
return;
ScopeLock lock(*_locker);
_functions->Remove(f);
ScopeLock lock(data->Locker);
data->Functions.Remove(f);
#endif
}
@@ -692,15 +678,11 @@ public:
Platform::AtomicStore((intptr volatile*)&bindings[i]._callee, 0);
}
#else
if (_functions == nullptr)
Data* data = (Data*)Platform::AtomicRead(&_data);
if (!data)
return;
ScopeLock lock(*_locker);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
{
if (i->Item._lambda)
i->Item.LambdaDtor();
}
_functions->Clear();
ScopeLock lock(data->Locker);
data->Functions.Clear();
#endif
}
@@ -710,22 +692,24 @@ public:
/// <returns>The bound functions count.</returns>
int32 Count() const
{
int32 result = 0;
#if DELEGATE_USE_ATOMIC
int32 count = 0;
const intptr size = Platform::AtomicRead((intptr volatile*)&_size);
FunctionType* bindings = (FunctionType*)Platform::AtomicRead((intptr volatile*)&_ptr);
for (intptr i = 0; i < size; i++)
{
if (Platform::AtomicRead((intptr volatile*)&bindings[i]._function) != 0)
count++;
result++;
}
return count;
#else
if (_functions == nullptr)
return 0;
ScopeLock lock(*_locker);
return _functions->Count();
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (data)
{
ScopeLock lock(data->Locker);
result = data->Functions.Count();
}
#endif
return result;
}
/// <summary>
@@ -736,10 +720,14 @@ public:
#if DELEGATE_USE_ATOMIC
return (int32)Platform::AtomicRead((intptr volatile*)&_size);
#else
if (_functions == nullptr)
return 0;
ScopeLock lock(*_locker);
return _functions->Capacity();
int32 result = 0;
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (data)
{
ScopeLock lock(data->Locker);
result = data->Functions.Capacity();
}
return result;
#endif
}
@@ -759,10 +747,14 @@ public:
}
return false;
#else
if (_functions == nullptr)
return false;
ScopeLock lock(*_locker);
return _functions->Count() > 0;
bool result = false;
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (data)
{
ScopeLock lock(data->Locker);
result = data->Functions.Count() != 0;
}
return result;
#endif
}
@@ -791,18 +783,13 @@ public:
}
}
#else
if (_functions == nullptr)
return 0;
ScopeLock lock(*_locker);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (data)
{
if (i->Item._function != nullptr)
ScopeLock lock(data->Locker);
for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i)
{
buffer[count]._function = (StubSignature)i->Item._function;
buffer[count]._callee = (void*)i->Item._callee;
buffer[count]._lambda = (typename FunctionType::Lambda*)i->Item._lambda;
if (buffer[count]._lambda)
buffer[count].LambdaCtor();
new(buffer + count) FunctionType((const FunctionType&)i->Item);
count++;
}
}
@@ -828,15 +815,15 @@ public:
++bindings;
}
#else
if (_functions == nullptr)
Data* data = (Data*)Platform::AtomicRead((intptr volatile*)&_data);
if (!data)
return;
ScopeLock lock(*_locker);
for (auto i = _functions->Begin(); i.IsNotEnd(); ++i)
ScopeLock lock(data->Locker);
for (auto i = data->Functions.Begin(); i.IsNotEnd(); ++i)
{
auto function = (StubSignature)(i->Item._function);
auto callee = (void*)(i->Item._callee);
if (function != nullptr)
function(callee, Forward<Params>(params)...);
const FunctionType& item = i->Item;
ASSERT_LOW_LAYER(item._function);
item._function(item._callee, Forward<Params>(params)...);
}
#endif
}
+13 -3
View File
@@ -728,9 +728,7 @@ namespace Math
/// <summary>
/// Returns value based on comparand. The main purpose of this function is to avoid branching based on floating point comparison which can be avoided via compiler intrinsics.
/// </summary>
/// <remarks>
/// Please note that this doesn't define what happens in the case of NaNs as there might be platform specific differences.
/// </remarks>
/// <remarks>Please note that this doesn't define what happens in the case of NaNs as there might be platform specific differences.</remarks>
/// <param name="comparand">Comparand the results are based on.</param>
/// <param name="valueGEZero">The result value if comparand >= 0.</param>
/// <param name="valueLTZero">The result value if comparand < 0.</param>
@@ -891,6 +889,18 @@ namespace Math
return Lerp<T>(a, b, alpha < 0.5f ? InterpCircularIn(0.f, 1.f, alpha * 2.f) * 0.5f : InterpCircularOut(0.f, 1.f, alpha * 2.f - 1.f) * 0.5f + 0.5f);
}
/// <summary>
/// Ping pongs the value <paramref name="t"/>, so that it is never larger than <paramref name="length"/> and never smaller than 0.
/// </summary>
/// <param name="t"></param>
/// <param name="length"></param>
/// <returns></returns>
template<class T>
static FORCE_INLINE T PingPong(const T& t, T length)
{
return length - Abs(Repeat(t, length * 2.0f) - length);
}
// Rotates position about the given axis by the given angle, in radians, and returns the offset to position
Vector3 FLAXENGINE_API RotateAboutAxis(const Vector3& normalizedRotationAxis, float angle, const Vector3& positionOnAxis, const Vector3& position);
+1 -2
View File
@@ -382,9 +382,8 @@ void Quaternion::GetRotationFromTo(const Float3& from, const Float3& to, Quatern
v0.Normalize();
v1.Normalize();
const float d = Float3::Dot(v0, v1);
// If dot == 1, vectors are the same
const float d = Float3::Dot(v0, v1);
if (d >= 1.0f)
{
result = Identity;
+109
View File
@@ -1077,6 +1077,115 @@ namespace FlaxEngine
}
}
/// <summary>
/// Gets the shortest arc quaternion to rotate this vector to the destination vector.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>
/// <param name="result">The result.</param>
/// <param name="fallbackAxis">The fallback axis.</param>
public static void GetRotationFromTo(ref Float3 from, ref Float3 to, out Quaternion result, ref Float3 fallbackAxis)
{
// Based on Stan Melax's article in Game Programming Gems
Float3 v0 = from;
Float3 v1 = to;
v0.Normalize();
v1.Normalize();
// If dot == 1, vectors are the same
float d = Float3.Dot(ref v0, ref v1);
if (d >= 1.0f)
{
result = Identity;
return;
}
if (d < 1e-6f - 1.0f)
{
if (fallbackAxis != Float3.Zero)
{
// Rotate 180 degrees about the fallback axis
RotationAxis(ref fallbackAxis, Mathf.Pi, out result);
}
else
{
// Generate an axis
Float3 axis = Float3.Cross(Float3.UnitX, from);
if (axis.LengthSquared < Mathf.Epsilon) // Pick another if colinear
axis = Float3.Cross(Float3.UnitY, from);
axis.Normalize();
RotationAxis(ref axis, Mathf.Pi, out result);
}
}
else
{
float s = Mathf.Sqrt((1 + d) * 2);
float invS = 1 / s;
Float3.Cross(ref v0, ref v1, out var c);
result.X = c.X * invS;
result.Y = c.Y * invS;
result.Z = c.Z * invS;
result.W = s * 0.5f;
result.Normalize();
}
}
/// <summary>
/// Gets the shortest arc quaternion to rotate this vector to the destination vector.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>
/// <param name="fallbackAxis">The fallback axis.</param>
/// <returns>The rotation.</returns>
public static Quaternion GetRotationFromTo(Float3 from, Float3 to, Float3 fallbackAxis)
{
GetRotationFromTo(ref from, ref to, out var result, ref fallbackAxis);
return result;
}
/// <summary>
/// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>
/// <param name="result">The result.</param>
public static void FindBetween(ref Float3 from, ref Float3 to, out Quaternion result)
{
// http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final
float normFromNormTo = Mathf.Sqrt(from.LengthSquared * to.LengthSquared);
if (normFromNormTo < Mathf.Epsilon)
{
result = Identity;
return;
}
float w = normFromNormTo + Float3.Dot(from, to);
if (w < 1.0-6f * normFromNormTo)
{
result = Mathf.Abs(from.X) > Mathf.Abs(from.Z)
? new Quaternion(-from.Y, from.X, 0.0f, 0.0f)
: new Quaternion(0.0f, -from.Z, from.Y, 0.0f);
}
else
{
Float3 cross = Float3.Cross(from, to);
result = new Quaternion(cross.X, cross.Y, cross.Z, w);
}
result.Normalize();
}
/// <summary>
/// Gets the quaternion that will rotate vector from into vector to, around their plan perpendicular axis.The input vectors don't need to be normalized.
/// </summary>
/// <param name="from">The source vector.</param>
/// <param name="to">The destination vector.</param>
/// <returns>The rotation.</returns>
public static Quaternion FindBetween(Float3 from, Float3 to)
{
FindBetween(ref from, ref to, out var result);
return result;
}
/// <summary>
/// Creates a left-handed spherical billboard that rotates around a specified object position.
/// </summary>
@@ -13,9 +13,7 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
@@ -23,9 +21,7 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Double2Converter : TypeConverter
internal class Double2Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Double2(double.Parse(v[0], culture), double.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Double3Converter : TypeConverter
internal class Double3Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Double3(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Double4Converter : TypeConverter
internal class Double4Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Double4(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture), double.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Float2Converter : TypeConverter
internal class Float2Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Float2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Float3Converter : TypeConverter
internal class Float3Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Float3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
@@ -7,15 +7,13 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Float4Converter : TypeConverter
internal class VectorConverter : TypeConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
@@ -23,18 +21,32 @@ namespace FlaxEngine.TypeConverters
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
internal static string[] GetParts(string str)
{
string[] v = str.Split(',');
if (v.Length == 1)
{
// When converting from ToString()
v = str.Split(' ');
for (int i = 0; i < v.Length; i++)
v[i] = v[i].Substring(v[i].IndexOf(':') + 1);
}
return v;
}
}
internal class Float4Converter : VectorConverter
{
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Float4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Int2Converter : TypeConverter
internal class Int2Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Int2(int.Parse(v[0], culture), int.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Int3Converter : TypeConverter
internal class Int3Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Int3(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Int4Converter : TypeConverter
internal class Int4Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Int4(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture), int.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class QuaternionConverter : TypeConverter
internal class QuaternionConverter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Quaternion(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Vector2Converter : TypeConverter
internal class Vector2Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Vector2(float.Parse(v[0], culture), float.Parse(v[1], culture));
}
return base.ConvertFrom(context, culture, value);
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Vector3Converter : TypeConverter
internal class Vector3Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Vector3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
}
return base.ConvertFrom(context, culture, value);
@@ -7,34 +7,14 @@ using System.Globalization;
namespace FlaxEngine.TypeConverters
{
internal class Vector4Converter : TypeConverter
internal class Vector4Converter : VectorConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
/// <inheritdoc />
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
string[] v = str.Split(',');
string[] v = GetParts(str);
return new Vector4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
}
return base.ConvertFrom(context, culture, value);
+6
View File
@@ -646,6 +646,12 @@ inline Vector2Base<T> operator/(typename TOtherFloat<T>::Type a, const Vector2Ba
return Vector2Base<T>(a) / b;
}
template<typename T>
inline uint32 GetHash(const Vector2Base<T>& key)
{
return (*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y;
}
namespace Math
{
template<typename T>
+6
View File
@@ -977,6 +977,12 @@ inline Vector3Base<T> operator/(typename TOtherFloat<T>::Type a, const Vector3Ba
return Vector3Base<T>(a) / b;
}
template<typename T>
inline uint32 GetHash(const Vector3Base<T>& key)
{
return (((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z;
}
namespace Math
{
template<typename T>
+6
View File
@@ -552,6 +552,12 @@ inline Vector4Base<T> operator/(typename TOtherFloat<T>::Type a, const Vector4Ba
return Vector4Base<T>(a) / b;
}
template<typename T>
inline uint32 GetHash(const Vector4Base<T>& key)
{
return (((((*(uint32*)&key.X * 397) ^ *(uint32*)&key.Y) * 397) ^ *(uint32*)&key.Z) * 397) ^*(uint32*)&key.W;
}
namespace Math
{
template<typename T>
+20 -19
View File
@@ -12,6 +12,8 @@ template<int Capacity>
class FixedAllocation
{
public:
enum { HasSwap = false };
template<typename T>
class Data
{
@@ -43,14 +45,14 @@ public:
return Capacity;
}
FORCE_INLINE void Allocate(uint64 capacity)
FORCE_INLINE void Allocate(int32 capacity)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(capacity <= Capacity);
#endif
}
FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(capacity <= Capacity);
@@ -61,12 +63,9 @@ public:
{
}
FORCE_INLINE void Swap(Data& other)
void Swap(Data& other)
{
byte tmp[Capacity * sizeof(T)];
Platform::MemoryCopy(tmp, _data, Capacity * sizeof(T));
Platform::MemoryCopy(_data, other._data, Capacity * sizeof(T));
Platform::MemoryCopy(other._data, tmp, Capacity * sizeof(T));
// Not supported
}
};
};
@@ -77,6 +76,8 @@ public:
class HeapAllocation
{
public:
enum { HasSwap = true };
template<typename T>
class Data
{
@@ -120,12 +121,15 @@ public:
capacity |= capacity >> 4;
capacity |= capacity >> 8;
capacity |= capacity >> 16;
capacity = (capacity + 1) * 2;
uint64 capacity64 = (uint64)(capacity + 1) * 2;
if (capacity64 > MAX_int32)
capacity64 = MAX_int32;
capacity = (int32)capacity64;
}
return capacity;
}
FORCE_INLINE void Allocate(uint64 capacity)
FORCE_INLINE void Allocate(int32 capacity)
{
#if ENABLE_ASSERTION_LOW_LAYERS
ASSERT(!_data);
@@ -137,7 +141,7 @@ public:
#endif
}
FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
T* newData = capacity != 0 ? (T*)Allocator::Allocate(capacity * sizeof(T)) : nullptr;
#if !BUILD_RELEASE
@@ -176,6 +180,8 @@ template<int Capacity, typename OtherAllocator = HeapAllocation>
class InlinedAllocation
{
public:
enum { HasSwap = false };
template<typename T>
class Data
{
@@ -210,7 +216,7 @@ public:
return minCapacity <= Capacity ? Capacity : _other.CalculateCapacityGrow(capacity, minCapacity);
}
FORCE_INLINE void Allocate(uint64 capacity)
FORCE_INLINE void Allocate(int32 capacity)
{
if (capacity > Capacity)
{
@@ -219,7 +225,7 @@ public:
}
}
FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount)
FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
// Check if the new allocation will fit into inlined storage
if (capacity <= Capacity)
@@ -264,14 +270,9 @@ public:
}
}
FORCE_INLINE void Swap(Data& other)
void Swap(Data& other)
{
byte tmp[Capacity * sizeof(T)];
Platform::MemoryCopy(tmp, _data, Capacity * sizeof(T));
Platform::MemoryCopy(_data, other._data, Capacity * sizeof(T));
Platform::MemoryCopy(other._data, tmp, Capacity * sizeof(T));
::Swap(_useOther, other._useOther);
_other.Swap(other._other);
// Not supported
}
};
};
@@ -5,6 +5,7 @@
#include "Collections/Dictionary.h"
#include "Engine/Engine/Time.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Platform/CriticalSection.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ScriptingObject.h"
+6
View File
@@ -3,6 +3,7 @@
#pragma once
#include "String.h"
#include "StringView.h"
#include "Engine/Core/Collections/Array.h"
/// <summary>
@@ -138,6 +139,11 @@ public:
_data.Add(*str, str.Length());
return *this;
}
StringBuilder& Append(const StringView& str)
{
_data.Add(*str, str.Length());
return *this;
}
// Append int to the string
// @param val Value to append
+24
View File
@@ -327,6 +327,18 @@ public:
bool operator!=(const String& other) const;
public:
using StringViewBase::StartsWith;
FORCE_INLINE bool StartsWith(const StringView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::StartsWith(prefix, searchCase);
}
using StringViewBase::EndsWith;
FORCE_INLINE bool EndsWith(const StringView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::EndsWith(suffix, searchCase);
}
/// <summary>
/// Gets the left most given number of characters.
/// </summary>
@@ -511,6 +523,18 @@ public:
bool operator!=(const StringAnsi& other) const;
public:
using StringViewBase::StartsWith;
FORCE_INLINE bool StartsWith(const StringAnsiView& prefix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::StartsWith(prefix, searchCase);
}
using StringViewBase::EndsWith;
FORCE_INLINE bool EndsWith(const StringAnsiView& suffix, StringSearchCase searchCase = StringSearchCase::IgnoreCase) const
{
return StringViewBase::EndsWith(suffix, searchCase);
}
/// <summary>
/// Retrieves substring created from characters starting from startIndex to the String end.
/// </summary>
+57
View File
@@ -2821,7 +2821,10 @@ void Variant::Inline()
type = VariantType::Types::Vector4;
}
if (type != VariantType::Null)
{
ASSERT(sizeof(data) >= AsBlob.Length);
Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
}
}
if (type != VariantType::Null)
{
@@ -2912,6 +2915,60 @@ void Variant::Inline()
}
}
void Variant::InvertInline()
{
byte data[sizeof(Matrix)];
switch (Type.Type)
{
case VariantType::Bool:
case VariantType::Int:
case VariantType::Uint:
case VariantType::Int64:
case VariantType::Uint64:
case VariantType::Float:
case VariantType::Double:
case VariantType::Pointer:
case VariantType::String:
case VariantType::Float2:
case VariantType::Float3:
case VariantType::Float4:
case VariantType::Color:
#if !USE_LARGE_WORLDS
case VariantType::BoundingSphere:
case VariantType::BoundingBox:
case VariantType::Ray:
#endif
case VariantType::Guid:
case VariantType::Quaternion:
case VariantType::Rectangle:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Int16:
case VariantType::Uint16:
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
static_assert(sizeof(data) >= sizeof(AsData), "Invalid memory size.");
Platform::MemoryCopy(data, AsData, sizeof(AsData));
break;
#if USE_LARGE_WORLDS
case VariantType::BoundingSphere:
case VariantType::BoundingBox:
case VariantType::Ray:
#endif
case VariantType::Transform:
case VariantType::Matrix:
ASSERT(sizeof(data) >= AsBlob.Length);
Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
break;
default:
return; // Not used
}
SetType(VariantType(VariantType::Structure, InBuiltTypesTypeNames[Type.Type]));
CopyStructure(data);
}
Variant Variant::NewValue(const StringAnsiView& typeName)
{
Variant v;
+3
View File
@@ -372,6 +372,9 @@ public:
// Inlines potential value type into in-built format (eg. Vector3 stored as Structure, or String stored as ManagedObject).
void Inline();
// Inverts the inlined value from in-built format into generic storage (eg. Float3 from inlined format into Structure).
void InvertInline();
// Allocates the Variant of the specific type (eg. structure or object or value).
static Variant NewValue(const StringAnsiView& typeName);
+3 -3
View File
@@ -7,15 +7,15 @@ Version::Version(int32 major, int32 minor, int32 build, int32 revision)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
_build = Math::Max(build, 0);
_revision = Math::Max(revision, 0);
_build = Math::Max(build, -1);
_revision = Math::Max(revision, -1);
}
Version::Version(int32 major, int32 minor, int32 build)
{
_major = Math::Max(major, 0);
_minor = Math::Max(minor, 0);
_build = Math::Max(build, 0);
_build = Math::Max(build, -1);
_revision = -1;
}
+6 -2
View File
@@ -51,10 +51,14 @@ namespace Utilities
int32 i = 0;
double dblSUnits = static_cast<double>(units);
for (; static_cast<uint64>(units / static_cast<double>(divider)) > 0; i++, units /= divider)
dblSUnits = units / static_cast<double>(divider);
dblSUnits = (double)units / (double)divider;
if (i >= sizes.Length())
i = 0;
return String::Format(TEXT("{0} {1}"), RoundTo2DecimalPlaces(dblSUnits), sizes[i]);
String text = String::Format(TEXT("{}"), RoundTo2DecimalPlaces(dblSUnits));
const int32 dot = text.FindLast('.');
if (dot != -1)
text = text.Left(dot + 3);
return String::Format(TEXT("{0} {1}"), text, sizes[i]);
}
// Converts size of the file (in bytes) to the best fitting string
+3
View File
@@ -696,12 +696,15 @@ void* DebugDraw::AllocateContext()
void DebugDraw::FreeContext(void* context)
{
ASSERT(context);
Memory::DestructItem((DebugDrawContext*)context);
Allocator::Free(context);
}
void DebugDraw::UpdateContext(void* context, float deltaTime)
{
if (!context)
context = &GlobalContext;
((DebugDrawContext*)context)->DebugDrawDefault.Update(deltaTime);
((DebugDrawContext*)context)->DebugDrawDepthTest.Update(deltaTime);
}
+7 -10
View File
@@ -20,7 +20,6 @@
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Threading/ThreadRegistry.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/JsonAsset.h"
@@ -327,14 +326,6 @@ void Engine::OnUpdate()
// Update services
EngineService::OnUpdate();
#ifdef USE_NETCORE
// Force GC to run in background periodically to avoid large blocking collections causing hitches
if (Time::Update.TicksCount % 60 == 0)
{
MCore::GC::Collect(MCore::GC::MaxGeneration(), MGCCollectionMode::Forced, false, false);
}
#endif
}
void Engine::OnLateUpdate()
@@ -531,7 +522,13 @@ void EngineImpl::InitLog()
LOG(Info, "Compiled for Dev Environment");
#endif
LOG(Info, "Version " FLAXENGINE_VERSION_TEXT);
LOG(Info, "Compiled: {0} {1}", TEXT(__DATE__), TEXT(__TIME__));
const Char* cpp = TEXT("?");
if (__cplusplus == 202101L) cpp = TEXT("C++23");
else if (__cplusplus == 202002L) cpp = TEXT("C++20");
else if (__cplusplus == 201703L) cpp = TEXT("C++17");
else if (__cplusplus == 201402L) cpp = TEXT("C++14");
else if (__cplusplus == 201103L) cpp = TEXT("C++11");
LOG(Info, "Compiled: {0} {1} {2}", TEXT(__DATE__), TEXT(__TIME__), cpp);
#ifdef _MSC_VER
const String mcsVer = StringUtils::ToString(_MSC_FULL_VER);
LOG(Info, "Compiled with Visual C++ {0}.{1}.{2}.{3:0^2d}", mcsVer.Substring(0, 2), mcsVer.Substring(2, 2), mcsVer.Substring(4, 5), _MSC_BUILD);
@@ -1022,6 +1022,8 @@ namespace FlaxEngine.Interop
pair.Value.Free();
classAttributesCacheCollectible.Clear();
FlaxEngine.Json.JsonSerializer.ResetCache();
// Unload the ALC
bool unloading = true;
scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; };
@@ -1269,6 +1271,9 @@ namespace FlaxEngine.Interop
case Type _ when type == typeof(IntPtr):
monoType = MTypes.Ptr;
break;
case Type _ when type.IsPointer:
monoType = MTypes.Ptr;
break;
case Type _ when type.IsEnum:
monoType = MTypes.Enum;
break;
+54 -27
View File
@@ -278,44 +278,70 @@ namespace FlaxEngine.Interop
if (typeCache.TryGetValue(typeName, out Type type))
return type;
type = Type.GetType(typeName, ResolveAssemblyByName, null);
type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null)
{
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{
type = assembly.GetType(typeName);
if (type != null)
break;
}
}
type = ResolveSlow(typeName);
if (type == null)
{
string oldTypeName = typeName;
string fullTypeName = typeName;
typeName = typeName.Substring(0, typeName.IndexOf(','));
type = Type.GetType(typeName, ResolveAssemblyByName, null);
type = Type.GetType(typeName, ResolveAssembly, null);
if (type == null)
{
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{
type = assembly.GetType(typeName);
if (type != null)
break;
}
}
typeName = oldTypeName;
type = ResolveSlow(typeName);
typeName = fullTypeName;
}
typeCache.Add(typeName, type);
return type;
static Type ResolveSlow(string typeName)
{
foreach (var assembly in scriptingAssemblyLoadContext.Assemblies)
{
var type = assembly.GetType(typeName);
if (type != null)
return type;
}
return null;
}
static Assembly ResolveAssembly(AssemblyName name) => ResolveScriptingAssemblyByName(name, allowPartial: false);
}
private static Assembly ResolveAssemblyByName(AssemblyName assemblyName)
/// <summary>Find <paramref name="assemblyName"/> among the scripting assemblies.</summary>
/// <param name="assemblyName">The name to find</param>
/// <param name="allowPartial">If true, partial names should be allowed to be resolved.</param>
/// <returns>The resolved assembly, or null if none could be found.</returns>
internal static Assembly ResolveScriptingAssemblyByName(AssemblyName assemblyName, bool allowPartial = false)
{
foreach (Assembly assembly in scriptingAssemblyLoadContext.Assemblies)
if (assembly.GetName() == assemblyName)
var lc = scriptingAssemblyLoadContext;
if (lc is null)
return null;
foreach (Assembly assembly in lc.Assemblies)
{
var curName = assembly.GetName();
if (curName == assemblyName)
return assembly;
}
if (allowPartial) // Check partial names if full name isn't found
{
string partialName = assemblyName.Name;
foreach (Assembly assembly in lc.Assemblies)
{
var curName = assembly.GetName();
if (curName.Name == partialName)
return assembly;
}
}
return null;
}
@@ -1109,7 +1135,7 @@ namespace FlaxEngine.Interop
marshallers[i](fields[i], offsets[i], ref managedValue, fieldPtr, out int fieldSize);
fieldPtr += fieldSize;
}
Assert.IsTrue((fieldPtr - nativePtr) <= Unsafe.SizeOf<T>());
//Assert.IsTrue((fieldPtr - nativePtr) <= GetTypeSize(typeof(T)));
}
internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef)
@@ -1156,7 +1182,7 @@ namespace FlaxEngine.Interop
marshallers[i](fields[i], offsets[i], ref managedValue, nativePtr, out int fieldSize);
nativePtr += fieldSize;
}
Assert.IsTrue((nativePtr - fieldPtr) <= Unsafe.SizeOf<T>());
//Assert.IsTrue((nativePtr - fieldPtr) <= GetTypeSize(typeof(T)));
}
internal static void ToNative(ref T managedValue, IntPtr nativePtr)
@@ -1302,7 +1328,8 @@ namespace FlaxEngine.Interop
#if !USE_AOT
internal bool TryGetDelegate(out Invoker.MarshalAndInvokeDelegate outDeleg, out object outDelegInvoke)
{
if (invokeDelegate == null)
// Skip using in-built delegate for value types (eg. Transform) to properly handle instance value passing to method
if (invokeDelegate == null && !method.DeclaringType.IsValueType)
{
List<Type> methodTypes = new List<Type>();
if (!method.IsStatic)
@@ -1553,7 +1580,7 @@ namespace FlaxEngine.Interop
private static IntPtr PinValue<T>(T value) where T : struct
{
// Store the converted value in unmanaged memory so it will not be relocated by the garbage collector.
int size = Unsafe.SizeOf<T>();
int size = GetTypeSize(typeof(T));
uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length;
ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index];
if (alloc.size < size)
+1 -1
View File
@@ -46,7 +46,7 @@ API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FoliageType : public ScriptingOb
DECLARE_SCRIPTING_TYPE_NO_SPAWN(FoliageType);
friend Foliage;
private:
int8 _isReady : 1;
uint8 _isReady : 1;
public:
/// <summary>
@@ -26,12 +26,12 @@ public:
, _srcResource(src)
, _dstResource(dst)
{
_srcResource.OnUnload.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceUnload>(this);
_dstResource.OnUnload.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceUnload>(this);
_srcResource.Released.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceReleased>(this);
_dstResource.Released.Bind<GPUCopyResourceTask, &GPUCopyResourceTask::OnResourceReleased>(this);
}
private:
void OnResourceUnload(GPUResourceReference* ref)
void OnResourceReleased()
{
Cancel();
}
@@ -47,14 +47,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
if (_srcResource.IsMissing() || _dstResource.IsMissing())
if (!_srcResource || !_dstResource)
return Result::MissingResources;
context->GPU->CopyResource(_dstResource, _srcResource);
return Result::Ok;
}
void OnEnd() override
{
_srcResource.Unlink();
@@ -31,12 +31,12 @@ public:
, _srcSubresource(srcSubresource)
, _dstSubresource(dstSubresource)
{
_srcResource.OnUnload.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceUnload>(this);
_dstResource.OnUnload.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceUnload>(this);
_srcResource.Released.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceReleased>(this);
_dstResource.Released.Bind<GPUCopySubresourceTask, &GPUCopySubresourceTask::OnResourceReleased>(this);
}
private:
void OnResourceUnload(GPUResourceReference* ref)
void OnResourceReleased()
{
Cancel();
}
@@ -52,14 +52,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
if (_srcResource.IsMissing() || _dstResource.IsMissing())
if (!_srcResource || !_dstResource)
return Result::MissingResources;
context->GPU->CopySubresource(_dstResource, _dstSubresource, _srcResource, _srcSubresource);
return Result::Ok;
}
void OnEnd() override
{
_srcResource.Unlink();
@@ -31,7 +31,7 @@ public:
, _buffer(buffer)
, _offset(offset)
{
_buffer.OnUnload.Bind<GPUUploadBufferTask, &GPUUploadBufferTask::OnResourceUnload>(this);
_buffer.Released.Bind<GPUUploadBufferTask, &GPUUploadBufferTask::OnResourceReleased>(this);
if (copyData)
_data.Copy(data);
@@ -40,7 +40,7 @@ public:
}
private:
void OnResourceUnload(BufferReference* ref)
void OnResourceReleased()
{
Cancel();
}
@@ -56,14 +56,11 @@ protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
if (_buffer.IsMissing())
if (!_buffer)
return Result::MissingResources;
context->GPU->UpdateBuffer(_buffer, _data.Get(), _data.Length(), _offset);
return Result::Ok;
}
void OnEnd() override
{
_buffer.Unlink();
@@ -35,7 +35,7 @@ public:
, _rowPitch(rowPitch)
, _slicePitch(slicePitch)
{
_texture.OnUnload.Bind<GPUUploadTextureMipTask, &GPUUploadTextureMipTask::OnResourceUnload>(this);
_texture.Released.Bind<GPUUploadTextureMipTask, &GPUUploadTextureMipTask::OnResourceReleased>(this);
if (copyData)
_data.Copy(data);
@@ -44,7 +44,7 @@ public:
}
private:
void OnResourceUnload(GPUTextureReference* ref)
void OnResourceReleased()
{
Cancel();
}
+7 -7
View File
@@ -591,37 +591,37 @@ API_ENUM() enum class Quality : byte
API_ENUM() enum class MaterialPostFxLocation : byte
{
/// <summary>
/// The 'after' post processing pass using LDR input frame.
/// Render the material after the post processing pass using *LDR* input frame.
/// </summary>
AfterPostProcessingPass = 0,
/// <summary>
/// The 'before' post processing pass using HDR input frame.
/// Render the material before the post processing pass using *HDR* input frame.
/// </summary>
BeforePostProcessingPass = 1,
/// <summary>
/// The 'before' forward pass but after GBuffer with HDR input frame.
/// Render the material before the forward pass but after *GBuffer* with *HDR* input frame.
/// </summary>
BeforeForwardPass = 2,
/// <summary>
/// The 'after' custom post effects.
/// Render the material after custom post effects (scripted).
/// </summary>
AfterCustomPostEffects = 3,
/// <summary>
/// The 'before' Reflections pass. After the Light pass. Can be used to implement a custom light types that accumulate lighting to the light buffer.
/// Render the material before the reflections pass but after the lighting pass using *HDR* input frame. It can be used to implement a custom light types that accumulate lighting to the light buffer.
/// </summary>
BeforeReflectionsPass = 4,
/// <summary>
/// The 'after' AA filter pass. Rendering is done to the output backbuffer.
/// Render the material after anti-aliasing into the output backbuffer.
/// </summary>
AfterAntiAliasingPass = 5,
/// <summary>
/// The 'after' forward pass but before any post processing.
/// Render the material after the forward pass but before any post processing.
/// </summary>
AfterForwardPass = 6,
+36
View File
@@ -3,6 +3,7 @@
#include "GPUDevice.h"
#include "RenderTargetPool.h"
#include "GPUPipelineState.h"
#include "GPUResourceProperty.h"
#include "GPUSwapChain.h"
#include "RenderTask.h"
#include "RenderTools.h"
@@ -25,6 +26,39 @@
#include "Engine/Renderer/RenderList.h"
#include "Engine/Scripting/Enums.h"
GPUResourcePropertyBase::~GPUResourcePropertyBase()
{
const auto e = _resource;
if (e)
{
_resource = nullptr;
e->Releasing.Unbind<GPUResourcePropertyBase, &GPUResourcePropertyBase::OnReleased>(this);
}
}
void GPUResourcePropertyBase::OnSet(GPUResource* resource)
{
auto e = _resource;
if (e != resource)
{
if (e)
e->Releasing.Unbind<GPUResourcePropertyBase, &GPUResourcePropertyBase::OnReleased>(this);
_resource = e = resource;
if (e)
e->Releasing.Bind<GPUResourcePropertyBase, &GPUResourcePropertyBase::OnReleased>(this);
}
}
void GPUResourcePropertyBase::OnReleased()
{
auto e = _resource;
if (e)
{
_resource = nullptr;
e->Releasing.Unbind<GPUResourcePropertyBase, &GPUResourcePropertyBase::OnReleased>(this);
}
}
GPUPipelineState* GPUPipelineState::Spawn(const SpawnParams& params)
{
return GPUDevice::Instance->CreatePipelineState();
@@ -313,6 +347,8 @@ bool GPUDevice::Init()
_res->TasksManager.SetExecutor(CreateTasksExecutor());
LOG(Info, "Total graphics memory: {0}", Utilities::BytesToText(TotalGraphicsMemory));
if (!Limits.HasCompute)
LOG(Warning, "Compute Shaders are not supported");
return false;
}
+59 -83
View File
@@ -8,28 +8,39 @@
/// <summary>
/// GPU Resource container utility object.
/// </summary>
template<typename T = GPUResource>
class GPUResourceProperty
class FLAXENGINE_API GPUResourcePropertyBase
{
private:
T* _resource;
protected:
GPUResource* _resource = nullptr;
private:
// Disable copy actions
GPUResourceProperty(const GPUResourceProperty& other) = delete;
public:
NON_COPYABLE(GPUResourcePropertyBase);
GPUResourcePropertyBase() = default;
~GPUResourcePropertyBase();
public:
/// <summary>
/// Action fired when resource gets unloaded (reference gets cleared bu async tasks should stop execution).
/// Action fired when resource gets released (reference gets cleared bu async tasks should stop execution).
/// </summary>
Delegate<GPUResourceProperty*> OnUnload;
Action Released;
protected:
void OnSet(GPUResource* resource);
void OnReleased();
};
/// <summary>
/// GPU Resource container utility object.
/// </summary>
template<typename T = GPUResource>
class GPUResourceProperty : public GPUResourcePropertyBase
{
public:
/// <summary>
/// Initializes a new instance of the <see cref="GPUResourceProperty"/> class.
/// </summary>
GPUResourceProperty()
: _resource(nullptr)
{
}
@@ -38,9 +49,37 @@ public:
/// </summary>
/// <param name="resource">The resource.</param>
GPUResourceProperty(T* resource)
: _resource(nullptr)
{
Set(resource);
OnSet(resource);
}
/// <summary>
/// Initializes a new instance of the <see cref="GPUResourceProperty"/> class.
/// </summary>
/// <param name="other">The other value.</param>
GPUResourceProperty(const GPUResourceProperty& other)
{
OnSet(other.Get());
}
/// <summary>
/// Initializes a new instance of the <see cref="GPUResourceProperty"/> class.
/// </summary>
/// <param name="other">The other value.</param>
GPUResourceProperty(GPUResourceProperty&& other)
{
OnSet(other.Get());
other.OnSet(nullptr);
}
GPUResourceProperty& operator=(GPUResourceProperty&& other)
{
if (&other != this)
{
OnSet(other._resource);
other.OnSet(nullptr);
}
return *this;
}
/// <summary>
@@ -48,13 +87,6 @@ public:
/// </summary>
~GPUResourceProperty()
{
// Check if object has been binded
if (_resource)
{
// Unlink
_resource->Releasing.template Unbind<GPUResourceProperty, &GPUResourceProperty::onResourceUnload>(this);
_resource = nullptr;
}
}
public:
@@ -63,43 +95,34 @@ public:
return Get() == other;
}
FORCE_INLINE bool operator==(GPUResourceProperty& other) const
FORCE_INLINE bool operator==(const GPUResourceProperty& other) const
{
return Get() == other.Get();
}
GPUResourceProperty& operator=(const GPUResourceProperty& other)
{
if (this != &other)
Set(other.Get());
return *this;
}
FORCE_INLINE GPUResourceProperty& operator=(T& other)
{
Set(&other);
OnSet(&other);
return *this;
}
FORCE_INLINE GPUResourceProperty& operator=(T* other)
{
Set(other);
OnSet(other);
return *this;
}
/// <summary>
/// Implicit conversion to GPU Resource
/// </summary>
/// <returns>Resource</returns>
FORCE_INLINE operator T*() const
{
return _resource;
return (T*)_resource;
}
/// <summary>
/// Implicit conversion to resource
/// </summary>
/// <returns>True if resource has been binded, otherwise false</returns>
FORCE_INLINE operator bool() const
{
return _resource != nullptr;
@@ -108,37 +131,17 @@ public:
/// <summary>
/// Implicit conversion to resource
/// </summary>
/// <returns>Resource</returns>
FORCE_INLINE T* operator->() const
{
return _resource;
return (T*)_resource;
}
/// <summary>
/// Gets linked resource
/// </summary>
/// <returns>Resource</returns>
FORCE_INLINE T* Get() const
{
return _resource;
}
/// <summary>
/// Checks if resource has been binded
/// </summary>
/// <returns>True if resource has been binded, otherwise false</returns>
FORCE_INLINE bool IsBinded() const
{
return _resource != nullptr;
}
/// <summary>
/// Checks if resource is missing
/// </summary>
/// <returns>True if resource is missing, otherwise false</returns>
FORCE_INLINE bool IsMissing() const
{
return _resource == nullptr;
return (T*)_resource;
}
public:
@@ -148,19 +151,7 @@ public:
/// <param name="value">Value to assign</param>
void Set(T* value)
{
if (_resource != value)
{
// Remove reference from the old one
if (_resource)
_resource->Releasing.template Unbind<GPUResourceProperty, &GPUResourceProperty::onResourceUnload>(this);
// Change referenced object
_resource = value;
// Add reference to the new one
if (_resource)
_resource->Releasing.template Bind<GPUResourceProperty, &GPUResourceProperty::onResourceUnload>(this);
}
OnSet(value);
}
/// <summary>
@@ -168,22 +159,7 @@ public:
/// </summary>
void Unlink()
{
if (_resource)
{
// Remove reference from the old one
_resource->Releasing.template Unbind<GPUResourceProperty, &GPUResourceProperty::onResourceUnload>(this);
_resource = nullptr;
}
}
private:
void onResourceUnload()
{
if (_resource)
{
_resource = nullptr;
OnUnload(this);
}
OnSet(nullptr);
}
};
+2 -1
View File
@@ -67,7 +67,8 @@ void GraphicsSettings::Apply()
Graphics::AllowCSMBlending = AllowCSMBlending;
Graphics::GlobalSDFQuality = GlobalSDFQuality;
Graphics::GIQuality = GIQuality;
Graphics::PostProcessSettings = PostProcessSettings;
Graphics::PostProcessSettings = ::PostProcessSettings();
Graphics::PostProcessSettings.BlendWith(PostProcessSettings, 1.0f);
}
void Graphics::DisposeDevice()
@@ -3,7 +3,6 @@
#pragma once
#include "../Enums.h"
#include "Engine/Core/Math/Math.h"
/// <summary>
/// Material domain type. Material domain defines the target usage of the material shader.
@@ -86,7 +85,7 @@ API_ENUM() enum class MaterialBlendMode : byte
API_ENUM() enum class MaterialShadingModel : byte
{
/// <summary>
/// The unlit material. Emissive channel is used as an output color. Can perform custom lighting operations or just glow. Won't be affected by the lighting pipeline.
/// The unlit material. The emissive channel is used as an output color. Can perform custom lighting operations or just glow. Won't be affected by the lighting pipeline.
/// </summary>
Unlit = 0,
@@ -96,7 +95,7 @@ API_ENUM() enum class MaterialShadingModel : byte
Lit = 1,
/// <summary>
/// The subsurface material. Intended for materials like vax or skin that need light scattering to transport simulation through the object.
/// The subsurface material. Intended for materials like wax or skin that need light scattering to transport simulation through the object.
/// </summary>
Subsurface = 2,
@@ -366,12 +365,12 @@ API_ENUM() enum class MaterialDecalBlendingMode : byte
API_ENUM() enum class MaterialTransparentLightingMode : byte
{
/// <summary>
/// Default directional lighting evaluated per-pixel at the material surface. Use it for semi-transparent surfaces - with both diffuse and specular lighting component active.
/// Default directional lighting evaluated per-pixel at the material surface. Use it for semi-transparent surfaces - with both diffuse and specular lighting components active.
/// </summary>
Surface = 0,
/// <summary>
/// Non-directional lighting evaluated per-pixel at material surface. Use it for volumetric objects such as smoke, rain or dust - only diffuse lighting term is active (no specular highlights).
/// Non-directional lighting evaluated per-pixel at material surface. Use it for volumetric objects such as smoke, rain or dust - only the diffuse lighting term is active (no specular highlights).
/// </summary>
SurfaceNonDirectional = 1,
};
+15
View File
@@ -90,6 +90,21 @@ public:
/// </summary>
Array<BlendShape> BlendShapes;
/// <summary>
/// Global translation for this mesh to be at it's local origin.
/// </summary>
Vector3 OriginTranslation = Vector3::Zero;
/// <summary>
/// Orientation for this mesh at it's local origin.
/// </summary>
Quaternion OriginOrientation = Quaternion::Identity;
/// <summary>
/// Meshes scaling.
/// </summary>
Vector3 Scaling = Vector3::One;
public:
/// <summary>
/// Determines whether this instance has any mesh data.
+29 -28
View File
@@ -5,6 +5,7 @@
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Core/Math/Vector4.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Content/SoftAssetReference.h"
#include "Engine/Core/ISerializable.h"
#include "Engine/Content/Assets/Texture.h"
#include "Engine/Content/Assets/MaterialBase.h"
@@ -446,13 +447,13 @@ API_STRUCT() struct FLAXENGINE_API BloomSettings : ISerializable
bool Enabled = true;
/// <summary>
/// Bloom effect strength. Value 0 disabled is, while higher values increase the effect.
/// Bloom effect strength. Set a value of 0 to disabled it, while higher values increase the effect.
/// </summary>
API_FIELD(Attributes="Limit(0, 20.0f, 0.01f), EditorOrder(1), PostProcessSetting((int)BloomSettingsOverride.Intensity)")
float Intensity = 1.0f;
/// <summary>
/// Minimum pixel brightness value to start blowing. Values below the threshold are skipped.
/// Minimum pixel brightness value to start blooming. Values below this threshold are skipped.
/// </summary>
API_FIELD(Attributes="Limit(0, 15.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)BloomSettingsOverride.Threshold)")
float Threshold = 3.0f;
@@ -850,7 +851,7 @@ API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable
/// The Lookup Table (LUT) used to perform color correction.
/// </summary>
API_FIELD(Attributes="DefaultValue(null), EditorOrder(22), PostProcessSetting((int)ColorGradingSettingsOverride.LutTexture)")
AssetReference<Texture> LutTexture;
SoftAssetReference<Texture> LutTexture;
/// <summary>
/// The LUT blending weight (normalized to range 0-1). Default is 1.0.
@@ -986,13 +987,13 @@ API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable
float MaxBrightness = 2.0f;
/// <summary>
/// The lower bound for the luminance histogram of the scene color. Value is in percent and limits the pixels below this brightness. Use values from range 60-80. Used only in AutomaticHistogram mode.
/// The lower bound for the luminance histogram of the scene color. This value is in percent and limits the pixels below this brightness. Use values in the range of 60-80. Used only in AutomaticHistogram mode.
/// </summary>
API_FIELD(Attributes="Limit(1, 99, 0.001f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.HistogramLowPercent)")
float HistogramLowPercent = 70.0f;
/// <summary>
/// The upper bound for the luminance histogram of the scene color. Value is in percent and limits the pixels above this brightness. Use values from range 80-95. Used only in AutomaticHistogram mode.
/// The upper bound for the luminance histogram of the scene color. This value is in percent and limits the pixels above this brightness. Use values in the range of 80-95. Used only in AutomaticHistogram mode.
/// </summary>
API_FIELD(Attributes="Limit(1, 99, 0.001f), EditorOrder(3), PostProcessSetting((int)EyeAdaptationSettingsOverride.HistogramHighPercent)")
float HistogramHighPercent = 98.0f;
@@ -1090,13 +1091,13 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable
Float3 VignetteColor = Float3(0, 0, 0.001f);
/// <summary>
/// Controls shape of the vignette. Values near 0 produce rectangle shape. Higher values result in round shape. The default value is 0.125.
/// Controls the shape of the vignette. Values near 0 produce a rectangular shape. Higher values result in a rounder shape. The default value is 0.125.
/// </summary>
API_FIELD(Attributes="Limit(0.0001f, 2.0f, 0.001f), EditorOrder(2), PostProcessSetting((int)CameraArtifactsSettingsOverride.VignetteShapeFactor)")
float VignetteShapeFactor = 0.125f;
/// <summary>
/// Intensity of the grain filter. Value 0 hides it. The default value is 0.005.
/// Intensity of the grain filter. A value of 0 hides it. The default value is 0.005.
/// </summary>
API_FIELD(Attributes="Limit(0.0f, 2.0f, 0.005f), EditorOrder(3), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainAmount)")
float GrainAmount = 0.006f;
@@ -1108,19 +1109,19 @@ API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable
float GrainParticleSize = 1.6f;
/// <summary>
/// Speed of the grain particles animation.
/// Speed of the grain particle animation.
/// </summary>
API_FIELD(Attributes="Limit(0.0f, 10.0f, 0.01f), EditorOrder(5), PostProcessSetting((int)CameraArtifactsSettingsOverride.GrainSpeed)")
float GrainSpeed = 1.0f;
/// <summary>
/// Controls chromatic aberration effect strength. Value 0 hides it.
/// Controls the chromatic aberration effect strength. A value of 0 hides it.
/// </summary>
API_FIELD(Attributes="Limit(0.0f, 1.0f, 0.01f), EditorOrder(6), PostProcessSetting((int)CameraArtifactsSettingsOverride.ChromaticDistortion)")
float ChromaticDistortion = 0.0f;
/// <summary>
/// Screen tint color (alpha channel defines the blending factor).
/// Screen tint color (the alpha channel defines the blending factor).
/// </summary>
API_FIELD(Attributes="DefaultValue(typeof(Color), \"0,0,0,0\"), EditorOrder(7), PostProcessSetting((int)CameraArtifactsSettingsOverride.ScreenFadeColor)")
Color ScreenFadeColor = Color::Transparent;
@@ -1226,7 +1227,7 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
LensFlaresSettingsOverride OverrideFlags = Override::None;
/// <summary>
/// Strength of the effect. Value 0 disabled it.
/// Strength of the effect. A value of 0 disables it.
/// </summary>
API_FIELD(Attributes="Limit(0, 10.0f, 0.01f), EditorOrder(0), PostProcessSetting((int)LensFlaresSettingsOverride.Intensity)")
float Intensity = 1.0f;
@@ -1277,10 +1278,10 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
/// Fullscreen lens dirt texture.
/// </summary>
API_FIELD(Attributes="DefaultValue(null), EditorOrder(8), PostProcessSetting((int)LensFlaresSettingsOverride.LensDirt)")
AssetReference<Texture> LensDirt;
SoftAssetReference<Texture> LensDirt;
/// <summary>
/// Fullscreen lens dirt intensity parameter. Allows to tune dirt visibility.
/// Fullscreen lens dirt intensity parameter. Allows tuning dirt visibility.
/// </summary>
API_FIELD(Attributes="Limit(0, 100, 0.01f), EditorOrder(9), PostProcessSetting((int)LensFlaresSettingsOverride.LensDirtIntensity)")
float LensDirtIntensity = 1.0f;
@@ -1289,13 +1290,13 @@ API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable
/// Custom lens color texture (1D) used for lens color spectrum.
/// </summary>
API_FIELD(Attributes="DefaultValue(null), EditorOrder(10), PostProcessSetting((int)LensFlaresSettingsOverride.LensColor)")
AssetReference<Texture> LensColor;
SoftAssetReference<Texture> LensColor;
/// <summary>
/// Custom lens star texture sampled by lens flares.
/// </summary>
API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)LensFlaresSettingsOverride.LensStar)")
AssetReference<Texture> LensStar;
SoftAssetReference<Texture> LensStar;
public:
/// <summary>
@@ -1418,13 +1419,13 @@ API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable
DepthOfFieldSettingsOverride OverrideFlags = Override::None;
/// <summary>
/// If checked, depth of field effect will be visible.
/// If checked, the depth of field effect will be visible.
/// </summary>
API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)DepthOfFieldSettingsOverride.Enabled)")
bool Enabled = false;
/// <summary>
/// The blur intensity in the out-of-focus areas. Allows reducing blur amount by scaling down the Gaussian Blur radius. Normalized to range 0-1.
/// The blur intensity in the out-of-focus areas. Allows reducing the blur amount by scaling down the Gaussian Blur radius. Normalized to range 0-1.
/// </summary>
API_FIELD(Attributes="Limit(0, 1, 0.01f), EditorOrder(1), PostProcessSetting((int)DepthOfFieldSettingsOverride.BlurStrength)")
float BlurStrength = 1.0f;
@@ -1478,7 +1479,7 @@ API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable
float BokehBrightness = 1.0f;
/// <summary>
/// Defines bokeh shapes type.
/// Defines the type of the bokeh shapes.
/// </summary>
API_FIELD(Attributes="EditorOrder(10), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehShape)")
BokehShapeType BokehShape = BokehShapeType::Octagon;
@@ -1487,22 +1488,22 @@ API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable
/// If BokehShape is set to Custom, then this texture will be used for the bokeh shapes. For best performance, use small, compressed, grayscale textures (for instance 32px).
/// </summary>
API_FIELD(Attributes="DefaultValue(null), EditorOrder(11), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehShapeCustom)")
AssetReference<Texture> BokehShapeCustom;
SoftAssetReference<Texture> BokehShapeCustom;
/// <summary>
/// The minimum pixel brightness to create bokeh. Pixels with lower brightness will be skipped.
/// The minimum pixel brightness to create the bokeh. Pixels with lower brightness will be skipped.
/// </summary>
API_FIELD(Attributes="Limit(0, 10000.0f, 0.01f), EditorOrder(12), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehBrightnessThreshold)")
float BokehBrightnessThreshold = 3.0f;
/// <summary>
/// Depth of Field bokeh shapes blur threshold.
/// Depth of Field bokeh shape blur threshold.
/// </summary>
API_FIELD(Attributes="Limit(0, 1.0f, 0.001f), EditorOrder(13), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehBlurThreshold)")
float BokehBlurThreshold = 0.05f;
/// <summary>
/// Controls bokeh shapes brightness falloff. Higher values reduce bokeh visibility.
/// Controls bokeh shape brightness falloff. Higher values reduce bokeh visibility.
/// </summary>
API_FIELD(Attributes="Limit(0, 2.0f, 0.001f), EditorOrder(14), PostProcessSetting((int)DepthOfFieldSettingsOverride.BokehFalloff)")
float BokehFalloff = 0.5f;
@@ -1574,25 +1575,25 @@ API_STRUCT() struct FLAXENGINE_API MotionBlurSettings : ISerializable
MotionBlurSettingsOverride OverrideFlags = Override::None;
/// <summary>
/// If checked, motion blur effect will be rendered.
/// If checked, the motion blur effect will be rendered.
/// </summary>
API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)MotionBlurSettingsOverride.Enabled)")
bool Enabled = true;
/// <summary>
/// The blur effect strength. Value 0 disabled is, while higher values increase the effect.
/// The blur effect strength. A value of 0 disables it, while higher values increase the effect.
/// </summary>
API_FIELD(Attributes="Limit(0, 5, 0.01f), EditorOrder(1), PostProcessSetting((int)MotionBlurSettingsOverride.Scale)")
float Scale = 1.0f;
/// <summary>
/// The amount of sample points used during motion blur rendering. It affects quality and performance.
/// The amount of sample points used during motion blur rendering. It affects blur quality and performance.
/// </summary>
API_FIELD(Attributes="Limit(4, 32, 0.1f), EditorOrder(2), PostProcessSetting((int)MotionBlurSettingsOverride.SampleCount)")
int32 SampleCount = 10;
/// <summary>
/// The motion vectors texture resolution. Motion blur uses per-pixel motion vectors buffer that contains objects movement information. Use lower resolution to improve performance.
/// The motion vectors texture resolution. Motion blur uses a per-pixel motion vector buffer that contains an objects movement information. Use a lower resolution to improve performance.
/// </summary>
API_FIELD(Attributes="EditorOrder(3), PostProcessSetting((int)MotionBlurSettingsOverride.MotionVectorsResolution)")
ResolutionMode MotionVectorsResolution = ResolutionMode::Half;
@@ -1897,13 +1898,13 @@ API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable
float TAA_Sharpness = 0.0f;
/// <summary>
/// The blend coefficient for stationary fragments. Controls the percentage of history sample blended into final color for fragments with minimal active motion.
/// The blend coefficient for stationary fragments. Controls the percentage of history samples blended into the final color for fragments with minimal active motion.
/// </summary>
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(3), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_StationaryBlending), EditorDisplay(null, \"TAA Stationary Blending\")")
float TAA_StationaryBlending = 0.95f;
/// <summary>
/// The blending coefficient for moving fragments. Controls the percentage of history sample blended into the final color for fragments with significant active motion.
/// The blending coefficient for moving fragments. Controls the percentage of history samples blended into the final color for fragments with significant active motion.
/// </summary>
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\")")
float TAA_MotionBlending = 0.7f;
+19 -4
View File
@@ -8,11 +8,12 @@
#include "Engine/Core/Collections/Sorting.h"
#include "Engine/Debug/DebugLog.h"
#include "Engine/Level/Level.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Level/Actors/Camera.h"
#include "Engine/Level/Actors/PostFxVolume.h"
#include "Engine/Renderer/Renderer.h"
#include "Engine/Render2D/Render2D.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Level/Actors/PostFxVolume.h"
#include "Engine/Profiler/Profiler.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Threading/Threading.h"
@@ -202,15 +203,21 @@ void SceneRenderTask::CollectPostFxVolumes(RenderContext& renderContext)
{
Level::CollectPostFxVolumes(renderContext);
}
if (EnumHasAllFlags(ActorsSource , ActorsSources::CustomActors))
if (EnumHasAllFlags(ActorsSource, ActorsSources::CustomActors))
{
for (Actor* a : CustomActors)
{
auto* postFxVolume = dynamic_cast<PostFxVolume*>(a);
if (postFxVolume && a->GetIsActive())
{
postFxVolume->Collect(renderContext);
}
}
}
if (EnumHasAllFlags(ActorsSource, ActorsSources::CustomScenes))
{
for (Scene* scene : CustomScenes)
{
if (scene && scene->IsActiveInHierarchy())
scene->Rendering.CollectPostFxVolumes(renderContext);
}
}
}
@@ -282,6 +289,14 @@ void SceneRenderTask::OnCollectDrawCalls(RenderContextBatch& renderContextBatch,
ASSERT_LOW_LAYER(_customActorsScene);
_customActorsScene->Draw(renderContextBatch, (SceneRendering::DrawCategory)category);
}
if (EnumHasAllFlags(ActorsSource, ActorsSources::CustomScenes))
{
for (Scene* scene : CustomScenes)
{
if (scene && scene->IsActiveInHierarchy())
scene->Rendering.Draw(renderContextBatch, (SceneRendering::DrawCategory)category);
}
}
if (EnumHasAllFlags(ActorsSource, ActorsSources::Scenes))
{
Level::DrawActors(renderContextBatch, category);
+13 -2
View File
@@ -21,6 +21,7 @@ class PostProcessEffect;
struct RenderContext;
class Camera;
class Actor;
class Scene;
/// <summary>
/// Allows to perform custom rendering using graphics pipeline.
@@ -174,6 +175,11 @@ API_ENUM(Attributes="Flags") enum class ActorsSources
/// </summary>
CustomActors = 2,
/// <summary>
/// The scenes from the custom collection.
/// </summary>
CustomScenes = 4,
/// <summary>
/// The actors from the loaded scenes and custom collection.
/// </summary>
@@ -267,9 +273,14 @@ public:
public:
/// <summary>
/// The custom set of actors to render.
/// The custom set of actors to render. Used when ActorsSources::CustomActors flag is active.
/// </summary>
Array<Actor*> CustomActors;
API_FIELD() Array<Actor*> CustomActors;
/// <summary>
/// The custom set of scenes to render. Used when ActorsSources::CustomScenes flag is active.
/// </summary>
API_FIELD() Array<Scene*> CustomScenes;
/// <summary>
/// Adds the custom actor to the rendering.
+10 -1
View File
@@ -87,7 +87,11 @@ bool GPUShader::Create(MemoryReadStream& stream)
GPUShaderProgramInitializer initializer;
#if !BUILD_RELEASE
initializer.Owner = this;
const StringView name = GetName();
#else
const StringView name;
#endif
const bool hasCompute = GPUDevice::Instance->Limits.HasCompute;
for (int32 i = 0; i < shadersCount; i++)
{
const ShaderStage type = static_cast<ShaderStage>(stream.ReadByte());
@@ -117,10 +121,15 @@ bool GPUShader::Create(MemoryReadStream& stream)
stream.ReadBytes(&initializer.Bindings, sizeof(ShaderBindings));
// Create shader program
if (type == ShaderStage::Compute && !hasCompute)
{
LOG(Warning, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
continue;
}
GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, cache, cacheSize, stream);
if (shader == nullptr)
{
LOG(Error, "Failed to create {} Shader program '{}'.", ::ToString(type), String(initializer.Name));
LOG(Error, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
return true;
}
@@ -122,6 +122,19 @@ public:
/// </summary>
class GPUShaderProgramVS : public GPUShaderProgram
{
public:
// Input element run-time data (see VertexShaderMeta::InputElement for compile-time data)
PACK_STRUCT(struct InputElement
{
byte Type; // VertexShaderMeta::InputType
byte Index;
byte Format; // PixelFormat
byte InputSlot;
uint32 AlignedByteOffset; // Fixed value or INPUT_LAYOUT_ELEMENT_ALIGN if auto
byte InputSlotClass; // INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA or INPUT_LAYOUT_ELEMENT_PER_INSTANCE_DATA
uint32 InstanceDataStepRate; // 0 if per-vertex
});
public:
/// <summary>
/// Gets input layout description handle (platform dependent).
@@ -22,10 +22,10 @@ TextureHeader::TextureHeader()
TextureGroup = -1;
}
TextureHeader::TextureHeader(TextureHeader_Deprecated& old)
TextureHeader::TextureHeader(const TextureHeader_Deprecated& old)
{
Platform::MemoryClear(this, sizeof(*this));
Width = old.Width;;
Width = old.Width;
Height = old.Height;
MipLevels = old.MipLevels;
Format = old.Format;
@@ -49,7 +49,7 @@ StreamingTexture::StreamingTexture(ITextureOwner* parent, const String& name)
, _texture(nullptr)
, _isBlockCompressed(false)
{
ASSERT(_owner != nullptr);
ASSERT(parent != nullptr);
// Always have created texture object
ASSERT(GPUDevice::Instance);
@@ -63,7 +63,6 @@ StreamingTexture::~StreamingTexture()
{
UnloadTexture();
SAFE_DELETE(_texture);
ASSERT(_streamingTasks.Count() == 0);
}
Float2 StreamingTexture::Size() const
@@ -134,11 +133,9 @@ bool StreamingTexture::Create(const TextureHeader& header)
void StreamingTexture::UnloadTexture()
{
ScopeLock lock(_owner->GetOwnerLocker());
// Release
CancelStreamingTasks();
_texture->ReleaseGPU();
_header.MipLevels = 0;
ASSERT(_streamingTasks.Count() == 0);
}
@@ -329,11 +326,11 @@ public:
, _dataLock(_streamingTexture->GetOwner()->LockData())
{
_streamingTexture->_streamingTasks.Add(this);
_texture.OnUnload.Bind<StreamTextureMipTask, &StreamTextureMipTask::onResourceUnload2>(this);
_texture.Released.Bind<StreamTextureMipTask, &StreamTextureMipTask::OnResourceReleased2>(this);
}
private:
void onResourceUnload2(GPUTextureReference* ref)
void OnResourceReleased2()
{
// Unlink texture
if (_streamingTexture)
@@ -660,6 +660,7 @@ uint64 TextureBase::GetMemoryUsage() const
void TextureBase::CancelStreaming()
{
Asset::CancelStreaming();
_texture.CancelStreamingTasks();
}
+1 -1
View File
@@ -106,5 +106,5 @@ struct FLAXENGINE_API TextureHeader
byte CustomData[10];
TextureHeader();
TextureHeader(TextureHeader_Deprecated& old);
TextureHeader(const TextureHeader_Deprecated& old);
};
@@ -4,6 +4,7 @@
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPUResource.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "../GPUDeviceDX.h"
#include "../IncludeDirectXHeaders.h"
@@ -15,32 +15,21 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
{
case ShaderStage::Vertex:
{
D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
// Temporary variables
byte Type, Format, Index, InputSlot, InputSlotClass;
uint32 AlignedByteOffset, InstanceDataStepRate;
// Load Input Layout (it may be empty)
// Load Input Layout
byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize);
ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS);
D3D11_INPUT_ELEMENT_DESC inputLayoutDesc[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
for (int32 a = 0; a < inputLayoutSize; a++)
{
// Read description
// TODO: maybe use struct and load at once?
stream.ReadByte(&Type);
stream.ReadByte(&Index);
stream.ReadByte(&Format);
stream.ReadByte(&InputSlot);
stream.ReadUint32(&AlignedByteOffset);
stream.ReadByte(&InputSlotClass);
stream.ReadUint32(&InstanceDataStepRate);
GPUShaderProgramVS::InputElement inputElement;
stream.Read(inputElement);
// Get semantic name
const char* semanticName = nullptr;
// TODO: maybe use enum+mapping ?
switch (Type)
switch (inputElement.Type)
{
case 1:
semanticName = "POSITION";
@@ -70,7 +59,7 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
semanticName = "BLENDWEIGHT";
break;
default:
LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type);
LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type);
break;
}
@@ -78,12 +67,12 @@ GPUShaderProgram* GPUShaderDX11::CreateGPUShaderProgram(ShaderStage type, const
inputLayoutDesc[a] =
{
semanticName,
static_cast<UINT>(Index),
static_cast<DXGI_FORMAT>(Format),
static_cast<UINT>(InputSlot),
static_cast<UINT>(AlignedByteOffset),
static_cast<D3D11_INPUT_CLASSIFICATION>(InputSlotClass),
static_cast<UINT>(InstanceDataStepRate)
static_cast<UINT>(inputElement.Index),
static_cast<DXGI_FORMAT>(inputElement.Format),
static_cast<UINT>(inputElement.InputSlot),
static_cast<UINT>(inputElement.AlignedByteOffset),
static_cast<D3D11_INPUT_CLASSIFICATION>(inputElement.InputSlotClass),
static_cast<UINT>(inputElement.InstanceDataStepRate)
};
}
@@ -7,6 +7,7 @@
#include "Engine/Graphics/GPUPipelineState.h"
#include "GPUDeviceDX12.h"
#include "Types.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "../IncludeDirectXHeaders.h"
class GPUTextureViewDX12;
@@ -20,32 +20,21 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
{
case ShaderStage::Vertex:
{
D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
// Temporary variables
byte Type, Format, Index, InputSlot, InputSlotClass;
uint32 AlignedByteOffset, InstanceDataStepRate;
// Load Input Layout (it may be empty)
byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize);
ASSERT(inputLayoutSize <= VERTEX_SHADER_MAX_INPUT_ELEMENTS);
D3D12_INPUT_ELEMENT_DESC inputLayout[VERTEX_SHADER_MAX_INPUT_ELEMENTS];
for (int32 a = 0; a < inputLayoutSize; a++)
{
// Read description
// TODO: maybe use struct and load at once?
stream.ReadByte(&Type);
stream.ReadByte(&Index);
stream.ReadByte(&Format);
stream.ReadByte(&InputSlot);
stream.ReadUint32(&AlignedByteOffset);
stream.ReadByte(&InputSlotClass);
stream.ReadUint32(&InstanceDataStepRate);
GPUShaderProgramVS::InputElement inputElement;
stream.Read(inputElement);
// Get semantic name
const char* semanticName = nullptr;
// TODO: maybe use enum+mapping ?
switch (Type)
switch (inputElement.Type)
{
case 1:
semanticName = "POSITION";
@@ -75,7 +64,7 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
semanticName = "BLENDWEIGHT";
break;
default:
LOG(Fatal, "Invalid vertex shader element semantic type: {0}", Type);
LOG(Fatal, "Invalid vertex shader element semantic type: {0}", inputElement.Type);
break;
}
@@ -83,12 +72,12 @@ GPUShaderProgram* GPUShaderDX12::CreateGPUShaderProgram(ShaderStage type, const
inputLayout[a] =
{
semanticName,
static_cast<UINT>(Index),
static_cast<DXGI_FORMAT>(Format),
static_cast<UINT>(InputSlot),
static_cast<UINT>(AlignedByteOffset),
static_cast<D3D12_INPUT_CLASSIFICATION>(InputSlotClass),
static_cast<UINT>(InstanceDataStepRate)
static_cast<UINT>(inputElement.Index),
static_cast<DXGI_FORMAT>(inputElement.Format),
static_cast<UINT>(inputElement.InputSlot),
static_cast<UINT>(inputElement.AlignedByteOffset),
static_cast<D3D12_INPUT_CLASSIFICATION>(inputElement.InputSlotClass),
static_cast<UINT>(inputElement.InstanceDataStepRate)
};
}
@@ -14,9 +14,9 @@
#include "Engine/Graphics/PixelFormatExtensions.h"
#if PLATFORM_DESKTOP
#define VULKAN_UNIFORM_RING_BUFFER_SIZE 24 * 1024 * 1024
#define VULKAN_UNIFORM_RING_BUFFER_SIZE (24 * 1024 * 1024)
#else
#define VULKAN_UNIFORM_RING_BUFFER_SIZE 8 * 1024 * 1024
#define VULKAN_UNIFORM_RING_BUFFER_SIZE (8 * 1024 * 1024)
#endif
UniformBufferUploaderVulkan::UniformBufferUploaderVulkan(GPUDeviceVulkan* device)
@@ -153,10 +153,6 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons
vertexBindingDescriptions[i].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
}
// Temporary variables
byte Type, Format, Index, InputSlot, InputSlotClass;
uint32 AlignedByteOffset, InstanceDataStepRate;
// Load Input Layout (it may be empty)
byte inputLayoutSize;
stream.ReadByte(&inputLayoutSize);
@@ -167,32 +163,26 @@ GPUShaderProgram* GPUShaderVulkan::CreateGPUShaderProgram(ShaderStage type, cons
for (int32 a = 0; a < inputLayoutSize; a++)
{
// Read description
// TODO: maybe use struct and load at once?
stream.ReadByte(&Type);
stream.ReadByte(&Index);
stream.ReadByte(&Format);
stream.ReadByte(&InputSlot);
stream.ReadUint32(&AlignedByteOffset);
stream.ReadByte(&InputSlotClass);
stream.ReadUint32(&InstanceDataStepRate);
GPUShaderProgramVS::InputElement inputElement;
stream.Read(inputElement);
const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)Format);
if (AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN)
offset = AlignedByteOffset;
const auto size = PixelFormatExtensions::SizeInBytes((PixelFormat)inputElement.Format);
if (inputElement.AlignedByteOffset != INPUT_LAYOUT_ELEMENT_ALIGN)
offset = inputElement.AlignedByteOffset;
auto& vertexBindingDescription = vertexBindingDescriptions[InputSlot];
vertexBindingDescription.binding = InputSlot;
auto& vertexBindingDescription = vertexBindingDescriptions[inputElement.InputSlot];
vertexBindingDescription.binding = inputElement.InputSlot;
vertexBindingDescription.stride = Math::Max(vertexBindingDescription.stride, (uint32_t)(offset + size));
vertexBindingDescription.inputRate = InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
ASSERT(InstanceDataStepRate == 0 || InstanceDataStepRate == 1);
vertexBindingDescription.inputRate = inputElement.InputSlotClass == INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE;
ASSERT(inputElement.InstanceDataStepRate == 0 || inputElement.InstanceDataStepRate == 1);
auto& vertexAttributeDescription = vertexAttributeDescriptions[a];
vertexAttributeDescription.location = a;
vertexAttributeDescription.binding = InputSlot;
vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)Format);
vertexAttributeDescription.binding = inputElement.InputSlot;
vertexAttributeDescription.format = RenderToolsVulkan::ToVulkanFormat((PixelFormat)inputElement.Format);
vertexAttributeDescription.offset = offset;
bindingsCount = Math::Max(bindingsCount, (uint32)InputSlot + 1);
bindingsCount = Math::Max(bindingsCount, (uint32)inputElement.InputSlot + 1);
offset += size;
}
+1 -1
View File
@@ -1406,7 +1406,7 @@ Script* Actor::FindScript(const MClass* type) const
CHECK_RETURN(type, nullptr);
for (auto script : Scripts)
{
if (script->GetClass()->IsSubClassOf(type))
if (script->GetClass()->IsSubClassOf(type) || script->GetClass()->HasInterface(type))
return script;
}
for (auto child : Children)
+6 -6
View File
@@ -33,12 +33,12 @@ API_CLASS(Abstract) class FLAXENGINE_API Actor : public SceneObject
friend Prefab;
friend PrefabInstanceData;
protected:
int16 _isActive : 1;
int16 _isActiveInHierarchy : 1;
int16 _isPrefabRoot : 1;
int16 _isEnabled : 1;
int16 _drawNoCulling : 1;
int16 _drawCategory : 4;
uint16 _isActive : 1;
uint16 _isActiveInHierarchy : 1;
uint16 _isPrefabRoot : 1;
uint16 _isEnabled : 1;
uint16 _drawNoCulling : 1;
uint16 _drawCategory : 4;
byte _layer;
StaticFlags _staticFlags;
Transform _localTransform;
@@ -172,6 +172,28 @@ void AnimatedModel::GetNodeTransformation(const StringView& nodeName, Matrix& no
GetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace);
}
void AnimatedModel::SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTransformation, bool worldSpace)
{
if (GraphInstance.NodesPose.IsEmpty())
const_cast<AnimatedModel*>(this)->PreInitSkinningData(); // Ensure to have valid nodes pose to return
CHECK(nodeIndex >= 0 && nodeIndex < GraphInstance.NodesPose.Count());
GraphInstance.NodesPose[nodeIndex] = nodeTransformation;
if (worldSpace)
{
Matrix world;
_transform.GetWorld(world);
Matrix invWorld;
Matrix::Invert(world, invWorld);
GraphInstance.NodesPose[nodeIndex] = GraphInstance.NodesPose[nodeIndex] * invWorld;
}
OnAnimationUpdated();
}
void AnimatedModel::SetNodeTransformation(const StringView& nodeName, const Matrix& nodeTransformation, bool worldSpace)
{
SetNodeTransformation(SkinnedModel ? SkinnedModel->FindNode(nodeName) : -1, nodeTransformation, worldSpace);
}
int32 AnimatedModel::FindClosestNode(const Vector3& location, bool worldSpace) const
{
if (GraphInstance.NodesPose.IsEmpty())
@@ -229,6 +229,22 @@ public:
/// <param name="worldSpace">True if convert matrices into world-space, otherwise returned values will be in local-space of the actor.</param>
API_FUNCTION() void GetNodeTransformation(const StringView& nodeName, API_PARAM(Out) Matrix& nodeTransformation, bool worldSpace = false) const;
/// <summary>
/// Sets the node final transformation. If multiple nodes are to be set within a frame, do not use set worldSpace to true, and do the conversion yourself to avoid recalculation of inv matrices.
/// </summary>
/// <param name="nodeIndex">The index of the skinned model skeleton node.</param>
/// <param name="nodeTransformation">The final node transformation matrix.</param>
/// <param name="worldSpace">True if convert matrices from world-space, otherwise values will be in local-space of the actor.</param>
API_FUNCTION() void SetNodeTransformation(int32 nodeIndex, const Matrix& nodeTransformation, bool worldSpace = false);
/// <summary>
/// Sets the node final transformation. If multiple nodes are to be set within a frame, do not use set worldSpace to true, and do the conversion yourself to avoid recalculation of inv matrices.
/// </summary>
/// <param name="nodeName">The name of the skinned model skeleton node.</param>
/// <param name="nodeTransformation">The final node transformation matrix.</param>
/// <param name="worldSpace">True if convert matrices from world-space, otherwise values will be in local-space of the actor.</param>
API_FUNCTION() void SetNodeTransformation(const StringView& nodeName, const Matrix& nodeTransformation, bool worldSpace = false);
/// <summary>
/// Finds the closest node to a given location.
/// </summary>
+6 -6
View File
@@ -66,7 +66,7 @@ public:
/// <summary>
/// Gets the value indicating if camera should use perspective rendering mode, otherwise it will use orthographic projection.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Camera\"), Tooltip(\"Enables perspective projection mode, otherwise uses orthographic.\")")
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(true), EditorDisplay(\"Camera\")")
bool GetUsePerspective() const;
/// <summary>
@@ -77,7 +77,7 @@ public:
/// <summary>
/// Gets the camera's field of view (in degrees).
/// </summary>
API_PROPERTY(Attributes="EditorOrder(10), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), Tooltip(\"Field of view angle in degrees.\")")
API_PROPERTY(Attributes="EditorOrder(20), DefaultValue(60.0f), Limit(0, 179), EditorDisplay(\"Camera\", \"Field Of View\"), VisibleIf(nameof(UsePerspective))")
float GetFieldOfView() const;
/// <summary>
@@ -88,7 +88,7 @@ public:
/// <summary>
/// Gets the custom aspect ratio. 0 if not use custom value.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(0.0f), Limit(0, 10, 0.01f), EditorDisplay(\"Camera\"), Tooltip(\"Custom aspect ratio to use. Set to 0 to disable.\")")
API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(0.0f), Limit(0, 10, 0.01f), EditorDisplay(\"Camera\"), VisibleIf(nameof(UsePerspective))")
float GetCustomAspectRatio() const;
/// <summary>
@@ -99,7 +99,7 @@ public:
/// <summary>
/// Gets camera's near plane distance.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\"), Tooltip(\"Near clipping plane distance\")")
API_PROPERTY(Attributes="EditorOrder(30), DefaultValue(10.0f), Limit(0, 1000, 0.05f), EditorDisplay(\"Camera\")")
float GetNearPlane() const;
/// <summary>
@@ -110,7 +110,7 @@ public:
/// <summary>
/// Gets camera's far plane distance.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\"), Tooltip(\"Far clipping plane distance\")")
API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(40000.0f), Limit(0, float.MaxValue, 5), EditorDisplay(\"Camera\")")
float GetFarPlane() const;
/// <summary>
@@ -121,7 +121,7 @@ public:
/// <summary>
/// Gets the orthographic projection scale.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1.0f), Limit(0.0001f, 1000, 0.01f), EditorDisplay(\"Camera\"), Tooltip(\"Orthographic projection scale\")")
API_PROPERTY(Attributes="EditorOrder(60), DefaultValue(1.0f), Limit(0.0001f, 1000, 0.01f), EditorDisplay(\"Camera\"), VisibleIf(nameof(UsePerspective), true)")
float GetOrthographicScale() const;
/// <summary>
@@ -13,7 +13,7 @@ class FLAXENGINE_API DirectionalLight : public LightWithShadow
DECLARE_SCENE_OBJECT(DirectionalLight);
public:
/// <summary>
/// The number of cascades used for slicing the range of depth covered by the light. Values are 1, 2 or 4 cascades; a typical scene uses 4 cascades.
/// The number of cascades used for slicing the range of depth covered by the light during shadow rendering. Values are 1, 2 or 4 cascades; a typical scene uses 4 cascades.
/// </summary>
API_FIELD(Attributes="EditorOrder(65), DefaultValue(4), Limit(1, 4), EditorDisplay(\"Shadow\")")
int32 CascadeCount = 4;
@@ -29,7 +29,7 @@ public:
float FogDensity = 0.02f;
/// <summary>
/// The fog height density factor that controls how the density increases as height decreases. The smaller values produce more visible transition larger.
/// The fog height density factor that controls how the density increases as height decreases. Smaller values produce a more visible transition layer.
/// </summary>
API_FIELD(Attributes="EditorOrder(20), DefaultValue(0.2f), Limit(0.0001f, 10.0f, 0.001f), EditorDisplay(\"Exponential Height Fog\")")
float FogHeightFalloff = 0.2f;
@@ -55,7 +55,7 @@ public:
float StartDistance = 0.0f;
/// <summary>
/// Scene elements past this distance will not have fog applied. This is useful for excluding skyboxes which already have fog baked in.
/// Scene elements past this distance will not have fog applied. This is useful for excluding skyboxes which already have fog baked in. Setting this value to 0 disables it.
/// </summary>
API_FIELD(Attributes="EditorOrder(60), DefaultValue(0.0f), Limit(0), EditorDisplay(\"Exponential Height Fog\")")
float FogCutoffDistance = 0.0f;
@@ -111,7 +111,7 @@ public:
Color VolumetricFogAlbedo = Color::White;
/// <summary>
/// Light emitted by height fog. This is a density so more light is emitted the further you are looking through the fog.
/// Light emitted by height fog. This is a density value so more light is emitted the further you are looking through the fog.
/// In most cases using a Skylight is a better choice, however, it may be useful in certain scenarios.
/// </summary>
API_FIELD(Attributes="EditorOrder(330), DefaultValue(typeof(Color), \"0,0,0,1\"), EditorDisplay(\"Volumetric Fog\", \"Emissive\")")
+5 -5
View File
@@ -29,7 +29,7 @@ public:
float Brightness = 3.14f;
/// <summary>
/// Controls light visibility range. The distance at which the light be completely faded. Use value 0 to always draw light.
/// Controls light visibility range. The distance at which the light becomes completely faded. Use a value of 0 to always draw light.
/// </summary>
API_FIELD(Attributes="EditorOrder(35), Limit(0, float.MaxValue, 10.0f), EditorDisplay(\"Light\")")
float ViewDistance = 0.0f;
@@ -87,19 +87,19 @@ public:
float MinRoughness = 0.04f;
/// <summary>
/// The light shadows casting distance from view.
/// Shadows casting distance from view.
/// </summary>
API_FIELD(Attributes="EditorOrder(80), EditorDisplay(\"Shadow\", \"Distance\"), Limit(0, 1000000)")
float ShadowsDistance = 5000.0f;
/// <summary>
/// The light shadows fade off distance
/// Shadows fade off distance.
/// </summary>
API_FIELD(Attributes="EditorOrder(90), EditorDisplay(\"Shadow\", \"Fade Distance\"), Limit(0.0f, 10000.0f, 0.1f)")
float ShadowsFadeDistance = 500.0f;
/// <summary>
/// The light shadows edges sharpness
/// TheShadows edges sharpness.
/// </summary>
API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Shadow\", \"Sharpness\"), Limit(1.0f, 10.0f, 0.001f)")
float ShadowsSharpness = 1.0f;
@@ -129,7 +129,7 @@ public:
float ContactShadowsLength = 0.0f;
/// <summary>
/// Shadows casting mode by this visual element
/// Describes how a visual element casts shadows.
/// </summary>
API_FIELD(Attributes="EditorOrder(60), EditorDisplay(\"Shadow\", \"Mode\")")
ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All;
+3 -3
View File
@@ -31,19 +31,19 @@ public:
/// <summary>
/// Directional light that is used to simulate the sun.
/// </summary>
API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Sun\")")
API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Sky\")")
ScriptingObjectReference<DirectionalLight> SunLight;
/// <summary>
/// The sun disc scale.
/// </summary>
API_FIELD(Attributes="EditorOrder(20), DefaultValue(2.0f), EditorDisplay(\"Sun\"), Limit(0, 100, 0.01f)")
API_FIELD(Attributes="EditorOrder(20), DefaultValue(2.0f), EditorDisplay(\"Sky\"), Limit(0, 100, 0.01f)")
float SunDiscScale = 2.0f;
/// <summary>
/// The sun power.
/// </summary>
API_FIELD(Attributes="EditorOrder(30), DefaultValue(8.0f), EditorDisplay(\"Sun\"), Limit(0, 1000, 0.01f)")
API_FIELD(Attributes="EditorOrder(30), DefaultValue(8.0f), EditorDisplay(\"Sky\"), Limit(0, 1000, 0.01f)")
float SunPower = 8.0f;
private:
+38 -2
View File
@@ -149,9 +149,14 @@ float Spline::GetSplineDuration() const
float Spline::GetSplineLength() const
{
float sum = 0.0f;
const int32 slices = 20;
const float step = 1.0f / (float)slices;
constexpr int32 slices = 20;
constexpr float step = 1.0f / (float)slices;
Vector3 prevPoint = Vector3::Zero;
if (Curve.GetKeyframes().Count() != 0)
{
const auto& a = Curve[0];
prevPoint = a.Value.Translation * _transform.Scale;
}
for (int32 i = 1; i < Curve.GetKeyframes().Count(); i++)
{
const auto& a = Curve[i - 1];
@@ -176,6 +181,37 @@ float Spline::GetSplineLength() const
return Math::Sqrt(sum);
}
float Spline::GetSplineSegmentLength(int32 index) const
{
if (index == 0)
return 0.0f;
CHECK_RETURN(index > 0 && index < GetSplinePointsCount(), 0.0f);
float sum = 0.0f;
constexpr int32 slices = 20;
constexpr float step = 1.0f / (float)slices;
const auto& a = Curve[index - 1];
const auto& b = Curve[index];
Vector3 startPoint = a.Value.Translation * _transform.Scale;
{
const float length = Math::Abs(b.Time - a.Time);
Vector3 leftTangent, rightTangent;
AnimationUtils::GetTangent(a.Value.Translation, a.TangentOut.Translation, length, leftTangent);
AnimationUtils::GetTangent(b.Value.Translation, b.TangentIn.Translation, length, rightTangent);
// TODO: implement sth more analytical than brute-force solution
for (int32 slice = 0; slice < slices; slice++)
{
const float t = (float)slice * step;
Vector3 pos;
AnimationUtils::Bezier(a.Value.Translation, leftTangent, rightTangent, b.Value.Translation, t, pos);
pos *= _transform.Scale;
sum += (float)Vector3::DistanceSquared(pos, startPoint);
startPoint = pos;
}
}
return Math::Sqrt(sum);
}
float Spline::GetSplineTime(int32 index) const
{
CHECK_RETURN(index >= 0 && index < GetSplinePointsCount(), 0.0f)
+7
View File
@@ -167,6 +167,13 @@ public:
/// </summary>
API_PROPERTY() float GetSplineLength() const;
/// <summary>
/// Gets the length of the spline segment (distance between pair of two points).
/// </summary>
/// <param name="index">The index of the segment end index. Zero-based, smaller than GetSplinePointsCount().</param>
/// <returns>The spline segment length.</returns>
API_FUNCTION() float GetSplineSegmentLength(int32 index) const;
/// <summary>
/// Gets the time of the spline keyframe.
/// </summary>
+13 -19
View File
@@ -24,7 +24,7 @@ private:
public:
/// <summary>
/// Light source bulb radius
/// Light source bulb radius.
/// </summary>
API_FIELD(Attributes="EditorOrder(2), DefaultValue(0.0f), EditorDisplay(\"Light\"), Limit(0, 1000, 0.01f)")
float SourceRadius = 0.0f;
@@ -42,54 +42,51 @@ public:
float FallOffExponent = 8.0f;
/// <summary>
/// IES texture (light profiles from real world measured data)
/// IES texture (light profiles from real world measured data).
/// </summary>
API_FIELD(Attributes="EditorOrder(211), DefaultValue(null), EditorDisplay(\"IES Profile\", \"IES Texture\")")
AssetReference<IESProfile> IESTexture;
/// <summary>
/// Enable/disable using light brightness from IES profile
/// Enable/disable using light brightness from IES profile.
/// </summary>
API_FIELD(Attributes="EditorOrder(212), DefaultValue(false), EditorDisplay(\"IES Profile\", \"Use IES Brightness\")")
bool UseIESBrightness = false;
/// <summary>
/// Global scale for IES brightness contribution
/// Global scale for IES brightness contribution.
/// </summary>
API_FIELD(Attributes="EditorOrder(213), DefaultValue(1.0f), Limit(0, 10000, 0.01f), EditorDisplay(\"IES Profile\", \"Brightness Scale\")")
float IESBrightnessScale = 1.0f;
public:
/// <summary>
/// Computes light brightness value
/// Computes light brightness value.
/// </summary>
/// <returns>Brightness</returns>
float ComputeBrightness() const;
/// <summary>
/// Gets scaled light radius
/// Gets scaled light radius.
/// </summary>
float GetScaledRadius() const;
/// <summary>
/// Gets light radius
/// Gets light radius.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(1000.0f), EditorDisplay(\"Light\"), Tooltip(\"Light radius\"), Limit(0, 10000, 0.1f)")
API_PROPERTY(Attributes="EditorOrder(1), DefaultValue(1000.0f), EditorDisplay(\"Light\"), Limit(0, 10000, 0.1f)")
FORCE_INLINE float GetRadius() const
{
return _radius;
}
/// <summary>
/// Sets light radius
/// Sets light radius.
/// </summary>
/// <param name="value">New radius</param>
API_PROPERTY() void SetRadius(float value);
/// <summary>
/// Gets the spot light's outer cone angle (in degrees)
/// Gets the spot light's outer cone angle (in degrees).
/// </summary>
/// <returns>Outer angle (in degrees)</returns>
API_PROPERTY(Attributes="EditorOrder(22), DefaultValue(43.0f), EditorDisplay(\"Light\"), Limit(1, 89, 0.1f)")
FORCE_INLINE float GetOuterConeAngle() const
{
@@ -97,15 +94,13 @@ public:
}
/// <summary>
/// Sets the spot light's outer cone angle (in degrees)
/// Sets the spot light's outer cone angle (in degrees).
/// </summary>
/// <param name="value">Value to assign</param>
API_PROPERTY() void SetOuterConeAngle(float value);
/// <summary>
/// Sets the spot light's inner cone angle (in degrees)
/// Sets the spot light's inner cone angle (in degrees).
/// </summary>
/// <returns>Inner angle (in degrees)</returns>
API_PROPERTY(Attributes="EditorOrder(21), DefaultValue(10.0f), EditorDisplay(\"Light\"), Limit(1, 89, 0.1f)")
FORCE_INLINE float GetInnerConeAngle() const
{
@@ -113,9 +108,8 @@ public:
}
/// <summary>
/// Sets the spot light's inner cone angle (in degrees)
/// Sets the spot light's inner cone angle (in degrees).
/// </summary>
/// <param name="value">Value to assign</param>
API_PROPERTY() void SetInnerConeAngle(float value);
private:
+1 -18
View File
@@ -42,24 +42,7 @@ public:
/// <summary>
/// Field for assigning new script to transfer data to.
/// </summary>
API_PROPERTY() void SetReferenceScript(const ScriptingObjectReference<Script>& value)
{
_referenceScript = value;
if (Data.IsEmpty())
return;
rapidjson_flax::Document document;
document.Parse(Data.ToStringAnsi().GetText());
auto modifier = Cache::ISerializeModifier.Get();
_referenceScript->Deserialize(document, modifier.Value);
DeleteObject();
}
API_PROPERTY() void SetReferenceScript(const ScriptingObjectReference<Script>& value);
};
inline MissingScript::MissingScript(const SpawnParams& params)
: Script(params)
{
}
#endif
+1 -1
View File
@@ -19,7 +19,7 @@ API_CLASS(Static) class FLAXENGINE_API LargeWorlds
/// <summary>
/// Defines the size of a single chunk. Large world (64-bit) gets divided into smaller chunks so all the math operations (32-bit) can be performed relative to the chunk origin without precision loss.
/// </summary>
API_FIELD() static constexpr Real ChunkSize = 262144;
API_FIELD() static constexpr Real ChunkSize = 8192;
/// <summary>
/// Updates the large world origin to match the input position. The origin is snapped to the best matching chunk location.
+7 -32
View File
@@ -416,7 +416,7 @@ public:
}
// Load scene
if (Level::loadScene(SceneAsset.Get()))
if (Level::loadScene(SceneAsset))
{
LOG(Error, "Failed to deserialize scene {0}", SceneId);
CallSceneEvent(SceneEventType::OnSceneLoadError, nullptr, SceneId);
@@ -816,40 +816,10 @@ bool LevelImpl::unloadScenes()
return false;
}
bool Level::loadScene(const Guid& sceneId)
{
const auto sceneAsset = Content::LoadAsync<JsonAsset>(sceneId);
return loadScene(sceneAsset);
}
bool Level::loadScene(const String& scenePath)
{
LOG(Info, "Loading scene from file. Path: \'{0}\'", scenePath);
// Check for missing file
if (!FileSystem::FileExists(scenePath))
{
LOG(Error, "Missing scene file.");
return true;
}
// Load file
BytesContainer sceneData;
if (File::ReadAllBytes(scenePath, sceneData))
{
LOG(Error, "Cannot load data from file.");
return true;
}
return loadScene(sceneData);
}
bool Level::loadScene(JsonAsset* sceneAsset)
{
// Keep reference to the asset (prevent unloading during action)
AssetReference<JsonAsset> ref = sceneAsset;
// Wait for loaded
if (sceneAsset == nullptr || sceneAsset->WaitForLoaded())
{
LOG(Error, "Cannot load scene asset.");
@@ -879,6 +849,7 @@ bool Level::loadScene(const BytesContainer& sceneData, Scene** outScene)
return true;
}
ScopeLock lock(ScenesLock);
return loadScene(document, outScene);
}
@@ -975,6 +946,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
SceneObject** objects = sceneObjects->Get();
if (context.Async)
{
ScenesLock.Unlock(); // Unlock scenes from Main Thread so Job Threads can use it to safely setup actors hierarchy (see Actor::Deserialize)
JobSystem::Execute([&](int32 i)
{
i++; // Start from 1. at index [0] was scene
@@ -992,6 +964,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
else
SceneObjectsFactory::HandleObjectDeserializationError(stream);
}, objectsCount - 1);
ScenesLock.Lock();
}
else
{
@@ -1091,6 +1064,7 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
}
}
}
prefabSyncData.InitNewObjects();
}
// /\ all above this has to be done on an any thread
@@ -1135,7 +1109,7 @@ bool LevelImpl::saveScene(Scene* scene)
bool LevelImpl::saveScene(Scene* scene, const String& path)
{
ASSERT(scene);
ASSERT(scene && EnumHasNoneFlags(scene->Flags, ObjectFlags::WasMarkedToDelete));
auto sceneId = scene->GetID();
LOG(Info, "Saving scene {0} to \'{1}\'", scene->GetName(), path);
@@ -1330,6 +1304,7 @@ bool Level::LoadScene(const Guid& id)
}
// Load scene
ScopeLock lock(ScenesLock);
if (loadScene(sceneAsset))
{
LOG(Error, "Failed to deserialize scene {0}", id);
+2 -2
View File
@@ -541,8 +541,8 @@ private:
};
static void callActorEvent(ActorEventType eventType, Actor* a, Actor* b);
static bool loadScene(const Guid& sceneId);
static bool loadScene(const String& scenePath);
// All loadScene assume that ScenesLock has been taken by the calling thread
static bool loadScene(JsonAsset* sceneAsset);
static bool loadScene(const BytesContainer& sceneData, Scene** outScene = nullptr);
static bool loadScene(rapidjson_flax::Document& document, Scene** outScene = nullptr);

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