From c2ec3fe2cbbb9dd6030fd0c85209c8f4a6a00f1a Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 3 Jun 2026 05:03:22 +0200 Subject: [PATCH 01/12] Simplify async render flushing code --- Source/Engine/Graphics/RenderTask.cpp | 7 +++++++ Source/Engine/Graphics/RenderTask.h | 5 +++++ Source/Engine/Renderer/Renderer.cpp | 8 ++------ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index ad8bbbdc0..94fdb11ac 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -507,3 +507,10 @@ RenderContextBatch::RenderContextBatch(const RenderContext& context) Contexts.Add(context); EnableAsync = JobSystem::GetThreadsCount() > 1; } + +void RenderContextBatch::FlushWaitLabels() +{ + for (const int64 label : WaitLabels) + JobSystem::Wait(label); + WaitLabels.Clear(); +} diff --git a/Source/Engine/Graphics/RenderTask.h b/Source/Engine/Graphics/RenderTask.h index 8cba1006e..db1332bba 100644 --- a/Source/Engine/Graphics/RenderTask.h +++ b/Source/Engine/Graphics/RenderTask.h @@ -533,4 +533,9 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API RenderContextBatch { return Contexts.Get()[0]; } + + /// + /// Waits for all scheduled async jobs to complete and clears WaitLabels. + /// + void FlushWaitLabels(); }; diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index a1f531ac1..1098692cc 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -356,9 +356,7 @@ void Renderer::DrawActors(RenderContext& renderContext, const Array& cus Level::DrawActors(renderContextBatch, SceneRendering::DrawCategory::SceneDraw); Level::DrawActors(renderContextBatch, SceneRendering::DrawCategory::SceneDrawAsync); JobSystem::SetJobStartingOnDispatch(true); - for (const int64 label : renderContextBatch.WaitLabels) - JobSystem::Wait(label); - renderContextBatch.WaitLabels.Clear(); + renderContextBatch.FlushWaitLabels(); } } @@ -483,9 +481,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont // Wait for async jobs to finish JobSystem::SetJobStartingOnDispatch(true); - for (const int64 label : renderContextBatch.WaitLabels) - JobSystem::Wait(label); - renderContextBatch.WaitLabels.Clear(); + renderContextBatch.FlushWaitLabels(); // Perform custom post-scene drawing (eg. GPU dispatches used by VFX) for (int32 i = 0; i < renderContextBatch.Contexts.Count(); i++) From f4be035f04a19a9f34ca68288ec32df91c042a71 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 3 Jun 2026 05:03:32 +0200 Subject: [PATCH 02/12] Add `Physics::DeleteScene` --- Source/Engine/Physics/Physics.cpp | 9 +++++++++ Source/Engine/Physics/Physics.h | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index ecd7c1093..24bb4c1a6 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -184,6 +184,15 @@ PhysicsScene* Physics::FindScene(const StringView& name) return nullptr; } +void Physics::DeleteScene(PhysicsScene* scene) +{ + if (scene == nullptr || scene == DefaultScene) + return; + scene->CollectResults(); + Scenes.RemoveKeepOrder(scene); + Delete(scene); +} + bool Physics::GetAutoSimulation() { return !DefaultScene || DefaultScene->GetAutoSimulation(); diff --git a/Source/Engine/Physics/Physics.h b/Source/Engine/Physics/Physics.h index 85cd5e77b..11fd3fb80 100644 --- a/Source/Engine/Physics/Physics.h +++ b/Source/Engine/Physics/Physics.h @@ -32,6 +32,11 @@ API_CLASS(Static) class FLAXENGINE_API Physics /// API_FUNCTION() static PhysicsScene* FindScene(const StringView& name); + /// + /// Delete an existing scene (excluding the default one). + /// + API_FUNCTION() static void DeleteScene(PhysicsScene* scene); + public: /// /// The automatic simulation feature. True if perform physics simulation after on fixed update by auto, otherwise user should do it. From 89a1f00c57e7ef986df77c7d2e46ab2a5a6eadd5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 28 Apr 2026 00:24:57 +0200 Subject: [PATCH 03/12] Fix `Guid` diff serialization and loading invalid values --- Source/Engine/Serialization/Serialization.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Serialization/Serialization.cpp b/Source/Engine/Serialization/Serialization.cpp index ec02fd65f..78fbf7ec5 100644 --- a/Source/Engine/Serialization/Serialization.cpp +++ b/Source/Engine/Serialization/Serialization.cpp @@ -408,7 +408,7 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian bool Serialization::ShouldSerialize(const Guid& v, const void* otherObj) { - return v.IsValid(); + return !otherObj || v != *(Guid*)otherObj; } void Serialization::Serialize(ISerializable::SerializeStream& stream, const Guid& v, const void* otherObj) @@ -427,10 +427,12 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Guid& const char* b = a + 8; const char* c = b + 8; const char* d = c + 8; - StringUtils::ParseHex(a, 8, &v.A); - StringUtils::ParseHex(b, 8, &v.B); - StringUtils::ParseHex(c, 8, &v.C); - StringUtils::ParseHex(d, 8, &v.D); + bool failed = StringUtils::ParseHex(a, 8, &v.A); + failed |= StringUtils::ParseHex(b, 8, &v.B); + failed |= StringUtils::ParseHex(c, 8, &v.C); + failed |= StringUtils::ParseHex(d, 8, &v.D); + if (failed) + v = Guid::Empty; } bool Serialization::ShouldSerialize(const DateTime& v, const void* otherObj) From 422300adbd69e2ff1c7ba169536783a05662ab44 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 3 Jun 2026 10:57:51 +0200 Subject: [PATCH 04/12] Add `VariantType::GetScriptingType` for easier type information access --- Source/Engine/AI/BehaviorKnowledge.cpp | 13 +++++-------- Source/Engine/Content/Assets/VisualScript.cpp | 2 +- Source/Engine/Core/Types/Variant.cpp | 5 +++++ Source/Engine/Core/Types/Variant.h | 1 + Source/Engine/Debug/DebugCommands.cpp | 2 +- Source/Engine/Level/Prefabs/Prefab.cpp | 2 +- Source/Engine/Visject/VisjectGraph.cpp | 2 +- 7 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Source/Engine/AI/BehaviorKnowledge.cpp b/Source/Engine/AI/BehaviorKnowledge.cpp index 4d8e2eed9..872a12834 100644 --- a/Source/Engine/AI/BehaviorKnowledge.cpp +++ b/Source/Engine/AI/BehaviorKnowledge.cpp @@ -205,10 +205,9 @@ bool BehaviorKnowledge::Set(const StringAnsiView& path, const Variant& value) bool BehaviorKnowledge::HasGoal(ScriptingTypeHandle type) const { - for (int32 i = 0; i < Goals.Count(); i++) + for (const Variant& goal : Goals) { - const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName()); - if (goalType == type) + if (goal.Type.GetScriptingType() == type) return true; } return false; @@ -218,8 +217,7 @@ const Variant& BehaviorKnowledge::GetGoal(ScriptingTypeHandle type) const { for (const Variant& goal : Goals) { - const ScriptingTypeHandle goalType = Scripting::FindScriptingType(goal.Type.GetTypeName()); - if (goalType == type) + if (goal.Type.GetScriptingType() == type) return goal; } return Variant::Null; @@ -242,10 +240,9 @@ void BehaviorKnowledge::RemoveGoal(ScriptingTypeHandle type) { for (int32 i = 0; i < Goals.Count(); i++) { - const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName()); - if (goalType == type) + if (Goals[i].Type.GetScriptingType() == type) { - Goals.RemoveAt(i); + Goals.RemoveAtKeepOrder(i); break; } } diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 62d901b6f..26b896b02 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -339,7 +339,7 @@ void VisualScriptExecutor::ProcessGroupTools(Box* box, Node* node, Value& value) obj = Value::Null; #else const ScriptingTypeHandle type = Scripting::FindScriptingType(StringAnsiView(typeNameAnsi.Get(), typeName.Length())); - const ScriptingTypeHandle objType = Scripting::FindScriptingType(obj.Type.GetTypeName()); + const ScriptingTypeHandle objType = obj.Type.GetScriptingType(); if (!type || !objType || !objType.IsSubclassOf(type)) obj = Value::Null; #endif diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 7f805f9e3..78b506b58 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -362,6 +362,11 @@ const char* VariantType::GetTypeName() const return InBuiltTypesTypeNames[Type]; } +ScriptingTypeHandle VariantType::GetScriptingType() const +{ + return Scripting::FindScriptingType(GetTypeName()); +} + VariantType VariantType::GetElementType() const { if (Type == Array) diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h index 13b928171..530a4da76 100644 --- a/Source/Engine/Core/Types/Variant.h +++ b/Source/Engine/Core/Types/Variant.h @@ -151,6 +151,7 @@ public: void SetTypeName(const ScriptingType& type); void SetTypeName(const MClass& klass); const char* GetTypeName() const; + ScriptingTypeHandle GetScriptingType() const; VariantType GetElementType() const; // Drops custom type name into the name allocated by the scripting module to reduce memory allocations when referencing types. void Inline(); diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp index feb985cd4..ac6e55de7 100644 --- a/Source/Engine/Debug/DebugCommands.cpp +++ b/Source/Engine/Debug/DebugCommands.cpp @@ -85,7 +85,7 @@ struct CommandData else if (value.Type.Type == VariantType::Structure) { // Prettify structure printing - ScriptingTypeHandle resultType = Scripting::FindScriptingType(value.Type.GetTypeName()); + ScriptingTypeHandle resultType = value.Type.GetScriptingType(); if (resultType) { Array fields; diff --git a/Source/Engine/Level/Prefabs/Prefab.cpp b/Source/Engine/Level/Prefabs/Prefab.cpp index 80120ea87..a14eae500 100644 --- a/Source/Engine/Level/Prefabs/Prefab.cpp +++ b/Source/Engine/Level/Prefabs/Prefab.cpp @@ -64,7 +64,7 @@ Actor* Prefab::GetDefaultInstance() // Skip if not loaded if (!IsLoaded()) { - LOG(Warning, "Cannot instantiate object from not loaded prefab asset."); + LOG(Warning, "Cannot instantiate object from not loaded prefab asset ({}, {})", GetPath(), GetID()); return nullptr; } diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index 8b8c27010..2b18a7d71 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -766,7 +766,7 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value) structureValue = Variant::Cast(structureValue, typeVariantType); } structureValue.InvertInline(); // Extract any Float3/Int32 into Structure type from inlined format - const ScriptingTypeHandle structureValueTypeHandle = Scripting::FindScriptingType(structureValue.Type.GetTypeName()); + const ScriptingTypeHandle structureValueTypeHandle = structureValue.Type.GetScriptingType(); if (structureValue.Type.Type != VariantType::Structure || typeHandle != structureValueTypeHandle) { OnError(node, box, String::Format(TEXT("Cannot unpack value of type {0} to structure of type {1}"), structureValue.Type, typeName)); From f6f7bbb3d01dd5ca44c05ae7e00ca9f349e63569 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 3 Jun 2026 10:58:17 +0200 Subject: [PATCH 05/12] Fix Variant static typenames caching bug in Editor --- Source/Engine/Core/Types/Variant.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Core/Types/Variant.cpp b/Source/Engine/Core/Types/Variant.cpp index 78b506b58..d4fd3fdce 100644 --- a/Source/Engine/Core/Types/Variant.cpp +++ b/Source/Engine/Core/Types/Variant.cpp @@ -125,7 +125,7 @@ VariantType::VariantType(Types type, const StringAnsiView& typeName, bool static VariantType::VariantType(Types type, const ScriptingType& sType) : VariantType(type) { - SetTypeName(sType.Fullname, sType.Module->CanReload); + SetTypeName(sType.Fullname, !sType.Module->CanReload); } VariantType::VariantType(Types type, const MClass* klass) @@ -345,13 +345,13 @@ void VariantType::SetTypeName(const StringAnsiView& typeName, bool staticName) void VariantType::SetTypeName(const ScriptingType& type) { - SetTypeName(type.Fullname, type.Module->CanReload); + SetTypeName(type.Fullname, !type.Module->CanReload); } void VariantType::SetTypeName(const MClass& klass) { #if USE_CSHARP - SetTypeName(klass.GetFullName(), klass.GetAssembly()->CanReload()); + SetTypeName(klass.GetFullName(), !klass.GetAssembly()->CanReload()); #endif } From 0f8653709919bf1262b2add4df69eb4ef088a6ca Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 3 Jun 2026 11:01:14 +0200 Subject: [PATCH 06/12] Add simpler `Variant::Enum` that auto-setups variant type from enum scripting info --- Source/Engine/Core/Types/Variant.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/Engine/Core/Types/Variant.h b/Source/Engine/Core/Types/Variant.h index 530a4da76..9532d4ec4 100644 --- a/Source/Engine/Core/Types/Variant.h +++ b/Source/Engine/Core/Types/Variant.h @@ -421,6 +421,15 @@ public: return MoveTemp(v); } + template + static typename TEnableIf::Value, Variant>::Type Enum(const T value) + { + Variant v; + v.SetType(VariantType(VariantType::Enum, StaticType().GetType())); + v.AsUint64 = (uint64)value; + return MoveTemp(v); + } + template static typename TEnableIf::Value && !TIsPointer::Value, Variant>::Type Structure(VariantType&& type, const T& value) { From bdeb89538cf129b71051444eefbf0a223994ceec Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 3 Jun 2026 11:05:17 +0200 Subject: [PATCH 07/12] Optimize auto generated Variant Types in bindings to reduce dynamic memory allocs in game builds --- .../Flax.Build/Bindings/BindingsGenerator.Cpp.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 6dd2aba6f..e67f66cde 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -203,11 +203,11 @@ namespace Flax.Build.Bindings var fullname = apiType.FullNameManaged; if (apiType.IsEnum) - return $"Variant::Enum(VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length})), {value})"; + return $"Variant::Enum(VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR), {value})"; if (apiType.IsStruct && !CppInBuildVariantStructures.Contains(apiType.Name)) { if (apiType.IsInBuild) - return $"Variant::Structure(VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length})), {(typeInfo.IsPtr ? "*" + value : value)})"; + return $"Variant::Structure(VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR), {(typeInfo.IsPtr ? "*" + value : value)})"; return $"Variant::Structure(VariantType(VariantType::Structure, {apiType.FullNameNative}::TypeInitializer.GetType()), {(typeInfo.IsPtr ? "*" + value : value)})"; } } @@ -269,7 +269,7 @@ namespace Flax.Build.Bindings { var elementType = FindApiTypeInfo(buildData, typeInfo.GenericArgs[0], caller); var elementName = $"{(elementType != null ? elementType.FullNameManaged : typeInfo.GenericArgs[0].Type)}[]"; - return $"VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length}))"; + return $"VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length}), !USE_EDITOR)"; } if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) return "VariantType(VariantType::Dictionary)"; @@ -280,11 +280,11 @@ namespace Flax.Build.Bindings { var fullname = apiType.FullNameManaged; if (apiType.IsEnum) - return $"VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length}))"; + return $"VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR)"; if (apiType.IsStruct) { if (apiType.IsInBuild) - return $"VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length}))"; + return $"VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR)"; return $"VariantType(VariantType::Structure, {apiType.FullNameNative}::TypeInitializer.GetType())"; } if (apiType.IsClass) @@ -3103,7 +3103,7 @@ namespace Flax.Build.Bindings header.Append(" Variant result;").AppendLine(); var apiType = FindApiTypeInfo(buildData, valueType, moduleInfo); var elementName = $"{(apiType != null ? apiType.FullNameManaged : valueType.Type)}[]"; - header.Append($" result.SetType(VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length})));").AppendLine(); + header.Append($" result.SetType(VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length}), !USE_EDITOR));").AppendLine(); header.Append(" auto* array = reinterpret_cast*>(result.AsData);").AppendLine(); header.Append(" array->Resize(length);").AppendLine(); header.Append(" for (int32 i = 0; i < length; i++)").AppendLine(); From e0f234c66767cafc3284911c6dbc47886dc7a338 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 3 Jun 2026 13:00:43 +0200 Subject: [PATCH 08/12] Add enum serialization as string via `EnumString` attribute --- .../Attributes/EnumStringAttribute.cs | 15 +++++ Source/Engine/Scripting/BinaryModule.cpp | 63 ++++++++----------- Source/Engine/Scripting/ScriptingType.h | 6 +- .../ExtendedDefaultContractResolver.cs | 18 ++++-- Source/Engine/Serialization/Serialization.cpp | 56 ++++++++++++++++- Source/Engine/Serialization/Serialization.h | 8 ++- .../Bindings/BindingsGenerator.Cpp.cs | 3 +- 7 files changed, 120 insertions(+), 49 deletions(-) create mode 100644 Source/Engine/Scripting/Attributes/EnumStringAttribute.cs diff --git a/Source/Engine/Scripting/Attributes/EnumStringAttribute.cs b/Source/Engine/Scripting/Attributes/EnumStringAttribute.cs new file mode 100644 index 000000000..aa9222c8b --- /dev/null +++ b/Source/Engine/Scripting/Attributes/EnumStringAttribute.cs @@ -0,0 +1,15 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// Changes enum serialization to use string names instead of integer values. This makes saved data resilient to enum reordering or changes in values (but not to renaming enums). Deserialization accepts both string names and integer values for backward compatibility. + /// + /// + [AttributeUsage(AttributeTargets.Enum)] + public sealed class EnumStringAttribute : Attribute + { + } +} diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 02358fac0..029a245c3 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -204,7 +204,7 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul Struct.SetField = setField; } -ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items) +ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items, bool stringSerialization) : ManagedClass(nullptr) , Module(module) , InitRuntime(DefaultInitRuntime) @@ -215,6 +215,7 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul , Size(size) { Enum.Items = items; + Enum.StringSerialization = stringSerialization; } ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, GetInterfaceWrapper getInterfaceWrapper) @@ -270,6 +271,7 @@ ScriptingType::ScriptingType(const ScriptingType& other) break; case ScriptingTypes::Enum: Enum.Items = other.Enum.Items; + Enum.StringSerialization = other.Enum.StringSerialization; break; case ScriptingTypes::Interface: Interface.SetupScriptVTable = other.Interface.SetupScriptVTable; @@ -323,6 +325,7 @@ ScriptingType::ScriptingType(ScriptingType&& other) break; case ScriptingTypes::Enum: Enum.Items = other.Enum.Items; + Enum.StringSerialization = other.Enum.StringSerialization; break; case ScriptingTypes::Interface: Interface.SetupScriptVTable = other.Interface.SetupScriptVTable; @@ -604,71 +607,57 @@ StringAnsiView ScriptingType::GetName() const return Fullname; } +#if BUILD_DEBUG || USE_EDITOR +#define INIT_TYPE(...) \ + module->Types.AddUninitialized(); \ + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, ##__VA_ARGS__); \ + if (module->TypeNameToTypeIndex.ContainsKey(fullname)) \ + LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); \ + module->TypeNameToTypeIndex[fullname] = TypeIndex; +#else +#define INIT_TYPE(...) \ + module->Types.AddUninitialized(); \ + new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, ##__VA_ARGS__); \ + module->TypeNameToTypeIndex[fullname] = TypeIndex; +#endif + ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { // Script - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces); } ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { // Class - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType, interfaces); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(size, initRuntime, ctor, dtor, baseType, interfaces); } ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces) : ScriptingTypeHandle(module, module->Types.Count()) { // Structure - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces); } -ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items) +ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items, bool stringSerialization) : ScriptingTypeHandle(module, module->Types.Count()) { // Enum - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, items); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(size, items, stringSerialization); } ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, ScriptingType::GetInterfaceWrapper getInterfaceWrapper) : ScriptingTypeHandle(module, module->Types.Count()) { // Interface - module->Types.AddUninitialized(); - new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, initRuntime, setupScriptVTable, setupScriptObjectVTable, getInterfaceWrapper); -#if BUILD_DEBUG - if (module->TypeNameToTypeIndex.ContainsKey(fullname)) - LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); -#endif - module->TypeNameToTypeIndex[fullname] = TypeIndex; + INIT_TYPE(initRuntime, setupScriptVTable, setupScriptObjectVTable, getInterfaceWrapper); } +#undef INIT_TYPE + CriticalSection BinaryModule::Locker; BinaryModule::BinaryModulesList& BinaryModule::GetModules() diff --git a/Source/Engine/Scripting/ScriptingType.h b/Source/Engine/Scripting/ScriptingType.h index e1fb3dc04..8a15dd336 100644 --- a/Source/Engine/Scripting/ScriptingType.h +++ b/Source/Engine/Scripting/ScriptingType.h @@ -266,6 +266,8 @@ struct FLAXENGINE_API ScriptingType { // Enum items table (the last item name is null) EnumItem* Items; + // Enum uses string names serialization instead of integer values. + bool StringSerialization; } Enum; struct @@ -290,7 +292,7 @@ struct FLAXENGINE_API ScriptingType ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime = DefaultInitRuntime, SpawnHandler spawn = DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const InterfaceImplementation* interfaces = nullptr); ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr); - ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items); + ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items, bool stringSerialization); ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, GetInterfaceWrapper getInterfaceWrapper); ScriptingType(const ScriptingType& other); ScriptingType(ScriptingType&& other); @@ -339,7 +341,7 @@ struct FLAXENGINE_API ScriptingTypeInitializer : ScriptingTypeHandle ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime = ScriptingType::DefaultInitRuntime, ScriptingType::SpawnHandler spawn = ScriptingType::DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, ScriptingType::SetupScriptVTableHandler setupScriptVTable = nullptr, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr); - ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items); + ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items, bool stringSerialization); ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, ScriptingType::GetInterfaceWrapper getInterfaceWrapper); }; diff --git a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs index b8e07e448..4f2690863 100644 --- a/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs +++ b/Source/Engine/Serialization/JsonCustomSerializers/ExtendedDefaultContractResolver.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; namespace FlaxEngine.Json.JsonCustomSerializers @@ -44,6 +45,13 @@ namespace FlaxEngine.Json.JsonCustomSerializers ((JsonObjectContract)contract).ItemReferenceLoopHandling = ReferenceLoopHandling.Serialize; } + // Check if use enum serialization as string + var type = Nullable.GetUnderlyingType(objectType) ?? objectType; + if (type.IsEnum && type.GetCustomAttribute() != null) + { + contract.Converter = new StringEnumConverter(); + } + return contract; } @@ -53,19 +61,19 @@ namespace FlaxEngine.Json.JsonCustomSerializers var contract = base.CreateDictionaryContract(objectType); // Override contract to save enums keys as integer - if (contract.DictionaryKeyType?.IsEnum ?? false) + var keyType = contract.DictionaryKeyType; + if ((keyType?.IsEnum ?? false) && keyType.GetCustomAttribute() == null) { - var enumType = contract.DictionaryKeyType; contract.DictionaryKeyResolver = name => { try { - var e = Enum.Parse(enumType, name); + var e = Enum.Parse(keyType, name); name = Convert.ToInt32(e).ToString(); } - catch + catch (Exception ex) { - // Ignore errors + Debug.Logger.LogHandler.LogWrite(LogType.Warning, $"Failed to parse enum '{name}' as {keyType.Name}: {ex.Message}"); } return name; }; diff --git a/Source/Engine/Serialization/Serialization.cpp b/Source/Engine/Serialization/Serialization.cpp index 78fbf7ec5..bf3098140 100644 --- a/Source/Engine/Serialization/Serialization.cpp +++ b/Source/Engine/Serialization/Serialization.cpp @@ -49,6 +49,54 @@ void ISerializable::DeserializeIfExists(DeserializeStream& stream, const char* m var = defaultValue;\ } +void Serialization::SerializeEnum(ISerializable::SerializeStream& stream, uint32 v, ScriptingTypeHandle typeHandle) +{ + if (typeHandle) + { + // Check if serialize enum as string + const ScriptingType& type = typeHandle.GetType(); + if (type.Type == ScriptingTypes::Enum && type.Enum.StringSerialization) + { + const auto items = type.Enum.Items; + for (int32 i = 0; items[i].Name; i++) + { + if (items[i].Value == v) + { + stream.String(items[i].Name); + return; + } + } + } + } + stream.Uint(v); +} + +int32 Serialization::DeserializeEnum(ISerializable::DeserializeStream& stream, ScriptingTypeHandle typeHandle) +{ + if (stream.IsString() && typeHandle) + { + // Deserialize enum from string + const ScriptingType& type = typeHandle.GetType(); + if (type.Type == ScriptingTypes::Enum) + { + const auto str = stream.GetStringAnsiView(); + const auto items = type.Enum.Items; + for (int32 i = 0; items[i].Name; i++) + { + if (str == items[i].Name) + { + return (int32)items[i].Value; + } + } + int32 result; + if (!StringUtils::Parse(stream.GetString(), &result)) + return result; + LOG(Warning, "Failed to parse enum '{}' as {}", str.ToString(), type.Fullname.ToString()); + } + } + return DeserializeInt(stream); +} + bool Serialization::ShouldSerialize(const VariantType& v, const void* otherObj) { return !otherObj || v != *(VariantType*)otherObj; @@ -129,7 +177,6 @@ void Serialization::Serialize(ISerializable::SerializeStream& stream, const Vari stream.Int64(v.AsInt64); break; case VariantType::Uint64: - case VariantType::Enum: stream.Uint64(v.AsUint64); break; case VariantType::Float: @@ -222,6 +269,9 @@ void Serialization::Serialize(ISerializable::SerializeStream& stream, const Vari else stream.String("", 0); break; + case VariantType::Enum: + SerializeEnum(stream, (int32)v.AsUint64, v.Type.GetScriptingType()); + break; case VariantType::ManagedObject: case VariantType::Structure: { @@ -276,7 +326,6 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian v.AsInt64 = value.GetInt64(); break; case VariantType::Uint64: - case VariantType::Enum: v.AsUint64 = value.GetUint64(); break; case VariantType::Float: @@ -371,6 +420,9 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian CHECK(value.IsString()); v.SetTypename(value.GetStringAnsiView()); break; + case VariantType::Enum: + v.AsInt64 = DeserializeEnum(value, v.Type.GetScriptingType()); + break; case VariantType::ManagedObject: case VariantType::Structure: { diff --git a/Source/Engine/Serialization/Serialization.h b/Source/Engine/Serialization/Serialization.h index 9af6d7be1..41ae4898a 100644 --- a/Source/Engine/Serialization/Serialization.h +++ b/Source/Engine/Serialization/Serialization.h @@ -38,12 +38,16 @@ namespace Serialization int32 result = 0; if (stream.IsInt()) result = stream.GetInt(); + else if (stream.IsInt64()) + result = (int32)stream.GetInt64(); else if (stream.IsFloat()) result = (int32)stream.GetFloat(); else if (stream.IsString()) StringUtils::Parse(stream.GetString(), &result); return result; } + FLAXENGINE_API void SerializeEnum(ISerializable::SerializeStream& stream, uint32 v, ScriptingTypeHandle typeHandle); + FLAXENGINE_API int32 DeserializeEnum(ISerializable::DeserializeStream& stream, ScriptingTypeHandle typeHandle); // In-build types @@ -226,12 +230,12 @@ namespace Serialization template inline typename TEnableIf::Value>::Type Serialize(ISerializable::SerializeStream& stream, const T& v, const void* otherObj) { - stream.Uint((uint32)v); + SerializeEnum(stream, (uint32)v, StaticType()); } template inline typename TEnableIf::Value>::Type Deserialize(ISerializable::DeserializeStream& stream, T& v, ISerializeModifier* modifier) { - v = (T)DeserializeInt(stream); + v = (T)DeserializeEnum(stream, StaticType()); } // Common types diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index e67f66cde..c3d2efbd9 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -2761,7 +2761,8 @@ namespace Flax.Build.Bindings contents.Append($"ScriptingTypeInitializer {enumTypeNameInternal}_TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), "); contents.Append($"StringAnsiView(\"{enumTypeNameManaged}\", {enumTypeNameManaged.Length}), "); contents.Append($"sizeof({enumTypeNameNative}), "); - contents.Append($"{enumTypeNameInternal}Internal::Items);").AppendLine(); + var stringSerialization = enumInfo.Attributes != null && enumInfo.Attributes.Contains("EnumString") ? "true" : "false"; + contents.Append($"{enumTypeNameInternal}Internal::Items, {stringSerialization});").AppendLine(); contents.AppendLine($"template<> {moduleInfo.Name.ToUpperInvariant()}_API ScriptingTypeHandle StaticType<{enumTypeNameNative}>() {{ return {enumTypeNameInternal}_TypeInitializer; }}"); } From fd8ae9bc2b65a2931944aed2c28fc6fe10d947ea Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 3 Jun 2026 13:01:11 +0200 Subject: [PATCH 09/12] Rename `SceneRenderTask::RenderingPercentage` to `RenderScale` --- .../Editor/Windows/GraphicsQualityWindow.cs | 8 +++--- Source/Engine/Graphics/RenderTask.cpp | 25 +++++++++++-------- Source/Engine/Graphics/RenderTask.h | 8 +++++- Source/Engine/Renderer/Renderer.cpp | 4 ++- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/Source/Editor/Windows/GraphicsQualityWindow.cs b/Source/Editor/Windows/GraphicsQualityWindow.cs index 27b131404..02e2da9bf 100644 --- a/Source/Editor/Windows/GraphicsQualityWindow.cs +++ b/Source/Editor/Windows/GraphicsQualityWindow.cs @@ -98,10 +98,10 @@ namespace FlaxEditor.Windows [NoSerialize, DefaultValue(1.0f), Limit(0.05f, 5, 0)] [EditorOrder(1400), EditorDisplay("Quality")] [Tooltip("The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.")] - public float RenderingPercentage + public float RenderScale { - get => MainRenderTask.Instance.RenderingPercentage; - set => MainRenderTask.Instance.RenderingPercentage = value; + get => MainRenderTask.Instance.RenderScale; + set => MainRenderTask.Instance.RenderScale = value; } [NoSerialize, DefaultValue(RenderingUpscaleLocation.AfterAntiAliasingPass), VisibleIf(nameof(UpscaleLocation_Visible))] @@ -113,7 +113,7 @@ namespace FlaxEditor.Windows set => MainRenderTask.Instance.UpscaleLocation = value; } - private bool UpscaleLocation_Visible => MainRenderTask.Instance.RenderingPercentage < 1.0f; + private bool UpscaleLocation_Visible => MainRenderTask.Instance.RenderScale < 1.0f; [NoSerialize, DefaultValue(1.0f), Limit(0, 1)] [EditorOrder(1500), EditorDisplay("Quality"), Tooltip("The global density scale for all foliage instances. The default value is 1. Use values from range 0-1. Lower values decrease amount of foliage instances in-game. Use it to tweak game performance for slower devices.")] diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index 94fdb11ac..31639c39d 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -353,8 +353,11 @@ Viewport SceneRenderTask::GetViewport() const viewport = Buffers->GetViewport(); else viewport = Viewport(0, 0, 1280, 720); - viewport.Width *= RenderingPercentage; - viewport.Height *= RenderingPercentage; +PRAGMA_DISABLE_DEPRECATION_WARNINGS + float renderScale = RenderingPercentage * RenderScale; +PRAGMA_ENABLE_DEPRECATION_WARNINGS + viewport.Width *= renderScale; + viewport.Height *= renderScale; return viewport; } @@ -394,13 +397,16 @@ void SceneRenderTask::OnBegin(GPUContext* context) } // Setup render buffers for the output rendering resolution +PRAGMA_DISABLE_DEPRECATION_WARNINGS + float renderScale = RenderingPercentage * RenderScale; +PRAGMA_ENABLE_DEPRECATION_WARNINGS if (Output) { - Buffers->Init((int32)((float)Output->Width() * RenderingPercentage), (int32)((float)Output->Height() * RenderingPercentage)); + Buffers->Init((int32)((float)Output->Width() * renderScale), (int32)((float)Output->Height() * renderScale)); } else if (SwapChain) { - Buffers->Init((int32)((float)SwapChain->GetWidth() * RenderingPercentage), (int32)((float)SwapChain->GetHeight() * RenderingPercentage)); + Buffers->Init((int32)((float)SwapChain->GetWidth() * renderScale), (int32)((float)SwapChain->GetHeight() * renderScale)); } } @@ -434,7 +440,10 @@ bool SceneRenderTask::Resize(int32 width, int32 height) PROFILE_MEM(Graphics); if (Output && Output->Resize(width, height)) return true; - if (Buffers && Buffers->Init((int32)((float)width * RenderingPercentage), (int32)((float)height * RenderingPercentage))) +PRAGMA_DISABLE_DEPRECATION_WARNINGS + float renderScale = RenderingPercentage * RenderScale; +PRAGMA_ENABLE_DEPRECATION_WARNINGS + if (Buffers && Buffers->Init((int32)((float)width * renderScale), (int32)((float)height * renderScale))) return true; return false; } @@ -477,12 +486,6 @@ void MainRenderTask::OnBegin(GPUContext* context) // Use the main camera for the game (can be later overriden in Begin event by external code) Camera = Camera::GetMainCamera(); -#if !USE_EDITOR - // Sync render buffers size with the backbuffer - const auto size = Screen::GetSize(); - Buffers->Init((int32)(size.X * RenderingPercentage), (int32)(size.Y * RenderingPercentage)); -#endif - SceneRenderTask::OnBegin(context); } diff --git a/Source/Engine/Graphics/RenderTask.h b/Source/Engine/Graphics/RenderTask.h index db1332bba..a4dbb5151 100644 --- a/Source/Engine/Graphics/RenderTask.h +++ b/Source/Engine/Graphics/RenderTask.h @@ -268,8 +268,14 @@ public: /// /// The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer. + /// [Deprecated in v1.13] /// - API_FIELD() float RenderingPercentage = 1.0f; + API_FIELD() DEPRECATED("Use RenderScale instead.") float RenderingPercentage = 1.0f; + + /// + /// The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer. + /// + API_FIELD() float RenderScale = 1.0f; /// /// The image resolution upscale location within rendering pipeline. Unused if RenderingPercentage is 1. diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 1098692cc..6173a1f5a 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -733,7 +733,9 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont } // Upscaling after scene rendering but before post processing - bool useUpscaling = task->RenderingPercentage < 1.0f; +PRAGMA_DISABLE_DEPRECATION_WARNINGS + bool useUpscaling = task->RenderingPercentage * task->RenderScale < 1.0f; +PRAGMA_ENABLE_DEPRECATION_WARNINGS const Viewport outputViewport = task->GetOutputViewport(); if (useUpscaling && setup.UpscaleLocation == RenderingUpscaleLocation::BeforePostProcessingPass) { From 27ee42b0a13810fce6b53f4e2adf6526e8d3d149 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 3 Jun 2026 13:01:26 +0200 Subject: [PATCH 10/12] Bump up build number --- Flax.flaxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flax.flaxproj b/Flax.flaxproj index 04a161cf2..ed79458d2 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -4,7 +4,7 @@ "Major": 1, "Minor": 12, "Revision": 0, - "Build": 6913 + "Build": 6914 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2026 Wojciech Figat. All rights reserved.", From eed227aa794c53586b98b8428d7b6b1339189a36 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 3 Jun 2026 14:15:40 +0200 Subject: [PATCH 11/12] Add distance-scale to vertex paint vertices Add vertex paint brush size changing with shift+scroll Fix vertex paint brush size to match the highlight sphere --- Source/Editor/Tools/VertexPainting.cs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Tools/VertexPainting.cs b/Source/Editor/Tools/VertexPainting.cs index 643441c46..dea5247f4 100644 --- a/Source/Editor/Tools/VertexPainting.cs +++ b/Source/Editor/Tools/VertexPainting.cs @@ -8,6 +8,7 @@ using FlaxEditor.Gizmo; using FlaxEditor.GUI.Tabs; using FlaxEditor.Modules; using FlaxEditor.SceneGraph; +using FlaxEditor.Utilities; using FlaxEditor.Viewport.Modes; using FlaxEngine; using FlaxEngine.GUI; @@ -307,7 +308,7 @@ namespace FlaxEditor.Tools public VertexPaintingGizmo Gizmo; public VertexColorsPreviewMode PreviewMode = VertexColorsPreviewMode.RGB; - public float PreviewVertexSize = 6.0f; + public float PreviewVertexSize = 4.0f; public float BrushSize = 100.0f; public float BrushStrength = 1.0f; public float BrushFalloff = 1.0f; @@ -402,7 +403,7 @@ namespace FlaxEditor.Tools if (meshDatas == null) throw new Exception("Missing mesh data of the model to paint."); var instanceTransform = _selectedModel.Transform; - var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize); + var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize * 0.5f); if (_paintUpdateCount == 0 && !_selectedModel.HasVertexColors) { // Initialize the instance vertex colors with originals from the asset @@ -509,6 +510,13 @@ namespace FlaxEditor.Tools return; } + // Increase or decrease brush size with scroll + if (Input.GetKey(KeyboardKeys.Shift) && !Input.GetMouseButton(MouseButton.Right)) + { + _gizmoMode.BrushSize += dt * _gizmoMode.BrushSize * Input.Mouse.ScrollDelta * 5f; + _gizmoMode.BrushSize = Mathf.Clamp(_gizmoMode.BrushSize, 0.0001f, 100000.0f); + } + // Perform detailed tracing to find cursor location for the brush var ray = Owner.MouseRay; var view = new Ray(Owner.ViewPosition, Owner.ViewDirection); @@ -570,7 +578,7 @@ namespace FlaxEditor.Tools } if (_brushModel && _brushMaterial) { - _brushMaterial.SetParameterValue("Color", new Color(1.0f, 0.85f, 0.0f)); // TODO: expose to editor options + _brushMaterial.SetParameterValue("Color", new Color(1.0f, 0.85f, 0.0f)); _brushMaterial.SetParameterValue("DepthBuffer", Owner.RenderTask.Buffers.DepthBuffer); Quaternion rotation = RootNode.RaycastNormalRotation(ref _hitNormal); Matrix transform = Matrix.Scaling(_gizmoMode.BrushSize * 0.01f) * Matrix.RotationQuaternion(rotation) * Matrix.Translation(_hitLocation - viewOrigin); @@ -586,8 +594,10 @@ namespace FlaxEditor.Tools _verticesPreviewMaterial = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.WiresDebugMaterial); } var instanceTransform = _selectedModel.Transform; - var modelScaleMatrix = Matrix.Scaling(_gizmoMode.PreviewVertexSize * 0.01f); - var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize); + var distanceScale = (float)Vector3.Distance(instanceTransform.Translation, renderContext.View.Position) / (10.0f * Units.Meters2Units); + var vertexScale = Mathf.Lerp(0.005f, 0.01f, Mathf.Saturate(distanceScale)); + var modelScaleMatrix = Matrix.Scaling(_gizmoMode.PreviewVertexSize * vertexScale); + var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize * 0.5f); var lodIndex = _gizmoMode.ModelLOD == -1 ? RenderTools.ComputeModelLOD(_selectedModel.Model, ref renderContext.View.Position, (float)_selectedModel.Sphere.Radius, ref renderContext) : _gizmoMode.ModelLOD; lodIndex = Mathf.Clamp(lodIndex, 0, meshDatas.Length - 1); var lodData = meshDatas[lodIndex]; From 1a8827ba7635ac62823eb53a14e2aca63eee2db4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 3 Jun 2026 14:21:38 +0200 Subject: [PATCH 12/12] Fix Web build when python is installed in folder with whitespaces in path --- Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp index a2c35f38f..e38a7310c 100644 --- a/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Web/WebPlatformTools.cpp @@ -191,7 +191,7 @@ bool WebPlatformTools::OnPostProcess(CookingData& data) FileSystem::GetChildDirectories(pythons, emscriptenSdk / TEXT("/python")); if (pythons.HasItems()) { - procSettings.Arguments = procSettings.FileName + TEXT(".py ") + procSettings.Arguments; + procSettings.Arguments = String::Format(TEXT("\"{}.py\" {}"), procSettings.FileName, procSettings.Arguments); #if PLATFORM_WINDOWS procSettings.FileName = pythons[0] / TEXT("/python.exe"); #else