From f8dc8ab903a9f246240a08d79b8683bfd05ae153 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 23 Nov 2025 14:19:11 -0600 Subject: [PATCH 01/40] Fix not being able to replay same animation in animation slot. --- Source/Engine/Animations/Graph/AnimGroup.Animation.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index fd62ec537..fe0ad52e1 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -2441,10 +2441,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu { if (bucket.LoopsLeft == 0) { - // End playing animation + // End playing animation and reset bucket params value = tryGetValue(node->GetBox(1), Value::Null); bucket.Index = -1; slot.Animation = nullptr; + bucket.TimePosition = 0.0f; + bucket.BlendInPosition = 0.0f; + bucket.BlendOutPosition = 0.0f; + bucket.LoopsDone = 0; return; } From 2d56411e5f8f4a292b328eeac51b6c3d59fe04f5 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 23 Nov 2025 14:19:37 -0600 Subject: [PATCH 02/40] Add slot stop methods without anim param. --- Source/Engine/Level/Actors/AnimatedModel.cpp | 24 ++++++++++++++++++++ Source/Engine/Level/Actors/AnimatedModel.h | 12 ++++++++++ 2 files changed, 36 insertions(+) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index ee66c6c77..a8c091eb9 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -563,6 +563,20 @@ void AnimatedModel::StopSlotAnimation(const StringView& slotName, Animation* ani } } +void AnimatedModel::StopSlotAnimation(const StringView& slotName) +{ + for (auto& slot : GraphInstance.Slots) + { + if (slot.Name == slotName) + { + //slot.Animation = nullptr; // TODO: make an immediate version of this method and set the animation to nullptr. + if (slot.Animation != nullptr) + slot.Reset = true; + break; + } + } +} + void AnimatedModel::PauseSlotAnimation() { for (auto& slot : GraphInstance.Slots) @@ -601,6 +615,16 @@ bool AnimatedModel::IsPlayingSlotAnimation(const StringView& slotName, Animation return false; } +bool AnimatedModel::IsPlayingSlotAnimation(const StringView& slotName) +{ + for (auto& slot : GraphInstance.Slots) + { + if (slot.Name == slotName && !slot.Pause) + return true; + } + return false; +} + void AnimatedModel::ApplyRootMotion(const Transform& rootMotionDelta) { // Skip if no motion diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index a011be0d0..dc837c658 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -414,6 +414,12 @@ public: /// The name of the slot. /// The animation to stop. API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim); + + /// + /// Stops the animation playback on the slot in Anim Graph. + /// + /// The name of the slot. + API_FUNCTION() void StopSlotAnimation(const StringView& slotName); /// /// Pauses all the animations playback on the all slots in Anim Graph. @@ -439,6 +445,12 @@ public: /// The animation to check. API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim); + /// + /// Checks if the animation playback is active on the slot in Anim Graph (not paused). + /// + /// The name of the slot. + API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName); + private: void ApplyRootMotion(const Transform& rootMotionDelta); void SyncParameters(); From a62ca5452eeb1fb01e15d23cb5216e733e6c7e6c Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 25 Nov 2025 17:33:11 +0100 Subject: [PATCH 03/40] Fixed missing move semantics in script object reference --- .../Scripting/ScriptingObjectReference.h | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Source/Engine/Scripting/ScriptingObjectReference.h b/Source/Engine/Scripting/ScriptingObjectReference.h index a71d22eae..58fed7668 100644 --- a/Source/Engine/Scripting/ScriptingObjectReference.h +++ b/Source/Engine/Scripting/ScriptingObjectReference.h @@ -33,6 +33,13 @@ public: { } + ScriptingObjectReferenceBase(ScriptingObjectReferenceBase&& other) noexcept + : _object(nullptr) + { + OnSet(other._object); + other.OnSet(nullptr); + } + /// /// Initializes a new instance of the class. /// @@ -96,6 +103,16 @@ protected: void OnSet(ScriptingObject* object); void OnDeleted(ScriptingObject* obj); + + ScriptingObjectReferenceBase& operator=(ScriptingObjectReferenceBase&& other) noexcept + { + if (this != &other) + { + OnSet(other._object); + other.OnSet(nullptr); + } + return *this; + } }; /// @@ -133,6 +150,11 @@ public: { } + ScriptingObjectReference(ScriptingObjectReference&& other) noexcept + : ScriptingObjectReferenceBase(MoveTemp(other)) + { + } + /// /// Finalizes an instance of the class. /// @@ -173,6 +195,12 @@ public: return *this; } + ScriptingObjectReference& operator=(ScriptingObjectReference&& other) noexcept + { + ScriptingObjectReferenceBase::operator=(MoveTemp(other)); + return *this; + } + FORCE_INLINE ScriptingObjectReference& operator=(const Guid& id) { OnSet(static_cast(FindObject(id, T::GetStaticClass()))); From 465f30661f5752426212cb5ef6150dd306b0a20f Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 25 Nov 2025 17:33:54 +0100 Subject: [PATCH 04/40] Minor memory layout optimization --- .../Engine/Networking/NetworkReplicator.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index c584d3526..bcca584b8 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -116,17 +116,6 @@ PACK_STRUCT(struct NetworkMessageObjectRpc struct NetworkReplicatedObject { - ScriptingObjectReference Object; - Guid ObjectId; - Guid ParentId; - uint32 OwnerClientId; - uint32 LastOwnerFrame = 0; - NetworkObjectRole Role; - uint8 Spawned : 1; - uint8 Synced : 1; - DataContainer TargetClientIds; - INetworkObject* AsNetworkObject; - struct { NetworkClientsMask Mask; @@ -139,6 +128,17 @@ struct NetworkReplicatedObject } } RepCache; + ScriptingObjectReference Object; + Guid ObjectId; + Guid ParentId; + DataContainer TargetClientIds; + INetworkObject* AsNetworkObject; + uint32 OwnerClientId; + uint32 LastOwnerFrame = 0; + NetworkObjectRole Role; + uint8 Spawned : 1; + uint8 Synced : 1; + NetworkReplicatedObject() { Spawned = 0; From d9a18b1d31ab76070d5a2ba7a54f7c89a37db4e8 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 25 Nov 2025 21:20:20 +0100 Subject: [PATCH 05/40] Fixed HashSet compact rehash under heavy collisions - Compact now iterates over the old bucket array using the saved oldSize, and frees with that size, avoiding out-of-bounds when _size changes. - If reinsertion finds no free slot during compaction (pathological collisions), the table grows once and retries, preventing AVs. - This fix addresses problems with weak hash keys (like #3824). --- Source/Engine/Core/Collections/HashSetBase.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Core/Collections/HashSetBase.h b/Source/Engine/Core/Collections/HashSetBase.h index 36fcf275d..96adabf79 100644 --- a/Source/Engine/Core/Collections/HashSetBase.h +++ b/Source/Engine/Core/Collections/HashSetBase.h @@ -409,26 +409,33 @@ protected: else { // Rebuild entire table completely + const int32 oldSize = _size; AllocationData oldAllocation; - AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); + AllocationUtils::MoveToEmpty(oldAllocation, _allocation, oldSize, oldSize); _allocation.Allocate(_size); BucketType* data = _allocation.Get(); for (int32 i = 0; i < _size; ++i) data[i]._state = HashSetBucketState::Empty; BucketType* oldData = oldAllocation.Get(); FindPositionResult pos; - for (int32 i = 0; i < _size; ++i) + for (int32 i = 0; i < oldSize; ++i) { BucketType& oldBucket = oldData[i]; if (oldBucket.IsOccupied()) { FindPosition(oldBucket.GetKey(), pos); + if (pos.FreeSlotIndex == -1) + { + // Grow and retry to handle pathological cases (eg. heavy collisions) + EnsureCapacity(_size + 1, true); + FindPosition(oldBucket.GetKey(), pos); + } ASSERT(pos.FreeSlotIndex != -1); BucketType& bucket = _allocation.Get()[pos.FreeSlotIndex]; bucket = MoveTemp(oldBucket); } } - for (int32 i = 0; i < _size; ++i) + for (int32 i = 0; i < oldSize; ++i) oldData[i].Free(); } _deletedCount = 0; From 1bf6612002c75cd3188804a80ef9d60e53eb3d5e Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Tue, 25 Nov 2025 17:26:57 -0600 Subject: [PATCH 06/40] Fix exception thrown when reloading open windows. --- Source/Editor/Modules/WindowsModule.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 5c9c613d2..218394a3b 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -896,9 +896,11 @@ namespace FlaxEditor.Modules if (type.IsAssignableTo(typeof(AssetEditorWindow))) { - var ctor = type.GetConstructor(new Type[] { typeof(Editor), typeof(AssetItem) }); var assetItem = Editor.ContentDatabase.FindAsset(winData.AssetItemID); + var assetType = assetItem.GetType(); + var ctor = type.GetConstructor(new Type[] { typeof(Editor), assetType }); var win = (AssetEditorWindow)ctor.Invoke(new object[] { Editor.Instance, assetItem }); + win.Show(winData.DockState, winData.DockState != DockState.Float ? winData.DockedTo : null, winData.SelectOnShow, winData.SplitterValue); if (winData.DockState == DockState.Float) { From 0007185b5fc9515b5a8222dc2c7efaeec876862f Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 26 Nov 2025 17:46:41 +0100 Subject: [PATCH 07/40] Fixed late-join network replication - Adjusted replication to resend unchanged state only to missing clients. - Skip server serialization when no recipients, and downgrade unknown-despawn noise. --- .../Engine/Networking/NetworkReplicator.cpp | 80 +++++++++++++++---- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index c584d3526..5e2979db0 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -698,14 +698,11 @@ void SendReplication(ScriptingObject* obj, NetworkClientsMask targetClients) return; auto& item = it->Item; const bool isClient = NetworkManager::IsClient(); + const NetworkClientsMask fullTargetClients = targetClients; - // Skip serialization of objects that none will receive - if (!isClient) - { - BuildCachedTargets(item, targetClients); - if (CachedTargets.Count() == 0) - return; - } + // If server has no recipients, skip early. + if (!isClient && !targetClients) + return; if (item.AsNetworkObject) item.AsNetworkObject->OnNetworkSerialize(); @@ -727,19 +724,33 @@ void SendReplication(ScriptingObject* obj, NetworkClientsMask targetClients) } #if USE_NETWORK_REPLICATOR_CACHE + // Check if only newly joined clients are missing this data to avoid resending it to everyone + NetworkClientsMask missingClients; + missingClients.Word0 = targetClients.Word0 & ~item.RepCache.Mask.Word0; + missingClients.Word1 = targetClients.Word1 & ~item.RepCache.Mask.Word1; + // Process replication cache to skip sending object data if it didn't change - if (item.RepCache.Data.Length() == size && - item.RepCache.Mask == targetClients && - Platform::MemoryCompare(item.RepCache.Data.Get(), stream->GetBuffer(), size) == 0) + if (item.RepCache.Data.Length() == size && Platform::MemoryCompare(item.RepCache.Data.Get(), stream->GetBuffer(), size) == 0) { - return; + // If data is the same and only the client set changed, replicate to missing clients only + if (!missingClients) + return; + targetClients = missingClients; } - item.RepCache.Mask = targetClients; + item.RepCache.Mask = fullTargetClients; item.RepCache.Data.Copy(stream->GetBuffer(), size); #endif // TODO: use Unreliable for dynamic objects that are replicated every frame? (eg. player state) constexpr NetworkChannelType repChannel = NetworkChannelType::Reliable; + // Skip serialization of objects that none will receive + if (!isClient) + { + BuildCachedTargets(item, targetClients); + if (CachedTargets.Count() == 0) + return; + } + // Send object to clients NetworkMessageObjectReplicate msgData; msgData.OwnerFrame = NetworkManager::Frame; @@ -1530,7 +1541,21 @@ void NetworkReplicator::DespawnObject(ScriptingObject* obj) // Register for despawning (batched during update) auto& despawn = DespawnQueue.AddOne(); despawn.Id = obj->GetID(); - despawn.Targets = item.TargetClientIds; + if (item.TargetClientIds.IsValid()) + { + despawn.Targets = item.TargetClientIds; + } + else + { + // Snapshot current recipients to avoid sending despawn to clients that connect later (and never got the spawn) + Array> clientIds; + for (const NetworkClient* client : NetworkManager::Clients) + { + if (client->State == NetworkConnectionState::Connected && client->ClientId != item.OwnerClientId) + clientIds.Add(client->ClientId); + } + despawn.Targets.Copy(clientIds); + } // Prevent spawning for (int32 i = 0; i < SpawnQueue.Count(); i++) @@ -1823,6 +1848,31 @@ void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client) { ScopeLock lock(ObjectsLock); NewClients.Add(client); + + // Ensure cached replication acknowledges the new client without resending to others. + // Clear the new client's bit in RepCache and schedule a near-term replication. + const int32 clientIndex = NetworkManager::Clients.Find(client); + if (clientIndex != -1) + { + const uint64 bitMask = 1ull << (uint64)(clientIndex % 64); + const int32 wordIndex = clientIndex / 64; + for (auto it = Objects.Begin(); it.IsNotEnd(); ++it) + { + auto& item = it->Item; + ScriptingObject* obj = item.Object.Get(); + if (!obj || !item.Spawned || item.Role != NetworkObjectRole::OwnedAuthoritative) + continue; + + // Mark this client as missing cached data + uint64* word = wordIndex == 0 ? &item.RepCache.Mask.Word0 : &item.RepCache.Mask.Word1; + *word &= ~bitMask; + + // Force next replication tick for this object so the new client gets data promptly + if (Hierarchy) + Hierarchy->DirtyObject(obj); + } + } + ASSERT(sizeof(NetworkClientsMask) * 8 >= (uint32)NetworkManager::Clients.Count()); // Ensure that clients mask can hold all of clients } @@ -2275,7 +2325,9 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network } else { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to despawn object {}", objectId); + // If this client never had the object (eg. it was targeted to other clients only), drop the message quietly. + DespawnedObjects.Add(objectId); + NETWORK_REPLICATOR_LOG(Warning, "[NetworkReplicator] Failed to despawn object {}", objectId); } } From 64cd898a659c103c76e964b0f596eeb6cdc1ff3a Mon Sep 17 00:00:00 2001 From: Alex Ray Date: Thu, 27 Nov 2025 18:09:11 +0100 Subject: [PATCH 08/40] Bypassing Call Logic in Editor Preview --- .../MaterialGenerator.Material.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index c778c03ee..bdade721e 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -790,9 +790,21 @@ void MaterialGenerator::ProcessGroupFunction(Box* box, Node* node, Value& value) // Function Input case 1: { + // Check the stack count.If only 1 graph is present, + // we are processing the graph in isolation (e.g., in the Editor Preview). + // In this case, we skip the caller-finding logic and use the node's default value. + if (_graphStack.Count() < 2) + { + // Use the default value from the function input node's box (usually box 1) + value = tryGetValue(node->TryGetBox(1), Value::Zero); + break; + } + // Find the function call Node* functionCallNode = nullptr; - ASSERT(_graphStack.Count() >= 2); + + // The original ASSERT has been effectively replaced by the 'if' above. + //ASSERT(_graphStack.Count() >= 2); Graph* graph; for (int32 i = _callStack.Count() - 1; i >= 0; i--) { From 56beca0db4d7e8b5d14323e988ab86275fcdf071 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 27 Nov 2025 22:54:43 +0100 Subject: [PATCH 09/40] Fixed network replicated-object deduplication by hashing/equality on ObjectId Aligned NetworkReplicatedObject equality with its hash (compare ObjectId, not pointer). --- Source/Engine/Networking/NetworkReplicator.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index c584d3526..bf874b3c4 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -152,12 +152,12 @@ struct NetworkReplicatedObject bool operator==(const NetworkReplicatedObject& other) const { - return Object == other.Object; + return ObjectId == other.ObjectId; } bool operator==(const ScriptingObject* other) const { - return Object == other; + return other && ObjectId == other->GetID(); } bool operator==(const Guid& other) const @@ -176,6 +176,11 @@ inline uint32 GetHash(const NetworkReplicatedObject& key) return GetHash(key.ObjectId); } +inline uint32 GetHash(const ScriptingObject* key) +{ + return key ? GetHash(key->GetID()) : 0; +} + struct Serializer { NetworkReplicator::SerializeFunc Methods[2]; From 00f9a28729e3c4c0c18efecb99d19dd647592bc4 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 28 Nov 2025 15:51:19 +0100 Subject: [PATCH 10/40] Fixed HashSet compaction count after mid-compact growth Ensure HashSetBase::Compact() preserves _elementsCount even when EnsureCapacity() triggers during compaction. The growth path resets the counter; we now cache the original count and restore it after moving all buckets so Count() stays correct in heavy-collision scenarios. --- Source/Engine/Core/Collections/HashSetBase.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Core/Collections/HashSetBase.h b/Source/Engine/Core/Collections/HashSetBase.h index 96adabf79..58f1702c2 100644 --- a/Source/Engine/Core/Collections/HashSetBase.h +++ b/Source/Engine/Core/Collections/HashSetBase.h @@ -408,6 +408,7 @@ protected: } else { + const int32 elementsCount = _elementsCount; // Rebuild entire table completely const int32 oldSize = _size; AllocationData oldAllocation; @@ -437,6 +438,7 @@ protected: } for (int32 i = 0; i < oldSize; ++i) oldData[i].Free(); + _elementsCount = elementsCount; } _deletedCount = 0; } From 93217da6191cc9f7ca0b4ca9762499bde49ab3d9 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 29 Nov 2025 15:04:11 -0800 Subject: [PATCH 11/40] Add option to merge vertex layout with reference order maintained --- .../Graphics/Shaders/GPUVertexLayout.cpp | 33 +++++++++++++++++-- .../Engine/Graphics/Shaders/GPUVertexLayout.h | 3 +- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index 585568d60..23382673f 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -216,20 +216,21 @@ GPUVertexLayout* GPUVertexLayout::Get(const Span& layouts) return result; } -GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused, bool addMissing, int32 missingSlotOverride) +GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused, bool addMissing, int32 missingSlotOverride, bool referenceOrder) { GPUVertexLayout* result = base ? base : reference; if (base && reference && base != reference) { bool elementsModified = false; Elements newElements = base->GetElements(); + const Elements& refElements = reference->GetElements(); if (removeUnused) { for (int32 i = newElements.Count() - 1; i >= 0; i--) { bool missing = true; const VertexElement& e = newElements.Get()[i]; - for (const VertexElement& ee : reference->GetElements()) + for (const VertexElement& ee : refElements) { if (ee.Type == e.Type) { @@ -247,7 +248,7 @@ GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* } if (addMissing) { - for (const VertexElement& e : reference->GetElements()) + for (const VertexElement& e : refElements) { bool missing = true; for (const VertexElement& ee : base->GetElements()) @@ -282,6 +283,32 @@ GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, GPUVertexLayout* } } } + if (referenceOrder) + { + for (int32 i = 0, j = 0; i < newElements.Count() && j < refElements.Count(); j++) + { + if (newElements[i].Type == refElements[j].Type) + { + // Elements match so move forward + i++; + continue; + } + + // Find reference element in a new list + for (int32 k = i + 1; k < newElements.Count(); k++) + { + if (newElements[k].Type == refElements[j].Type) + { + // Move matching element to the reference position + VertexElement e = newElements[k]; + newElements.RemoveAt(k); + newElements.Insert(i, e); + i++; + break; + } + } + } + } if (elementsModified) result = Get(newElements, true); } diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h index 9d33566fb..04815565b 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.h +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.h @@ -84,8 +84,9 @@ public: /// True to remove elements from base layout that don't exist in a reference layout. /// True to add missing elements to base layout that exist in a reference layout. /// Allows to override the input slot for missing elements. Use value -1 to inherit slot from the reference layout. + /// True to reorder result elements to match the reference layout. For example, if input vertex buffer layout is different than vertex shader then it can match those. /// Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime. - static GPUVertexLayout* Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused = false, bool addMissing = true, int32 missingSlotOverride = -1); + static GPUVertexLayout* Merge(GPUVertexLayout* base, GPUVertexLayout* reference, bool removeUnused = false, bool addMissing = true, int32 missingSlotOverride = -1, bool referenceOrder = false); public: // [GPUResource] From 6a3ce862cb37e9185c0eb644c06906d266c8d268 Mon Sep 17 00:00:00 2001 From: Inertia Date: Mon, 1 Dec 2025 11:19:35 +1100 Subject: [PATCH 12/40] - Add X11 Class hints for easy hooking by WMs for window-specific rules (required to fix some bugs in WMs like Hyprland) --- Source/Engine/Platform/Linux/LinuxWindow.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index a2bd17222..e7996229b 100644 --- a/Source/Engine/Platform/Linux/LinuxWindow.cpp +++ b/Source/Engine/Platform/Linux/LinuxWindow.cpp @@ -18,6 +18,7 @@ #include "Engine/Graphics/PixelFormatSampler.h" #include "Engine/Graphics/Textures/TextureData.h" #include "IncludeX11.h" +#include "ThirdParty/X11/Xutil.h" // ICCCM #define WM_NormalState 1L // window normal state @@ -178,6 +179,20 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings) X11::XSetTransientForHint(display, window, (X11::Window)((LinuxWindow*)settings.Parent)->GetNativePtr()); } + // Provides class hint for WMs like Hyprland to hook onto and apply window rules + X11::XClassHint* classHint = X11::XAllocClassHint(); + if (classHint) + { + const char* className = settings.IsRegularWindow ? "FlexEditor" : "FlaxPopup"; + + classHint->res_name = const_cast(className); + classHint->res_class = const_cast(className); + + X11::XSetClassHint(display, window, classHint); + + XFree(classHint); + } + _dpi = Platform::GetDpi(); _dpiScale = (float)_dpi / (float)DefaultDPI; From 77aea0c69cae0b688bc8643716f5e451c10fb13e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 1 Dec 2025 08:18:54 -0800 Subject: [PATCH 13/40] Fix fatal error reporting from multiple therads to sync and properly log (eg. out of memory) --- Source/Engine/Platform/Base/PlatformBase.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index e472f7e1a..4512a59b3 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -51,6 +51,7 @@ Array> PlatformBase::Users; Delegate PlatformBase::UserAdded; Delegate PlatformBase::UserRemoved; void* OutOfMemoryBuffer = nullptr; +volatile int64 FatalReporting = 0; const Char* ToString(NetworkConnectionType value) { @@ -306,11 +307,20 @@ int32 PlatformBase::GetCacheLineSize() void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType error) { + // Let only one thread to report the error (and wait for it to end to have valid log before crash) +RETRY: + if (Platform::InterlockedCompareExchange(&FatalReporting, 1, 0) != 0) + { + Platform::Sleep(1); + goto RETRY; + } + // Check if is already during fatal state if (Engine::FatalError != FatalErrorType::None) { // Just send one more error to the log and back LOG(Error, "Error after fatal error: {0}", msg); + Platform::AtomicStore(&FatalReporting, 0); return; } @@ -429,6 +439,8 @@ void PlatformBase::Fatal(const StringView& msg, void* context, FatalErrorType er } #endif + Platform::AtomicStore(&FatalReporting, 0); + // Show error message if (Engine::ReportCrash.IsBinded()) Engine::ReportCrash(msg, context); From 02429266b1a2b180b10870a926b6fb0c4508d283 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 3 Dec 2025 05:03:21 -0800 Subject: [PATCH 14/40] Fix `Array::RemoveAtKeepOrder` to avoid memory override with large mem copy --- Source/Engine/Core/Collections/Array.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index 4f660d2a9..919ff8f80 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -658,7 +658,10 @@ public: --_count; T* data = _allocation.Get(); if (index < _count) - Memory::MoveAssignItems(data + index, data + (index + 1), _count - index); + { + for (int32 i = index; i < _count; i++) + data[i] = MoveTemp(data[i + 1]); + } Memory::DestructItems(data + _count, 1); } From 3a798a70fadeefde6b6c32ddd0bca411d70c19a0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 4 Dec 2025 23:29:15 +0100 Subject: [PATCH 15/40] Fix collections capacity growing to use the closest power of two Capacity was incorrectly 2x larger than needed. Added unit test to ensure it stays correct. --- Source/Engine/Core/Memory/Allocation.h | 4 +--- Source/Engine/Tests/TestCollections.cpp | 13 +++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Core/Memory/Allocation.h b/Source/Engine/Core/Memory/Allocation.h index ffd51d96c..f274d37a5 100644 --- a/Source/Engine/Core/Memory/Allocation.h +++ b/Source/Engine/Core/Memory/Allocation.h @@ -18,9 +18,7 @@ namespace AllocationUtils capacity |= capacity >> 8; capacity |= capacity >> 16; uint64 capacity64 = (uint64)(capacity + 1) * 2; - if (capacity64 > MAX_int32) - capacity64 = MAX_int32; - return (int32)capacity64; + return capacity64 >= MAX_int32 ? MAX_int32 : (int32)capacity64 / 2; } // Aligns the input value to the next power of 2 to be used as bigger memory allocation block. diff --git a/Source/Engine/Tests/TestCollections.cpp b/Source/Engine/Tests/TestCollections.cpp index b477140cf..a96c6ce1c 100644 --- a/Source/Engine/Tests/TestCollections.cpp +++ b/Source/Engine/Tests/TestCollections.cpp @@ -27,6 +27,19 @@ void CheckBitArray(const BitArray& array) TEST_CASE("Array") { + SECTION("Test Capacity") + { + // Ensure correct collections capacity growing to meet proper memory usage vs safe slack + CHECK(AllocationUtils::CalculateCapacityGrow(1, 0) == 8); + CHECK(AllocationUtils::CalculateCapacityGrow(7, 0) == 8); + CHECK(AllocationUtils::CalculateCapacityGrow(1, 16) == 16); + CHECK(AllocationUtils::CalculateCapacityGrow(31, 0) == 32); + CHECK(AllocationUtils::CalculateCapacityGrow(32, 0) == 32); + CHECK(AllocationUtils::CalculateCapacityGrow(1000, 0) == 1024); + CHECK(AllocationUtils::CalculateCapacityGrow(1024, 0) == 1024); + CHECK(AllocationUtils::CalculateCapacityGrow(1025, 0) == 2048); + } + SECTION("Test Allocators") { Array a1; From 32bd72fecd591cc25f781cd1c9da60ec20b99b7c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 4 Dec 2025 23:51:07 +0100 Subject: [PATCH 16/40] Minor fix to the game cooker assets summary log of a single asset --- Source/Editor/Cooker/Steps/CookAssetsStep.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index 57654a6b3..c95306505 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -1368,7 +1368,10 @@ bool CookAssetsStep::Perform(CookingData& data) { typeName = e.TypeName; } - LOG(Info, "{0}: {1:>4} assets of total size {2}", typeName, e.Count, Utilities::BytesToText(e.ContentSize)); + if (e.Count == 1) + LOG(Info, "{0}: 1 asset of total size {1}", typeName, Utilities::BytesToText(e.ContentSize)); + else + LOG(Info, "{0}: {1:>4} assets of total size {2}", typeName, e.Count, Utilities::BytesToText(e.ContentSize)); } LOG(Info, ""); } From bd78db72b99a8a85201022df91295ea9516a7e76 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 5 Dec 2025 03:46:28 -0800 Subject: [PATCH 17/40] Add Mono AOT dynamic module preloading to speed up startup time --- Source/Engine/Scripting/Runtime/DotNet.cpp | 59 +++++++++++++++++++--- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index ed662801e..37b906743 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -2137,6 +2137,53 @@ static void* OnMonoDlFallbackClose(void* handle, void* user_data) #endif +#ifdef USE_MONO_AOT_MODULE + +#include "Engine/Threading/ThreadPoolTask.h" +#include "Engine/Engine/EngineService.h" + +class MonoAotPreloadTask : public ThreadPoolTask +{ +public: + bool Run() override; +}; + +// Preloads in-build AOT dynamic module in async +class MonoAotPreloadService : public EngineService +{ +public: + volatile int64 Ready = 0; + void* Library = nullptr; + + MonoAotPreloadService() + : EngineService(TEXT("AOT Preload"), -800) + { + } + + bool Init() override + { + New()->Start(); + return false; + } +}; + +MonoAotPreloadService MonoAotPreloadServiceInstance; + +bool MonoAotPreloadTask::Run() +{ + // Load AOT module + Stopwatch aotModuleLoadStopwatch; + LOG(Info, "Loading Mono AOT module..."); + MonoAotPreloadServiceInstance.Library = Platform::LoadLibrary(TEXT(USE_MONO_AOT_MODULE)); + aotModuleLoadStopwatch.Stop(); + LOG(Info, "Mono AOT module loaded in {0}ms", aotModuleLoadStopwatch.GetMilliseconds()); + + Platform::AtomicStore(&MonoAotPreloadServiceInstance.Ready, 1); + return false; +} + +#endif + bool InitHostfxr() { #if DOTNET_HOST_MONO_DEBUG @@ -2167,10 +2214,12 @@ bool InitHostfxr() #endif #ifdef USE_MONO_AOT_MODULE - // Load AOT module - Stopwatch aotModuleLoadStopwatch; - LOG(Info, "Loading Mono AOT module..."); - void* libAotModule = Platform::LoadLibrary(TEXT(USE_MONO_AOT_MODULE)); + // Wait for AOT module preloading + while (Platform::AtomicRead(&MonoAotPreloadServiceInstance.Ready) == 0) + Platform::Yield(); + + // Initialize AOT module + void* libAotModule = MonoAotPreloadServiceInstance.Library; if (libAotModule == nullptr) { LOG(Error, "Failed to laod Mono AOT module (" TEXT(USE_MONO_AOT_MODULE) ")"); @@ -2193,8 +2242,6 @@ bool InitHostfxr() mono_aot_register_module((void**)modules[i]); } Allocator::Free(modules); - aotModuleLoadStopwatch.Stop(); - LOG(Info, "Mono AOT module loaded in {0}ms", aotModuleLoadStopwatch.GetMilliseconds()); #endif // Setup debugger From 56278b17ee135df748891f426baacb939112e7f3 Mon Sep 17 00:00:00 2001 From: Phantom Date: Sun, 7 Dec 2025 16:53:43 +0100 Subject: [PATCH 18/40] Add Text Color Highlighted on Dropdown --- Source/Engine/UI/GUI/Common/Dropdown.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index 868db4b89..7af0185aa 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -303,6 +303,12 @@ namespace FlaxEngine.GUI [EditorDisplay("Text Style"), EditorOrder(2023), ExpandGroups] public Color TextColor { get; set; } + /// + /// Gets or sets the color used to display highlighted text. + /// + [EditorDisplay("Text Style"), EditorOrder(2023), ExpandGroups] + public Color TextColorHighlighted { get; set; } + /// /// Gets or sets the horizontal text alignment within the control bounds. /// @@ -386,6 +392,7 @@ namespace FlaxEngine.GUI var style = Style.Current; Font = new FontReference(style.FontMedium); TextColor = style.Foreground; + TextColorHighlighted = style.Foreground; BackgroundColor = style.BackgroundNormal; BackgroundColorHighlighted = BackgroundColor; BackgroundColorSelected = BackgroundColor; @@ -749,7 +756,7 @@ namespace FlaxEngine.GUI // Draw text of the selected item var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); - var textColor = TextColor; + var textColor = (IsMouseOver || IsNavFocused) ? TextColorHighlighted : TextColor; string text = _items[_selectedIndex]; string format = TextFormat != null ? TextFormat : null; if (!string.IsNullOrEmpty(format)) From a7e77f6e21f15e2ea2ab92891d2226d256f53169 Mon Sep 17 00:00:00 2001 From: Phantom Date: Sun, 7 Dec 2025 18:23:38 +0100 Subject: [PATCH 19/40] Update CreatePopupItem method -Modify the `TextColour` property to use a dynamic value based on `TextColour` multiplied by `0.9f` instead of a fixed value (`Colour.White * 0.9f`). -Modify the `TextColourHighlighted` property to use the dynamic value of `TextColourHighlighted` instead of a fixed value (`Colour.White`). --- Source/Engine/UI/GUI/Common/Dropdown.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index 7af0185aa..0d24c5491 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -594,8 +594,8 @@ namespace FlaxEngine.GUI X = margin, Size = new Float2(size.X - margin, size.Y), Font = Font, - TextColor = Color.White * 0.9f, - TextColorHighlighted = Color.White, + TextColor = TextColor * 0.9f, + TextColorHighlighted = TextColorHighlighted, HorizontalAlignment = HorizontalAlignment, VerticalAlignment = VerticalAlignment, Text = _items[i], From ed50ce9c90197dca45e90f7cf8b90aad3a4d2242 Mon Sep 17 00:00:00 2001 From: Phantom Date: Sun, 7 Dec 2025 18:48:16 +0100 Subject: [PATCH 20/40] Change Dropdown's EditorOrder from 2023 to 2024 --- Source/Engine/UI/GUI/Common/Dropdown.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index 0d24c5491..a227e5acd 100644 --- a/Source/Engine/UI/GUI/Common/Dropdown.cs +++ b/Source/Engine/UI/GUI/Common/Dropdown.cs @@ -306,7 +306,7 @@ namespace FlaxEngine.GUI /// /// Gets or sets the color used to display highlighted text. /// - [EditorDisplay("Text Style"), EditorOrder(2023), ExpandGroups] + [EditorDisplay("Text Style"), EditorOrder(2024), ExpandGroups] public Color TextColorHighlighted { get; set; } /// From 56066a3212e157598f0a31d2bd33421018107357 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 8 Dec 2025 14:41:55 -0800 Subject: [PATCH 21/40] Porting to a famous blue platform --- .../Graphics/Materials/DeferredMaterialShader.cpp | 1 + .../Graphics/Materials/ForwardMaterialShader.cpp | 5 +++++ .../Graphics/Materials/ParticleMaterialShader.cpp | 5 +++++ Source/Engine/Graphics/RenderBuffers.cpp | 3 ++- Source/Engine/Renderer/PostProcessingPass.cpp | 1 + Source/Engine/Renderer/RenderList.cpp | 3 ++- Source/Engine/Renderer/RenderList.h | 2 +- Source/Engine/Scripting/Runtime/DotNet.cpp | 2 +- Source/Engine/ShadersCompilation/ShaderCompiler.cpp | 11 +++++++++++ Source/Engine/ShadersCompilation/ShaderCompiler.h | 1 + 10 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp index bbcdc8207..7b31a606e 100644 --- a/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeferredMaterialShader.cpp @@ -201,6 +201,7 @@ bool DeferredMaterialShader::Load() psDesc.DepthWriteEnable = true; psDesc.DepthEnable = true; psDesc.DepthFunc = ComparisonFunc::Less; + psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::None; psDesc.HS = nullptr; psDesc.DS = nullptr; GPUShaderProgramVS* instancedDepthPassVS; diff --git a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp index 72ec3c7bd..d8d986163 100644 --- a/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ForwardMaterialShader.cpp @@ -195,5 +195,10 @@ bool ForwardMaterialShader::Load() psDesc.VS = _shader->GetVS("VS_Skinned"); _cache.DepthSkinned.Init(psDesc); +#if PLATFORM_PS5 + // Fix shader binding issues on forward shading materials on PS5 + _drawModes = DrawPass::None; +#endif + return false; } diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp index 6c780cc55..a2c45a6ee 100644 --- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp @@ -264,5 +264,10 @@ bool ParticleMaterialShader::Load() // Lazy initialization _cacheVolumetricFog.Desc.PS = nullptr; +#if PLATFORM_PS5 + // Fix shader binding issues on forward shading materials on PS5 + _drawModes = DrawPass::None; +#endif + return false; } diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index ed49260ec..931312aa5 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -113,7 +113,8 @@ GPUTexture* RenderBuffers::RequestHalfResDepth(GPUContext* context) PixelFormat RenderBuffers::GetOutputFormat() const { - return _useAlpha ? PixelFormat::R16G16B16A16_Float : PixelFormat::R11G11B10_Float; + // TODO: fix incorrect alpha leaking into reflections on PS5 with R11G11B10_Float + return _useAlpha || PLATFORM_PS5 ? PixelFormat::R16G16B16A16_Float : PixelFormat::R11G11B10_Float; } bool RenderBuffers::GetUseAlpha() const diff --git a/Source/Engine/Renderer/PostProcessingPass.cpp b/Source/Engine/Renderer/PostProcessingPass.cpp index a9eba14d2..030541e4c 100644 --- a/Source/Engine/Renderer/PostProcessingPass.cpp +++ b/Source/Engine/Renderer/PostProcessingPass.cpp @@ -375,6 +375,7 @@ void PostProcessingPass::Render(RenderContext& renderContext, GPUTexture* input, RENDER_TARGET_POOL_SET_NAME(bloomBuffer1, "PostProcessing.Bloom"); RENDER_TARGET_POOL_SET_NAME(bloomBuffer2, "PostProcessing.Bloom"); + // TODO: skip this clear? or do it at once for the whole textures (2 calls instead of per-mip) for (int32 mip = 0; mip < bloomMipCount; mip++) { context->Clear(bloomBuffer1->View(0, mip), Color::Transparent); diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index bb580aaf3..ace6f9342 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -917,6 +917,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL perDraw.DrawPadding = Float3::Zero; GPUConstantBuffer* perDrawCB = IMaterial::BindParameters::PerDrawConstants; context->BindCB(2, perDrawCB); // TODO: use rootSignature/pushConstants on D3D12/Vulkan + context->UpdateCB(perDrawCB, &perDraw); constexpr int32 vbMax = ARRAY_COUNT(DrawCall::Geometry.VertexBuffers); if (useInstancing) { @@ -1057,7 +1058,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL materialBinds += list.PreBatchedDrawCalls.Count(); if (list.Batches.IsEmpty() && list.Indices.Count() != 0) { - // Draw calls list has bot been batched so execute draw calls separately + // Draw calls list has not been batched so execute draw calls separately for (int32 j = 0; j < list.Indices.Count(); j++) { perDraw.DrawObjectIndex = listData[j]; diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index 7a3ac867a..8eb3540e0 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -273,7 +273,7 @@ struct DrawCallsList /// /// True if draw calls batches list can be rendered using hardware instancing, otherwise false. /// - bool CanUseInstancing; + bool CanUseInstancing = true; void Clear(); bool IsEmpty() const; diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 37b906743..1c8c2bcdd 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -2173,7 +2173,7 @@ bool MonoAotPreloadTask::Run() { // Load AOT module Stopwatch aotModuleLoadStopwatch; - LOG(Info, "Loading Mono AOT module..."); + //LOG(Info, "Loading Mono AOT module..."); MonoAotPreloadServiceInstance.Library = Platform::LoadLibrary(TEXT(USE_MONO_AOT_MODULE)); aotModuleLoadStopwatch.Stop(); LOG(Info, "Mono AOT module loaded in {0}ms", aotModuleLoadStopwatch.GetMilliseconds()); diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp index b6b859afa..cfb08b925 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.cpp +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.cpp @@ -278,6 +278,17 @@ bool ShaderCompiler::WriteShaderFunctionPermutation(ShaderCompilationContext* co return false; } +bool ShaderCompiler::WriteShaderFunctionPermutation(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, const void* header, int32 headerSize, const void* cache1, int32 cache1Size, const void* cache2, int32 cache2Size) +{ + auto output = context->Output; + output->Write((uint32)(cache1Size + cache2Size + headerSize)); + output->WriteBytes(header, headerSize); + output->WriteBytes(cache1, cache1Size); + output->WriteBytes(cache2, cache2Size); + output->Write(bindings); + return false; +} + bool ShaderCompiler::WriteShaderFunctionPermutation(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, const void* cache, int32 cacheSize) { auto output = context->Output; diff --git a/Source/Engine/ShadersCompilation/ShaderCompiler.h b/Source/Engine/ShadersCompilation/ShaderCompiler.h index 36fd592e6..9666d8f13 100644 --- a/Source/Engine/ShadersCompilation/ShaderCompiler.h +++ b/Source/Engine/ShadersCompilation/ShaderCompiler.h @@ -108,6 +108,7 @@ protected: static bool WriteShaderFunctionBegin(ShaderCompilationContext* context, ShaderFunctionMeta& meta); static bool WriteShaderFunctionPermutation(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, const void* header, int32 headerSize, const void* cache, int32 cacheSize); + static bool WriteShaderFunctionPermutation(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, const void* header, int32 headerSize, const void* cache1, int32 cache1Size, const void* cache2, int32 cache2Size); static bool WriteShaderFunctionPermutation(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const ShaderBindings& bindings, const void* cache, int32 cacheSize); static bool WriteShaderFunctionEnd(ShaderCompilationContext* context, ShaderFunctionMeta& meta); static bool WriteCustomDataVS(ShaderCompilationContext* context, ShaderFunctionMeta& meta, int32 permutationIndex, const Array& macros, void* additionalData); From 5c81c7111697d1216f3d704e7c19f3930071c4b8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 9 Dec 2025 09:51:53 +0100 Subject: [PATCH 22/40] Move constant buffer init for instanced draws only, others do it in all paths --- Source/Engine/Renderer/RenderList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index ace6f9342..544438bb5 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -917,10 +917,10 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL perDraw.DrawPadding = Float3::Zero; GPUConstantBuffer* perDrawCB = IMaterial::BindParameters::PerDrawConstants; context->BindCB(2, perDrawCB); // TODO: use rootSignature/pushConstants on D3D12/Vulkan - context->UpdateCB(perDrawCB, &perDraw); constexpr int32 vbMax = ARRAY_COUNT(DrawCall::Geometry.VertexBuffers); if (useInstancing) { + context->UpdateCB(perDrawCB, &perDraw); GPUBuffer* vb[vbMax + 1]; uint32 vbOffsets[vbMax + 1]; vb[3] = _instanceBuffer.GetBuffer(); // Pass object index in a vertex stream at slot 3 (used by VS in Surface.shader) From 0a8752ec0af77f01065a4dc56f464f22e2906edd Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Dec 2025 09:48:27 +0100 Subject: [PATCH 23/40] Fix cross-building building engine with separate executable and library for Unix platforms on Windows --- Source/Tools/Flax.Build/Build/EngineTarget.cs | 3 ++- Source/Tools/Flax.Build/Build/Graph/TaskGraph.cs | 9 ++------- Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs | 3 ++- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/EngineTarget.cs b/Source/Tools/Flax.Build/Build/EngineTarget.cs index 039f9fb16..e22a924d9 100644 --- a/Source/Tools/Flax.Build/Build/EngineTarget.cs +++ b/Source/Tools/Flax.Build/Build/EngineTarget.cs @@ -217,7 +217,8 @@ namespace Flax.Build var engineLibraryType = LinkerOutput.SharedLibrary; if (buildOptions.Toolchain?.Compiler == TargetCompiler.MSVC) engineLibraryType = LinkerOutput.ImportLibrary; // MSVC links DLL against import library - exeBuildOptions.LinkEnv.InputLibraries.Add(Path.Combine(buildOptions.OutputFolder, buildOptions.Platform.GetLinkOutputFileName(LibraryName, engineLibraryType))); + var engineLibraryPath = Utilities.NormalizePath(Path.Combine(buildOptions.OutputFolder, buildOptions.Platform.GetLinkOutputFileName(LibraryName, engineLibraryType))); + exeBuildOptions.LinkEnv.InputLibraries.Add(engineLibraryPath); exeBuildOptions.LinkEnv.InputFiles.AddRange(mainModuleOptions.OutputFiles); exeBuildOptions.DependencyFiles.AddRange(mainModuleOptions.DependencyFiles); exeBuildOptions.NugetPackageReferences.AddRange(mainModuleOptions.NugetPackageReferences); diff --git a/Source/Tools/Flax.Build/Build/Graph/TaskGraph.cs b/Source/Tools/Flax.Build/Build/Graph/TaskGraph.cs index bd0ef2ad5..6c459b33e 100644 --- a/Source/Tools/Flax.Build/Build/Graph/TaskGraph.cs +++ b/Source/Tools/Flax.Build/Build/Graph/TaskGraph.cs @@ -134,7 +134,7 @@ namespace Flax.Build.Graph } /// - /// Performs tasks list sorting based on task dependencies and cost heuristics to to improve parallelism of the graph execution. + /// Performs tasks list sorting based on task dependencies and cost heuristics to improve parallelism of the graph execution. /// public void SortTasks() { @@ -149,12 +149,7 @@ namespace Flax.Build.Graph { if (FileToProducingTaskMap.TryGetValue(prerequisiteFile, out var prerequisiteTask)) { - HashSet dependentTasks; - if (taskToDependentActionsMap.ContainsKey(prerequisiteTask)) - { - dependentTasks = taskToDependentActionsMap[prerequisiteTask]; - } - else + if (!taskToDependentActionsMap.TryGetValue(prerequisiteTask, out var dependentTasks)) { dependentTasks = new HashSet(); taskToDependentActionsMap[prerequisiteTask] = dependentTasks; diff --git a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs index fcb4397a1..1d623c568 100644 --- a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs @@ -605,7 +605,8 @@ namespace Flax.Build.Platforms /// public override void LinkFiles(TaskGraph graph, BuildOptions options, string outputFilePath) { - outputFilePath = outputFilePath.Replace('\\', '/'); + //outputFilePath = outputFilePath.Replace('\\', '/'); + outputFilePath = Utilities.NormalizePath(outputFilePath.Replace('\\', '/')); Task linkTask; switch (options.LinkEnv.Output) From 3b9b49950ccdc77ecf2942ba647796e97254e17a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Dec 2025 09:48:47 +0100 Subject: [PATCH 24/40] Fixes for Xbox One --- Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp | 6 ++++++ .../app/src/main/java/com/flaxengine/GameActivity.java | 2 +- Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp index 62070ecda..4b9298b6c 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUDeviceDX12.cpp @@ -628,6 +628,7 @@ bool GPUDeviceDX12::Init() VALIDATE_DIRECTX_CALL(dxgiAdapter->EnumOutputs(0, dxgiOutput.GetAddressOf())); DXGI_FORMAT backbufferFormat = RenderToolsDX::ToDxgiFormat(GPU_BACK_BUFFER_PIXEL_FORMAT); UINT modesCount = 0; +#ifdef _GAMING_XBOX_SCARLETT VALIDATE_DIRECTX_CALL(dxgiOutput->GetDisplayModeList(backbufferFormat, 0, &modesCount, NULL)); Array modes; modes.Resize((int32)modesCount); @@ -642,6 +643,11 @@ bool GPUDeviceDX12::Init() videoOutput.RefreshRate = Math::Max(videoOutput.RefreshRate, mode.RefreshRate.Numerator / (float)mode.RefreshRate.Denominator); } modes.Resize(0); +#else + videoOutput.Width = 1920; + videoOutput.Height = 1080; + videoOutput.RefreshRate = 60; +#endif #if PLATFORM_GDK GDKPlatform::Suspended.Bind(this); diff --git a/Source/Platforms/Android/Binaries/Project/app/src/main/java/com/flaxengine/GameActivity.java b/Source/Platforms/Android/Binaries/Project/app/src/main/java/com/flaxengine/GameActivity.java index b19663fc9..ecb27b013 100644 --- a/Source/Platforms/Android/Binaries/Project/app/src/main/java/com/flaxengine/GameActivity.java +++ b/Source/Platforms/Android/Binaries/Project/app/src/main/java/com/flaxengine/GameActivity.java @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. +// Copyright (c) Wojciech Figat. All rights reserved. package com.flaxengine; diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs b/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs index 4d6a1aab6..66909f6b9 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/nethost.cs @@ -94,6 +94,7 @@ namespace Flax.Deps.Dependencies defines += "-DDISABLE_EXECUTABLES=1-DDISABLE_SHARED_LIBS=1"; buildArgs = $" -subset mono+libs -cmakeargs \"{defines}\" /p:FeaturePerfTracing=false /p:FeatureWin32Registry=false /p:FeatureCominteropApartmentSupport=false /p:FeatureManagedEtw=false /p:FeatureManagedEtwChannels=false /p:FeatureEtw=false /p:ApiCompatValidateAssemblies=false"; envVars.Add("_GAMING_XBOX", "1"); + envVars.Add(targetPlatform == TargetPlatform.XboxScarlett ? "_GAMING_XBOX_SCARLETT" : "_GAMING_XBOX_XBOXONE", "1"); break; case TargetPlatform.Linux: os = "linux"; From 43665aa7eb7cef67ecf4e9d80f632c3717314117 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Dec 2025 13:00:59 +0100 Subject: [PATCH 25/40] Rename `GPUContext::ClearState` to `ResetState` for constentency --- Source/Engine/Graphics/GPUContext.cpp | 2 +- Source/Engine/Graphics/GPUContext.h | 11 ++++++++++- .../GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp | 2 +- .../GraphicsDevice/DirectX/DX11/GPUContextDX11.h | 2 +- .../GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp | 2 +- .../GraphicsDevice/DirectX/DX12/GPUContextDX12.h | 2 +- Source/Engine/GraphicsDevice/Null/GPUContextNull.h | 2 +- .../Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp | 2 +- .../Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h | 2 +- Source/Engine/Renderer/ProbesRenderer.cpp | 4 ++-- Source/Engine/Renderer/Renderer.cpp | 2 +- Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp | 4 ++-- 12 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Source/Engine/Graphics/GPUContext.cpp b/Source/Engine/Graphics/GPUContext.cpp index 55f87ba3b..905919794 100644 --- a/Source/Engine/Graphics/GPUContext.cpp +++ b/Source/Engine/Graphics/GPUContext.cpp @@ -67,7 +67,7 @@ void GPUContext::FrameBegin() void GPUContext::FrameEnd() { - ClearState(); + ResetState(); FlushState(); } diff --git a/Source/Engine/Graphics/GPUContext.h b/Source/Engine/Graphics/GPUContext.h index 4f1306567..5f4bd8020 100644 --- a/Source/Engine/Graphics/GPUContext.h +++ b/Source/Engine/Graphics/GPUContext.h @@ -618,7 +618,16 @@ public: /// /// Clears the context state. /// - API_FUNCTION() virtual void ClearState() = 0; + DEPRECATED("Use ResetState instead") + API_FUNCTION() void ClearState() + { + ResetState(); + } + + /// + /// Resets the context state. + /// + API_FUNCTION() virtual void ResetState() = 0; /// /// Flushes the internal cached context state with a command buffer. diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp index a0ec80bb1..f623f53b5 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.cpp @@ -724,7 +724,7 @@ void GPUContextDX11::SetState(GPUPipelineState* state) } } -void GPUContextDX11::ClearState() +void GPUContextDX11::ResetState() { if (!_context) return; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h index 7dc693019..eee2699df 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX11/GPUContextDX11.h @@ -158,7 +158,7 @@ public: void SetScissor(const Rectangle& scissorRect) override; GPUPipelineState* GetState() const override; void SetState(GPUPipelineState* state) override; - void ClearState() override; + void ResetState() override; void FlushState() override; void Flush() override; void UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 size, uint32 offset) override; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp index 6752a9b8d..98143c7c3 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.cpp @@ -1304,7 +1304,7 @@ void GPUContextDX12::SetState(GPUPipelineState* state) } } -void GPUContextDX12::ClearState() +void GPUContextDX12::ResetState() { if (!_commandList) return; diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h index 4bd1b54a1..51f24f4a6 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUContextDX12.h @@ -201,7 +201,7 @@ public: void SetScissor(const Rectangle& scissorRect) override; GPUPipelineState* GetState() const override; void SetState(GPUPipelineState* state) override; - void ClearState() override; + void ResetState() override; void FlushState() override; void Flush() override; void UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 size, uint32 offset) override; diff --git a/Source/Engine/GraphicsDevice/Null/GPUContextNull.h b/Source/Engine/GraphicsDevice/Null/GPUContextNull.h index 0ea111d24..22786c157 100644 --- a/Source/Engine/GraphicsDevice/Null/GPUContextNull.h +++ b/Source/Engine/GraphicsDevice/Null/GPUContextNull.h @@ -177,7 +177,7 @@ public: { } - void ClearState() override + void ResetState() override { } diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp index c374bbeed..979ccc0f8 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.cpp @@ -1329,7 +1329,7 @@ void GPUContextVulkan::SetState(GPUPipelineState* state) } } -void GPUContextVulkan::ClearState() +void GPUContextVulkan::ResetState() { ResetRenderTarget(); ResetSR(); diff --git a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h index 8ed541089..fa94aa139 100644 --- a/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h +++ b/Source/Engine/GraphicsDevice/Vulkan/GPUContextVulkan.h @@ -193,7 +193,7 @@ public: void SetScissor(const Rectangle& scissorRect) override; GPUPipelineState* GetState() const override; void SetState(GPUPipelineState* state) override; - void ClearState() override; + void ResetState() override; void FlushState() override; void Flush() override; void UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 size, uint32 offset) override; diff --git a/Source/Engine/Renderer/ProbesRenderer.cpp b/Source/Engine/Renderer/ProbesRenderer.cpp index eaf7a53ca..4c0a43cb6 100644 --- a/Source/Engine/Renderer/ProbesRenderer.cpp +++ b/Source/Engine/Renderer/ProbesRenderer.cpp @@ -509,7 +509,7 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context) // Render frame Renderer::Render(_task); - context->ClearState(); + context->ResetState(); // Copy frame to cube face { @@ -568,7 +568,7 @@ void ProbesRendererService::OnRender(RenderTask* task, GPUContext* context) } // Cleanup - context->ClearState(); + context->ResetState(); if (_workStep < 7) return; // Continue rendering next frame diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 278c8237d..7a72cd923 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -200,7 +200,7 @@ void Renderer::Render(SceneRenderTask* task) // Prepare GPU context auto context = GPUDevice::Instance->GetMainContext(); - context->ClearState(); + context->ResetState(); context->FlushState(); const Viewport viewport = task->GetViewport(); context->SetViewportAndScissors(viewport); diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp index a66a0e283..b77ee51b6 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp @@ -376,7 +376,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) EnableLightmapsUsage = _giBounceRunningIndex != 0; // Renderer::Render(_task); - context->ClearState(); + context->ResetState(); // IsRunningRadiancePass = false; EnableLightmapsUsage = true; @@ -515,7 +515,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) } // Cleanup after rendering - context->ClearState(); + context->ResetState(); // Mark job as done Platform::AtomicStore(&_wasJobDone, 1); From 20a7fcf6a0accd31cf1ba4fa43a4bff379d90096 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Dec 2025 13:01:24 +0100 Subject: [PATCH 26/40] Add profiler wait event for GPU wait on D3D12 --- Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp index 81fef4965..a384b6383 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/CommandQueueDX12.cpp @@ -143,6 +143,8 @@ void CommandQueueDX12::WaitForFence(uint64 fenceValue) void CommandQueueDX12::WaitForGPU() { + PROFILE_CPU(); + ZoneColor(TracyWaitZoneColor); const uint64 value = _fence.Signal(this); _fence.WaitCPU(value); } From ca52122656fc4312303e6b423b70ebc310fd4fd5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Dec 2025 14:53:51 +0100 Subject: [PATCH 27/40] Fix validation error on Windows for textures but optimize buffers instead --- Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp | 2 +- Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp index 0d9ff88d4..6f3ff5fba 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUBufferDX12.cpp @@ -137,7 +137,7 @@ bool GPUBufferDX12::OnInit() // Create resource ID3D12Resource* resource; #if PLATFORM_WINDOWS - D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_CREATE_NOT_ZEROED; + D3D12_HEAP_FLAGS heapFlags = EnumHasAnyFlags(_desc.Flags, GPUBufferFlags::VertexBuffer | GPUBufferFlags::IndexBuffer) || _desc.InitData ? D3D12_HEAP_FLAG_CREATE_NOT_ZEROED : D3D12_HEAP_FLAG_NONE; #else D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_NONE; #endif diff --git a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp index e7e4c5ca2..a4fbc683d 100644 --- a/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp +++ b/Source/Engine/GraphicsDevice/DirectX/DX12/GPUTextureDX12.cpp @@ -159,7 +159,7 @@ bool GPUTextureDX12::OnInit() initialState = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; // Create texture -#if PLATFORM_WINDOWS +#if PLATFORM_WINDOWS && 0 D3D12_HEAP_FLAGS heapFlags = useRTV || useDSV ? D3D12_HEAP_FLAG_CREATE_NOT_ZEROED : D3D12_HEAP_FLAG_NONE; #else D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_NONE; From a63b97d31d63674c1893168de736193bccf48e17 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Dec 2025 14:58:12 +0100 Subject: [PATCH 28/40] Add stripping DXIL debug data from the shader cache when not used --- .../DirectX/ShaderCompilerDX.cpp | 38 +++++++++++++++---- .../DirectX/ShaderCompilerDX.h | 1 + 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp index ca04c916d..7dd7182cf 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.cpp @@ -76,16 +76,19 @@ ShaderCompilerDX::ShaderCompilerDX(ShaderProfile profile, PlatformType platform, { IDxcCompiler3* compiler = nullptr; IDxcLibrary* library = nullptr; + IDxcContainerBuilder* builder = nullptr; IDxcContainerReflection* containerReflection = nullptr; DxcCreateInstanceProc createInstance = dxcCreateInstanceProc ? (DxcCreateInstanceProc)dxcCreateInstanceProc : &DxcCreateInstance; if (FAILED(createInstance(CLSID_DxcCompiler, __uuidof(compiler), reinterpret_cast(&compiler))) || FAILED(createInstance(CLSID_DxcLibrary, __uuidof(library), reinterpret_cast(&library))) || + FAILED(createInstance(CLSID_DxcContainerBuilder, __uuidof(builder), reinterpret_cast(&builder))) || FAILED(createInstance(CLSID_DxcContainerReflection, __uuidof(containerReflection), reinterpret_cast(&containerReflection)))) { LOG(Error, "DxcCreateInstance failed"); } _compiler = compiler; _library = library; + _builder = builder; _containerReflection = containerReflection; static HashSet PrintVersions; if (PrintVersions.Add(createInstance)) @@ -103,14 +106,13 @@ ShaderCompilerDX::ShaderCompilerDX(ShaderProfile profile, PlatformType platform, ShaderCompilerDX::~ShaderCompilerDX() { - auto compiler = (IDxcCompiler2*)_compiler; - if (compiler) + if (auto compiler = (IDxcCompiler2*)_compiler) compiler->Release(); - auto library = (IDxcLibrary*)_library; - if (library) + if (auto library = (IDxcLibrary*)_library) library->Release(); - auto containerReflection = (IDxcContainerReflection*)_containerReflection; - if (containerReflection) + if (auto builder = (IDxcContainerBuilder*)_builder) + builder->Release(); + if (auto containerReflection = (IDxcContainerReflection*)_containerReflection) containerReflection->Release(); } @@ -254,7 +256,7 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD } // Get the output - ComPtr shaderBuffer = nullptr; + ComPtr shaderBuffer; if (FAILED(results->GetResult(&shaderBuffer))) { LOG(Error, "IDxcOperationResult::GetResult failed."); @@ -460,6 +462,28 @@ bool ShaderCompilerDX::CompileShader(ShaderFunctionMeta& meta, WritePermutationD } } + // Strip reflection data + if (!options->GenerateDebugData) + { + if (auto builder = (IDxcContainerBuilder*)_builder) + { + if (builder->Load(shaderBuffer) == S_OK) + { + builder->RemovePart(DXC_PART_PDB); + builder->RemovePart(DXC_PART_REFLECTION_DATA); + ComPtr serializeResult; + if (builder->SerializeContainer(&serializeResult) == S_OK) + { + ComPtr optimizedShaderBuffer; + if (SUCCEEDED(serializeResult->GetResult(&optimizedShaderBuffer))) + { + shaderBuffer = optimizedShaderBuffer; + } + } + } + } + } + if (WriteShaderFunctionPermutation(_context, meta, permutationIndex, bindings, &header, sizeof(header), shaderBuffer->GetBufferPointer(), (int32)shaderBuffer->GetBufferSize())) return true; diff --git a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h index bf522cf3b..0b758cd3f 100644 --- a/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h +++ b/Source/Engine/ShadersCompilation/DirectX/ShaderCompilerDX.h @@ -15,6 +15,7 @@ private: Array _funcNameDefineBuffer; void* _compiler; void* _library; + void* _builder; void* _containerReflection; public: From 02cff3973a5fa7f6a92d6a4875b8e9163f12bd68 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Dec 2025 15:01:53 +0100 Subject: [PATCH 29/40] Bump up engine version --- Flax.flaxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index 5123e5b8f..100d4e9ff 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -4,7 +4,7 @@ "Major": 1, "Minor": 11, "Revision": 0, - "Build": 6804 + "Build": 6805 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2025 Wojciech Figat. All rights reserved.", From c39c642b608ce15c431a5db43d6976dc374ff3cb Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Dec 2025 17:39:18 +0100 Subject: [PATCH 30/40] Add safety check for invalid math values in shader graph generation --- Source/Engine/Visject/ShaderGraph.cpp | 21 ++++++++++++++++++++- Source/Engine/Visject/ShaderGraph.h | 2 ++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp index 688e46382..faf344263 100644 --- a/Source/Engine/Visject/ShaderGraph.cpp +++ b/Source/Engine/Visject/ShaderGraph.cpp @@ -146,6 +146,8 @@ void ShaderGenerator::ProcessGroupMath(Box* box, Node* node, Value& value) Box* b2 = node->GetBox(1); Value v1 = tryGetValue(b1, 0, Value::Zero); Value v2 = tryGetValue(b2, 1, Value::Zero); + if (SanitizeMathValue(v1, node, b1, &value)) + break; if (b1->HasConnection()) v2 = v2.Cast(v1.Type); else @@ -251,7 +253,10 @@ void ShaderGenerator::ProcessGroupMath(Box* box, Node* node, Value& value) // Lerp case 25: { - Value a = tryGetValue(node->GetBox(0), 0, Value::Zero); + auto boxA = node->GetBox(0); + Value a = tryGetValue(boxA, 0, Value::Zero); + if (SanitizeMathValue(a, node, boxA, &value)) + break; Value b = tryGetValue(node->GetBox(1), 1, Value::One).Cast(a.Type); Value alpha = tryGetValue(node->GetBox(2), 2, Value::Zero).Cast(ValueType::Float); String text = String::Format(TEXT("lerp({0}, {1}, {2})"), a.Value, b.Value, alpha.Value); @@ -1364,6 +1369,20 @@ SerializedMaterialParam& ShaderGenerator::findOrAddGlobalSDF() return param; } +bool ShaderGenerator::SanitizeMathValue(Value& value, Node* node, Box* box, Value* resultOnInvalid) +{ + bool invalid = value.Type == VariantType::Object; + if (invalid) + { + OnError(node, box, TEXT("Invalid input type for math operation")); + if (resultOnInvalid) + *resultOnInvalid = Value::Zero; + else + value = Value::Zero; + } + return invalid; +} + String ShaderGenerator::getLocalName(int32 index) { return TEXT("local") + StringUtils::ToString(index); diff --git a/Source/Engine/Visject/ShaderGraph.h b/Source/Engine/Visject/ShaderGraph.h index ab0e7d405..5b17604d0 100644 --- a/Source/Engine/Visject/ShaderGraph.h +++ b/Source/Engine/Visject/ShaderGraph.h @@ -255,6 +255,8 @@ protected: SerializedMaterialParam& findOrAddTextureGroupSampler(int32 index); SerializedMaterialParam& findOrAddGlobalSDF(); + bool SanitizeMathValue(Value& value, Node* node, Box* box, Value* resultOnInvalid = nullptr); + static String getLocalName(int32 index); static String getParamName(int32 index); }; From 9f07a2a54ed75701dba16031210475ed75d4db22 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 10 Dec 2025 18:58:43 +0100 Subject: [PATCH 31/40] Attempt to fix regression from 32bd72fecd591cc25f781cd1c9da60ec20b99b7c --- Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs index 1d623c568..b6e2d7998 100644 --- a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs @@ -518,7 +518,7 @@ namespace Flax.Build.Platforms var args = new List(); args.AddRange(options.LinkEnv.CustomArgs); { - args.Add(string.Format("-o \"{0}\"", outputFilePath)); + args.Add(string.Format("-o \"{0}\"", outputFilePath.Replace('\\', '/'))); if (!options.LinkEnv.DebugInformation) { @@ -605,8 +605,7 @@ namespace Flax.Build.Platforms /// public override void LinkFiles(TaskGraph graph, BuildOptions options, string outputFilePath) { - //outputFilePath = outputFilePath.Replace('\\', '/'); - outputFilePath = Utilities.NormalizePath(outputFilePath.Replace('\\', '/')); + outputFilePath = Utilities.NormalizePath(outputFilePath); Task linkTask; switch (options.LinkEnv.Output) From b5286af52626c096cba06ea9575ce0af305f7f7b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 11 Dec 2025 14:48:18 +0100 Subject: [PATCH 32/40] Attempt to fix regression from 32bd72fecd591cc25f781cd1c9da60ec20b99b7c --- .../Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index fe5abe4a3..583b93b55 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -778,6 +778,7 @@ namespace Flax.Build.Platforms /// public override void LinkFiles(TaskGraph graph, BuildOptions options, string outputFilePath) { + outputFilePath = Utilities.NormalizePath(outputFilePath); var linkEnvironment = options.LinkEnv; var task = graph.Add(); From 71391cf1cc890fc96732391c6dae767cc91f3622 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 11 Dec 2025 16:38:28 +0100 Subject: [PATCH 33/40] Fix deprecated tag placement --- Source/Engine/Graphics/GPUContext.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Graphics/GPUContext.h b/Source/Engine/Graphics/GPUContext.h index 5f4bd8020..1144d6f49 100644 --- a/Source/Engine/Graphics/GPUContext.h +++ b/Source/Engine/Graphics/GPUContext.h @@ -189,7 +189,7 @@ public: /// [Deprecated in v1.10] /// /// true if depth buffer is binded; otherwise, false. - DEPRECATED("IsDepthBufferBinded has been deprecated and will be removed in ") + DEPRECATED("IsDepthBufferBinded has been deprecated and will be removed in future") virtual bool IsDepthBufferBinded() = 0; public: @@ -617,9 +617,9 @@ public: /// /// Clears the context state. + /// [Deprecated in v1.12] /// - DEPRECATED("Use ResetState instead") - API_FUNCTION() void ClearState() + API_FUNCTION() DEPRECATED("Use ResetState instead") void ClearState() { ResetState(); } From 82bd91527440dafe28fc841f06852778974f1843 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 12 Dec 2025 14:44:24 +0200 Subject: [PATCH 34/40] Fix out-of-bounds write while parsing command-line arguments --- Source/Engine/Main/MainUtil.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Main/MainUtil.h b/Source/Engine/Main/MainUtil.h index 668c69c3e..9e1a1503e 100644 --- a/Source/Engine/Main/MainUtil.h +++ b/Source/Engine/Main/MainUtil.h @@ -16,7 +16,7 @@ const Char* GetCommandLine(int argc, char* argv[]) const Char* cmdLine; if (length != 0) { - Char* str = (Char*)malloc(length * sizeof(Char)); + Char* str = (Char*)malloc((length + 1) * sizeof(Char)); cmdLine = str; for (int i = 1; i < argc; i++) { From 2a53d0a46217c76e98ed86a79d9e60eb4e735fd3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 13 Dec 2025 02:10:41 +0100 Subject: [PATCH 35/40] Fix crash on Visual Script missing asset ref after hot-reload in Editor #3823 --- Source/Editor/Content/Items/VisualScriptItem.cs | 11 +++++++++-- Source/Editor/Scripting/TypeUtils.cs | 2 ++ Source/Editor/Surface/SurfaceUtils.cs | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs index 8eea041ce..5b2f505d7 100644 --- a/Source/Editor/Content/Items/VisualScriptItem.cs +++ b/Source/Editor/Content/Items/VisualScriptItem.cs @@ -281,6 +281,13 @@ namespace FlaxEditor.Content private void CacheData() { + if (!_asset) + { + _parameters = Utils.GetEmptyArray(); + _methods = Utils.GetEmptyArray(); + _attributes = Utils.GetEmptyArray(); + return; + } if (_parameters != null) return; if (_asset.WaitForLoaded()) @@ -344,13 +351,13 @@ namespace FlaxEditor.Content } /// - public string Name => Path.GetFileNameWithoutExtension(_asset.Path); + public string Name => _asset ? Path.GetFileNameWithoutExtension(_asset.Path) : null; /// public string Namespace => string.Empty; /// - public string TypeName => JsonSerializer.GetStringID(_asset.ID); + public string TypeName => _asset ? JsonSerializer.GetStringID(_asset.ID) : null; /// public bool IsPublic => true; diff --git a/Source/Editor/Scripting/TypeUtils.cs b/Source/Editor/Scripting/TypeUtils.cs index 2451e1d87..3537b6218 100644 --- a/Source/Editor/Scripting/TypeUtils.cs +++ b/Source/Editor/Scripting/TypeUtils.cs @@ -406,6 +406,8 @@ namespace FlaxEngine.Utilities { if (type == ScriptType.Null) return null; + if (type.BaseType == null) + return type.Type; while (type.Type == null) type = type.BaseType; return type.Type; diff --git a/Source/Editor/Surface/SurfaceUtils.cs b/Source/Editor/Surface/SurfaceUtils.cs index d75efb5a0..7a19567fa 100644 --- a/Source/Editor/Surface/SurfaceUtils.cs +++ b/Source/Editor/Surface/SurfaceUtils.cs @@ -400,7 +400,7 @@ namespace FlaxEditor.Surface return scriptType.GetGenericTypeDefinition() == typeof(Dictionary<,>); } var managedType = TypeUtils.GetType(scriptType); - return !TypeUtils.IsDelegate(managedType); + return managedType != null && !TypeUtils.IsDelegate(managedType); } internal static bool IsValidVisualScriptFunctionType(ScriptType scriptType) @@ -408,7 +408,7 @@ namespace FlaxEditor.Surface if (scriptType.IsGenericType || scriptType.IsStatic || !scriptType.IsPublic || scriptType.HasAttribute(typeof(HideInEditorAttribute), true)) return false; var managedType = TypeUtils.GetType(scriptType); - return !TypeUtils.IsDelegate(managedType); + return managedType != null && !TypeUtils.IsDelegate(managedType); } internal static string GetVisualScriptTypeDescription(ScriptType type) From 0c887cd29eaa3a886ca6bf3e72e967b4111d960d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 13 Dec 2025 23:11:01 +0100 Subject: [PATCH 36/40] Use fix from #3830 in particle and anim graphs too --- Source/Engine/Animations/Graph/AnimGroup.Animation.cpp | 8 +++++++- .../Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp | 8 +++++++- .../MaterialGenerator/MaterialGenerator.Material.cpp | 5 +---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index fd62ec537..a1f2a4565 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -2553,9 +2553,15 @@ void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& va // Function Input case 1: { + // Skip when graph is too small (eg. preview) and fallback with default value from the function graph + if (context.GraphStack.Count() < 2) + { + value = tryGetValue(node->TryGetBox(1), Value::Zero); + break; + } + // Find the function call AnimGraphNode* functionCallNode = nullptr; - ASSERT(context.GraphStack.Count() >= 2); Graph* graph; for (int32 i = context.CallStack.Count() - 1; i >= 0; i--) { diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index a47e15275..1a7ba37d1 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -482,9 +482,15 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupFunction(Box* box, Node* node, // Function Input case 1: { + // Skip when graph is too small (eg. preview) and fallback with default value from the function graph + if (context.GraphStack.Count() < 2) + { + value = tryGetValue(node->TryGetBox(1), Value::Zero); + break; + } + // Find the function call Node* functionCallNode = nullptr; - ASSERT(context.GraphStack.Count() >= 2); ParticleEmitterGraphCPU* graph; for (int32 i = context.CallStackSize - 1; i >= 0; i--) { diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index bdade721e..09ac78e6b 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -790,7 +790,7 @@ void MaterialGenerator::ProcessGroupFunction(Box* box, Node* node, Value& value) // Function Input case 1: { - // Check the stack count.If only 1 graph is present, + // Check the stack count. If only 1 graph is present, // we are processing the graph in isolation (e.g., in the Editor Preview). // In this case, we skip the caller-finding logic and use the node's default value. if (_graphStack.Count() < 2) @@ -802,9 +802,6 @@ void MaterialGenerator::ProcessGroupFunction(Box* box, Node* node, Value& value) // Find the function call Node* functionCallNode = nullptr; - - // The original ASSERT has been effectively replaced by the 'if' above. - //ASSERT(_graphStack.Count() >= 2); Graph* graph; for (int32 i = _callStack.Count() - 1; i >= 0; i--) { From 0e627577fc1d039942b1fa10bd2bb4292c2ec377 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 14 Dec 2025 15:00:44 -0600 Subject: [PATCH 37/40] Simplify code. --- Source/Engine/Level/Actors/AnimatedModel.cpp | 27 ++------------------ Source/Engine/Level/Actors/AnimatedModel.h | 16 ++---------- 2 files changed, 4 insertions(+), 39 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index a8c091eb9..991ac3e05 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -554,20 +554,7 @@ void AnimatedModel::StopSlotAnimation(const StringView& slotName, Animation* ani { for (auto& slot : GraphInstance.Slots) { - if (slot.Animation == anim && slot.Name == slotName) - { - //slot.Animation = nullptr; // TODO: make an immediate version of this method and set the animation to nullptr. - slot.Reset = true; - break; - } - } -} - -void AnimatedModel::StopSlotAnimation(const StringView& slotName) -{ - for (auto& slot : GraphInstance.Slots) - { - if (slot.Name == slotName) + if ((slot.Animation == anim || anim == nullptr) && slot.Name == slotName) { //slot.Animation = nullptr; // TODO: make an immediate version of this method and set the animation to nullptr. if (slot.Animation != nullptr) @@ -609,17 +596,7 @@ bool AnimatedModel::IsPlayingSlotAnimation(const StringView& slotName, Animation { for (auto& slot : GraphInstance.Slots) { - if (slot.Animation == anim && slot.Name == slotName && !slot.Pause) - return true; - } - return false; -} - -bool AnimatedModel::IsPlayingSlotAnimation(const StringView& slotName) -{ - for (auto& slot : GraphInstance.Slots) - { - if (slot.Name == slotName && !slot.Pause) + if ((slot.Animation == anim || anim == nullptr) && slot.Name == slotName && !slot.Pause) return true; } return false; diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index dc837c658..344f633cc 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -413,13 +413,7 @@ public: /// /// The name of the slot. /// The animation to stop. - API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim); - - /// - /// Stops the animation playback on the slot in Anim Graph. - /// - /// The name of the slot. - API_FUNCTION() void StopSlotAnimation(const StringView& slotName); + API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim = nullptr); /// /// Pauses all the animations playback on the all slots in Anim Graph. @@ -443,13 +437,7 @@ public: /// /// The name of the slot. /// The animation to check. - API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim); - - /// - /// Checks if the animation playback is active on the slot in Anim Graph (not paused). - /// - /// The name of the slot. - API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName); + API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim = nullptr); private: void ApplyRootMotion(const Transform& rootMotionDelta); From 5fdbed2b56f9d1f738f4ed499efafd316c1eb2f2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 14 Dec 2025 22:41:00 +0100 Subject: [PATCH 38/40] Minor codestyle adjustments --- Source/Engine/Core/Collections/HashSetBase.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Core/Collections/HashSetBase.h b/Source/Engine/Core/Collections/HashSetBase.h index 58f1702c2..94bbd149d 100644 --- a/Source/Engine/Core/Collections/HashSetBase.h +++ b/Source/Engine/Core/Collections/HashSetBase.h @@ -408,14 +408,14 @@ protected: } else { - const int32 elementsCount = _elementsCount; // Rebuild entire table completely + const int32 elementsCount = _elementsCount; const int32 oldSize = _size; AllocationData oldAllocation; AllocationUtils::MoveToEmpty(oldAllocation, _allocation, oldSize, oldSize); _allocation.Allocate(_size); BucketType* data = _allocation.Get(); - for (int32 i = 0; i < _size; ++i) + for (int32 i = 0; i < oldSize; ++i) data[i]._state = HashSetBucketState::Empty; BucketType* oldData = oldAllocation.Get(); FindPositionResult pos; @@ -430,8 +430,8 @@ protected: // Grow and retry to handle pathological cases (eg. heavy collisions) EnsureCapacity(_size + 1, true); FindPosition(oldBucket.GetKey(), pos); + ASSERT(pos.FreeSlotIndex != -1); } - ASSERT(pos.FreeSlotIndex != -1); BucketType& bucket = _allocation.Get()[pos.FreeSlotIndex]; bucket = MoveTemp(oldBucket); } From 2b6339c05cd8a66e7964b29076c3b3053f19c891 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 14 Dec 2025 22:58:53 +0100 Subject: [PATCH 39/40] Minor code cleanup --- .../Networking/NetworkReplicationHierarchy.h | 29 +++++++++++++++++++ .../Engine/Networking/NetworkReplicator.cpp | 10 +++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicationHierarchy.h b/Source/Engine/Networking/NetworkReplicationHierarchy.h index 9db851996..29cf0b30f 100644 --- a/Source/Engine/Networking/NetworkReplicationHierarchy.h +++ b/Source/Engine/Networking/NetworkReplicationHierarchy.h @@ -100,6 +100,35 @@ API_STRUCT(NoDefault, Namespace="FlaxEngine.Networking") struct FLAXENGINE_API N return Word0 + Word1 != 0; } + NetworkClientsMask operator&(const NetworkClientsMask& other) const + { + return { Word0 & other.Word0, Word1 & other.Word1 }; + } + + NetworkClientsMask operator|(const NetworkClientsMask& other) const + { + return { Word0 | other.Word0, Word1 | other.Word1 }; + } + + NetworkClientsMask operator~() const + { + return { ~Word0, ~Word1 }; + } + + NetworkClientsMask& operator|=(const NetworkClientsMask& other) + { + Word0 |= other.Word0; + Word1 |= other.Word1; + return *this; + } + + NetworkClientsMask& operator&=(const NetworkClientsMask& other) + { + Word0 &= other.Word0; + Word1 &= other.Word1; + return *this; + } + bool operator==(const NetworkClientsMask& other) const { return Word0 == other.Word0 && Word1 == other.Word1; diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 02333aa64..e5a7d232e 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -729,14 +729,12 @@ void SendReplication(ScriptingObject* obj, NetworkClientsMask targetClients) } #if USE_NETWORK_REPLICATOR_CACHE - // Check if only newly joined clients are missing this data to avoid resending it to everyone - NetworkClientsMask missingClients; - missingClients.Word0 = targetClients.Word0 & ~item.RepCache.Mask.Word0; - missingClients.Word1 = targetClients.Word1 & ~item.RepCache.Mask.Word1; - // Process replication cache to skip sending object data if it didn't change if (item.RepCache.Data.Length() == size && Platform::MemoryCompare(item.RepCache.Data.Get(), stream->GetBuffer(), size) == 0) { + // Check if only newly joined clients are missing this data to avoid resending it to everyone + NetworkClientsMask missingClients = targetClients & ~item.RepCache.Mask; + // If data is the same and only the client set changed, replicate to missing clients only if (!missingClients) return; @@ -2330,7 +2328,7 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network } else { - // If this client never had the object (eg. it was targeted to other clients only), drop the message quietly. + // If this client never had the object (eg. it was targeted to other clients only), drop the message quietly DespawnedObjects.Add(objectId); NETWORK_REPLICATOR_LOG(Warning, "[NetworkReplicator] Failed to despawn object {}", objectId); } From 056de752ed02d9d145ef7eee61e08267f589d63f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 14 Dec 2025 23:03:34 +0100 Subject: [PATCH 40/40] Add docs --- Source/Engine/Level/Actors/AnimatedModel.cpp | 2 +- Source/Engine/Level/Actors/AnimatedModel.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 991ac3e05..343710183 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -574,7 +574,7 @@ void AnimatedModel::PauseSlotAnimation(const StringView& slotName, Animation* an { for (auto& slot : GraphInstance.Slots) { - if (slot.Animation == anim && slot.Name == slotName) + if ((slot.Animation == anim || anim == nullptr) && slot.Name == slotName) { slot.Pause = true; break; diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 344f633cc..a520d6723 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -412,7 +412,7 @@ public: /// Stops the animation playback on the slot in Anim Graph. /// /// The name of the slot. - /// The animation to stop. + /// The animation to check. Null to use slot name only. API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim = nullptr); /// @@ -424,8 +424,8 @@ public: /// Pauses the animation playback on the slot in Anim Graph. /// /// The name of the slot. - /// The animation to pause. - API_FUNCTION() void PauseSlotAnimation(const StringView& slotName, Animation* anim); + /// The animation to check. Null to use slot name only. + API_FUNCTION() void PauseSlotAnimation(const StringView& slotName, Animation* anim = nullptr); /// /// Checks if any animation playback is active on any slot in Anim Graph (not paused). @@ -436,7 +436,7 @@ public: /// Checks if the animation playback is active on the slot in Anim Graph (not paused). /// /// The name of the slot. - /// The animation to check. + /// The animation to check. Null to use slot name only. API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim = nullptr); private: