diff --git a/Flax.flaxproj b/Flax.flaxproj index f6cae9501..7fd591727 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.", 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/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, ""); } diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index 0052a37a4..51c88e082 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -897,9 +897,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) { 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) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index fd62ec537..1e4444af0 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; } @@ -2553,9 +2557,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/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); } diff --git a/Source/Engine/Core/Collections/HashSetBase.h b/Source/Engine/Core/Collections/HashSetBase.h index 36fcf275d..94bbd149d 100644 --- a/Source/Engine/Core/Collections/HashSetBase.h +++ b/Source/Engine/Core/Collections/HashSetBase.h @@ -409,27 +409,36 @@ protected: else { // Rebuild entire table completely + const int32 elementsCount = _elementsCount; + 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) + for (int32 i = 0; i < oldSize; ++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); - ASSERT(pos.FreeSlotIndex != -1); + 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(); + _elementsCount = elementsCount; } _deletedCount = 0; } 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/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..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,8 +617,17 @@ public: /// /// Clears the context state. + /// [Deprecated in v1.12] /// - API_FUNCTION() virtual void ClearState() = 0; + API_FUNCTION() DEPRECATED("Use ResetState instead") 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/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/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] 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/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); } 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/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/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/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; 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/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index ee66c6c77..343710183 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -554,10 +554,11 @@ void AnimatedModel::StopSlotAnimation(const StringView& slotName, Animation* ani { for (auto& slot : GraphInstance.Slots) { - if (slot.Animation == anim && 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. - slot.Reset = true; + if (slot.Animation != nullptr) + slot.Reset = true; break; } } @@ -573,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; @@ -595,7 +596,7 @@ bool AnimatedModel::IsPlayingSlotAnimation(const StringView& slotName, Animation { for (auto& slot : GraphInstance.Slots) { - if (slot.Animation == anim && 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 a011be0d0..a520d6723 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -412,8 +412,8 @@ public: /// Stops the animation playback on the slot in Anim Graph. /// /// The name of the slot. - /// The animation to stop. - API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim); + /// The animation to check. Null to use slot name only. + API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim = nullptr); /// /// Pauses all the animations playback on the all slots in Anim Graph. @@ -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,8 +436,8 @@ 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. - API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim); + /// The animation to check. Null to use slot name only. + API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim = nullptr); private: void ApplyRootMotion(const Transform& rootMotionDelta); 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++) { 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 c584d3526..e5a7d232e 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; @@ -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]; @@ -698,14 +703,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(); @@ -728,18 +730,30 @@ void SendReplication(ScriptingObject* obj, NetworkClientsMask targetClients) #if USE_NETWORK_REPLICATOR_CACHE // 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; + // 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; + 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 +1544,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 +1851,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 +2328,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); } } 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/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index df6a720ba..664eaa964 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -52,6 +52,7 @@ Array> PlatformBase::Users; Delegate PlatformBase::UserAdded; Delegate PlatformBase::UserRemoved; void* OutOfMemoryBuffer = nullptr; +volatile int64 FatalReporting = 0; const Char* ToString(NetworkConnectionType value) { @@ -330,11 +331,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; } @@ -453,6 +463,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); diff --git a/Source/Engine/Platform/Linux/LinuxWindow.cpp b/Source/Engine/Platform/Linux/LinuxWindow.cpp index f50fbce7f..4850c1e3b 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; 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/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/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index bb580aaf3..544438bb5 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -920,6 +920,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL 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) @@ -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/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/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index ed662801e..1c8c2bcdd 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 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()))); 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: 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); 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); 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; diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index c778c03ee..09ac78e6b 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -790,9 +790,18 @@ 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); Graph* graph; for (int32 i = _callStack.Count() - 1; i >= 0; i--) { diff --git a/Source/Engine/UI/GUI/Common/Dropdown.cs b/Source/Engine/UI/GUI/Common/Dropdown.cs index 868db4b89..a227e5acd 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(2024), 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; @@ -587,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], @@ -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)) 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); }; 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/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/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"; diff --git a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs index 446771675..22f212e18 100644 --- a/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Unix/UnixToolchain.cs @@ -525,7 +525,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) { @@ -612,7 +612,7 @@ namespace Flax.Build.Platforms /// public override void LinkFiles(TaskGraph graph, BuildOptions options, string outputFilePath) { - outputFilePath = outputFilePath.Replace('\\', '/'); + outputFilePath = Utilities.NormalizePath(outputFilePath); Task linkTask; switch (options.LinkEnv.Output) diff --git a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs index 0ce3c2d79..8d581d986 100644 --- a/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs +++ b/Source/Tools/Flax.Build/Platforms/Windows/WindowsToolchainBase.cs @@ -788,6 +788,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();